installer: support user/password RPC authentication

If using a self-managed node, this adds the option to authenticate
with the RPC server using user and password.
This commit is contained in:
jp1ac4 2023-12-22 17:38:26 +00:00
parent 3ccdafbda2
commit 38198cc79f
No known key found for this signature in database
GPG Key ID: A7ACD32423568D7B
4 changed files with 169 additions and 55 deletions

View File

@ -299,3 +299,22 @@ pub struct RpcAuthValues {
pub user: form::Value<String>,
pub password: form::Value<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ConfigField {
Address,
CookieFilePath,
User,
Password,
}
impl fmt::Display for ConfigField {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ConfigField::Address => write!(f, "Socket address"),
ConfigField::CookieFilePath => write!(f, "Cookie file path"),
ConfigField::User => write!(f, "User"),
ConfigField::Password => write!(f, "Password"),
}
}
}

View File

@ -5,7 +5,11 @@ use liana::miniscript::{
use std::path::PathBuf;
use super::Error;
use crate::{bitcoind::Bitcoind, download::Progress, hw::HardwareWalletMessage};
use crate::{
bitcoind::{Bitcoind, ConfigField, RpcAuthType},
download::Progress,
hw::HardwareWalletMessage,
};
use async_hwi::DeviceKind;
#[derive(Debug, Clone)]
@ -39,8 +43,8 @@ pub enum Message {
#[derive(Debug, Clone)]
pub enum DefineBitcoind {
CookiePathEdited(String),
AddressEdited(String),
ConfigFieldEdited(ConfigField, String),
RpcAuthTypeSelected(RpcAuthType),
PingBitcoindResult(Result<(), Error>),
PingBitcoind,
}

View File

@ -9,7 +9,10 @@ use bitcoin_hashes::{sha256, Hash};
#[cfg(any(target_os = "macos", target_os = "linux"))]
use flate2::read::GzDecoder;
use iced::{Command, Subscription};
use liana::{config::BitcoindConfig, miniscript::bitcoin::Network};
use liana::{
config::{BitcoindConfig, BitcoindRpcAuth},
miniscript::bitcoin::Network,
};
#[cfg(any(target_os = "macos", target_os = "linux"))]
use tar::Archive;
use tracing::info;
@ -21,7 +24,7 @@ use liana_ui::{component::form, widget::*};
use crate::{
bitcoind::{
self, bitcoind_network_dir, internal_bitcoind_datadir, internal_bitcoind_directory,
Bitcoind, StartInternalBitcoindError, VERSION,
Bitcoind, ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError, VERSION,
},
download,
hw::HardwareWallets,
@ -465,7 +468,8 @@ impl Step for SelectBitcoindTypeStep {
}
pub struct DefineBitcoind {
cookie_path: form::Value<String>,
rpc_auth_vals: RpcAuthValues,
selected_auth_type: RpcAuthType,
address: form::Value<String>,
is_running: Option<Result<(), Error>>,
}
@ -473,7 +477,8 @@ pub struct DefineBitcoind {
impl DefineBitcoind {
pub fn new() -> Self {
Self {
cookie_path: form::Value::default(),
rpc_auth_vals: RpcAuthValues::default(),
selected_auth_type: RpcAuthType::CookieFile,
address: form::Value::default(),
is_running: None,
}
@ -481,16 +486,28 @@ impl DefineBitcoind {
pub fn ping(&self) -> Command<Message> {
let address = self.address.value.to_owned();
let cookie_path = self.cookie_path.value.to_owned();
let selected_auth_type = self.selected_auth_type;
let rpc_auth_vals = self.rpc_auth_vals.clone();
Command::perform(
async move {
let cookie = std::fs::read_to_string(&cookie_path)
.map_err(|e| Error::Bitcoind(format!("Failed to read cookie file: {}", e)))?;
let builder = match selected_auth_type {
RpcAuthType::CookieFile => {
let cookie_path = rpc_auth_vals.cookie_path.value;
let cookie = std::fs::read_to_string(&cookie_path).map_err(|e| {
Error::Bitcoind(format!("Failed to read cookie file: {}", e))
})?;
SimpleHttpTransport::builder().cookie_auth(cookie)
}
RpcAuthType::UserPass => {
let user = rpc_auth_vals.user.value;
let password = rpc_auth_vals.password.value;
SimpleHttpTransport::builder().auth(user, Some(password))
}
};
let client = Client::with_transport(
SimpleHttpTransport::builder()
builder
.url(&address)?
.timeout(std::time::Duration::from_secs(3))
.cookie_auth(cookie)
.build(),
);
client.send_request(client.build_request("echo", &[]))?;
@ -503,8 +520,8 @@ impl DefineBitcoind {
impl Step for DefineBitcoind {
fn load_context(&mut self, ctx: &Context) {
if self.cookie_path.value.is_empty() {
self.cookie_path.value =
if self.rpc_auth_vals.cookie_path.value.is_empty() {
self.rpc_auth_vals.cookie_path.value =
bitcoind_default_cookie_path(&ctx.bitcoin_config.network).unwrap_or_default()
}
if self.address.value.is_empty() {
@ -519,15 +536,31 @@ impl Step for DefineBitcoind {
return self.ping();
}
message::DefineBitcoind::PingBitcoindResult(res) => self.is_running = Some(res),
message::DefineBitcoind::AddressEdited(address) => {
message::DefineBitcoind::ConfigFieldEdited(field, value) => match field {
ConfigField::Address => {
self.is_running = None;
self.address.value = value;
self.address.valid = true;
}
ConfigField::CookieFilePath => {
self.is_running = None;
self.rpc_auth_vals.cookie_path.value = value;
self.rpc_auth_vals.cookie_path.valid = true;
}
ConfigField::User => {
self.is_running = None;
self.rpc_auth_vals.user.value = value;
self.rpc_auth_vals.user.valid = true;
}
ConfigField::Password => {
self.is_running = None;
self.rpc_auth_vals.password.value = value;
self.rpc_auth_vals.password.valid = true;
}
},
message::DefineBitcoind::RpcAuthTypeSelected(auth_type) => {
self.is_running = None;
self.address.value = address;
self.address.valid = true;
}
message::DefineBitcoind::CookiePathEdited(path) => {
self.is_running = None;
self.cookie_path.value = path;
self.address.valid = true;
self.selected_auth_type = auth_type;
}
};
};
@ -535,28 +568,29 @@ impl Step for DefineBitcoind {
}
fn apply(&mut self, ctx: &mut Context) -> bool {
match (
PathBuf::from_str(&self.cookie_path.value),
std::net::SocketAddr::from_str(&self.address.value),
) {
(Err(_), Ok(_)) => {
self.cookie_path.valid = false;
false
let addr = std::net::SocketAddr::from_str(&self.address.value);
let rpc_auth = match self.selected_auth_type {
RpcAuthType::CookieFile => {
if let Ok(path) = PathBuf::from_str(&self.rpc_auth_vals.cookie_path.value) {
Some(BitcoindRpcAuth::CookieFile(path))
} else {
self.rpc_auth_vals.cookie_path.valid = false;
None
}
}
(Ok(_), Err(_)) => {
RpcAuthType::UserPass => Some(BitcoindRpcAuth::UserPass(
self.rpc_auth_vals.user.value.clone(),
self.rpc_auth_vals.password.value.clone(),
)),
};
match (rpc_auth, addr) {
(None, Ok(_)) => false,
(_, Err(_)) => {
self.address.valid = false;
false
}
(Err(_), Err(_)) => {
self.cookie_path.valid = false;
self.address.valid = false;
false
}
(Ok(path), Ok(addr)) => {
ctx.bitcoind_config = Some(BitcoindConfig {
rpc_auth: liana::config::BitcoindRpcAuth::CookieFile(path),
addr,
});
(Some(rpc_auth), Ok(addr)) => {
ctx.bitcoind_config = Some(BitcoindConfig { rpc_auth, addr });
true
}
}
@ -566,7 +600,8 @@ impl Step for DefineBitcoind {
view::define_bitcoin(
progress,
&self.address,
&self.cookie_path,
&self.rpc_auth_vals,
&self.selected_auth_type,
self.is_running.as_ref(),
)
}
@ -803,7 +838,7 @@ impl Step for InternalBitcoindStep {
match Bitcoind::start(
&self.network,
BitcoindConfig {
rpc_auth: liana::config::BitcoindRpcAuth::CookieFile(cookie_path),
rpc_auth: BitcoindRpcAuth::CookieFile(cookie_path),
addr: internal_bitcoind_address(rpc_port),
},
&self.liana_datadir,

View File

@ -1,5 +1,6 @@
use iced::widget::{
checkbox, container, pick_list, scrollable, scrollable::Properties, slider, Space, TextInput,
checkbox, container, pick_list, radio, scrollable, scrollable::Properties, slider, Space,
TextInput,
};
use iced::{alignment, widget::progress_bar, Alignment, Length};
@ -21,7 +22,7 @@ use liana_ui::{
};
use crate::{
bitcoind::StartInternalBitcoindError,
bitcoind::{ConfigField, RpcAuthType, RpcAuthValues, StartInternalBitcoindError},
hw::HardwareWallet,
installer::{
message::{self, Message},
@ -787,14 +788,18 @@ pub fn help_backup<'a>() -> Element<'a, Message> {
pub fn define_bitcoin<'a>(
progress: (usize, usize),
address: &form::Value<String>,
cookie_path: &form::Value<String>,
rpc_auth_vals: &RpcAuthValues,
selected_auth_type: &RpcAuthType,
is_running: Option<&Result<(), Error>>,
) -> Element<'a, Message> {
let col_address = Column::new()
.push(text("Address:").bold())
.push(
form::Form::new_trimmed("Address", address, |msg| {
Message::DefineBitcoind(message::DefineBitcoind::AddressEdited(msg))
Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited(
ConfigField::Address,
msg,
))
})
.warning("Please enter correct address")
.size(20)
@ -802,16 +807,67 @@ pub fn define_bitcoin<'a>(
)
.spacing(10);
let col_cookie = Column::new()
.push(text("Cookie path:").bold())
let col_auth = Column::new()
.push(
form::Form::new_trimmed("Cookie path", cookie_path, |msg| {
Message::DefineBitcoind(message::DefineBitcoind::CookiePathEdited(msg))
})
.warning("Please enter correct path")
.size(20)
.padding(10),
[RpcAuthType::CookieFile, RpcAuthType::UserPass]
.iter()
.fold(
Row::new()
.push(text("RPC authentication:").small().bold())
.spacing(10),
|row, auth_type| {
row.push(radio(
format!("{}", auth_type),
*auth_type,
Some(*selected_auth_type),
|new_selection| {
Message::DefineBitcoind(
message::DefineBitcoind::RpcAuthTypeSelected(new_selection),
)
},
))
.spacing(30)
.align_items(Alignment::Center)
},
),
)
.push(match selected_auth_type {
RpcAuthType::CookieFile => Row::new().push(
form::Form::new_trimmed("Cookie path", &rpc_auth_vals.cookie_path, |msg| {
Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited(
ConfigField::CookieFilePath,
msg,
))
})
.warning("Please enter correct path")
.size(20)
.padding(10),
),
RpcAuthType::UserPass => Row::new()
.push(
form::Form::new_trimmed("User", &rpc_auth_vals.user, |msg| {
Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited(
ConfigField::User,
msg,
))
})
.warning("Please enter correct user")
.size(20)
.padding(10),
)
.push(
form::Form::new_trimmed("Password", &rpc_auth_vals.password, |msg| {
Message::DefineBitcoind(message::DefineBitcoind::ConfigFieldEdited(
ConfigField::Password,
msg,
))
})
.warning("Please enter correct password")
.size(20)
.padding(10),
)
.spacing(10),
})
.spacing(10);
layout(
@ -819,7 +875,7 @@ pub fn define_bitcoin<'a>(
"Set up connection to the Bitcoin full node",
Column::new()
.push(col_address)
.push(col_cookie)
.push(col_auth)
.push_maybe(if is_running.is_some() {
is_running.map(|res| {
if res.is_ok() {