diff --git a/gui/Cargo.lock b/gui/Cargo.lock index c353e4cb..4b889c27 100644 --- a/gui/Cargo.lock +++ b/gui/Cargo.lock @@ -2431,7 +2431,7 @@ dependencies = [ [[package]] name = "liana" version = "4.0.0" -source = "git+https://github.com/wizardsardine/liana?branch=master#dee069e72343a67607f4429125e4c80dc0d73055" +source = "git+https://github.com/wizardsardine/liana?branch=master#b8f8d1b944120879986a71255bab19e5b342ecc8" dependencies = [ "backtrace", "bdk_coin_select", diff --git a/gui/src/app/state/settings/bitcoind.rs b/gui/src/app/state/settings/bitcoind.rs index 02b65767..37054101 100644 --- a/gui/src/app/state/settings/bitcoind.rs +++ b/gui/src/app/state/settings/bitcoind.rs @@ -8,12 +8,13 @@ use chrono::prelude::*; use iced::Command; use tracing::info; -use liana::config::{BitcoinConfig, BitcoindConfig, Config}; +use liana::config::{BitcoinConfig, BitcoindConfig, BitcoindRpcAuth, Config}; use liana_ui::{component::form, widget::Element}; use crate::{ app::{cache::Cache, error::Error, message::Message, state::settings::Setting, view, State}, + bitcoind::{RpcAuthType, RpcAuthValues}, daemon::Daemon, }; @@ -140,7 +141,8 @@ pub struct BitcoindSettings { bitcoin_config: BitcoinConfig, edit: bool, processing: bool, - cookie_path: form::Value, + rpc_auth_vals: RpcAuthValues, + selected_auth_type: RpcAuthType, addr: form::Value, daemon_is_external: bool, bitcoind_is_internal: bool, @@ -159,7 +161,33 @@ impl BitcoindSettings { daemon_is_external: bool, bitcoind_is_internal: bool, ) -> BitcoindSettings { - let path = bitcoind_config.cookie_path.to_str().unwrap().to_string(); + let (rpc_auth_vals, selected_auth_type) = match &bitcoind_config.rpc_auth { + BitcoindRpcAuth::CookieFile(path) => ( + RpcAuthValues { + cookie_path: form::Value { + valid: true, + value: path.to_str().unwrap().to_string(), + }, + user: form::Value::default(), + password: form::Value::default(), + }, + RpcAuthType::CookieFile, + ), + BitcoindRpcAuth::UserPass(user, password) => ( + RpcAuthValues { + cookie_path: form::Value::default(), + user: form::Value { + valid: true, + value: user.clone(), + }, + password: form::Value { + valid: true, + value: password.clone(), + }, + }, + RpcAuthType::UserPass, + ), + }; let addr = bitcoind_config.addr.to_string(); BitcoindSettings { daemon_is_external, @@ -168,10 +196,8 @@ impl BitcoindSettings { bitcoin_config, edit: false, processing: false, - cookie_path: form::Value { - valid: true, - value: path, - }, + rpc_auth_vals, + selected_auth_type, addr: form::Value { valid: true, value: addr, @@ -209,21 +235,41 @@ impl Setting for BitcoindSettings { if !self.processing { match field { "socket_address" => self.addr.value = value, - "cookie_file_path" => self.cookie_path.value = value, + "cookie_file_path" => self.rpc_auth_vals.cookie_path.value = value, + "user" => self.rpc_auth_vals.user.value = value, + "password" => self.rpc_auth_vals.password.value = value, _ => {} } } } + view::SettingsEditMessage::BitcoindRpcAuthTypeSelected(auth_type) => { + if !self.processing { + self.selected_auth_type = auth_type; + } + } view::SettingsEditMessage::Confirm => { let new_addr = SocketAddr::from_str(&self.addr.value); self.addr.valid = new_addr.is_ok(); - let new_path = PathBuf::from_str(&self.cookie_path.value); - self.cookie_path.valid = new_path.is_ok(); + let rpc_auth = match self.selected_auth_type { + RpcAuthType::CookieFile => { + let new_path = PathBuf::from_str(&self.rpc_auth_vals.cookie_path.value); + if let Ok(path) = new_path { + self.rpc_auth_vals.cookie_path.valid = true; + Some(BitcoindRpcAuth::CookieFile(path)) + } else { + None + } + } + RpcAuthType::UserPass => Some(BitcoindRpcAuth::UserPass( + self.rpc_auth_vals.user.value.clone(), + self.rpc_auth_vals.password.value.clone(), + )), + }; - if self.addr.valid & self.cookie_path.valid { + if let (true, Some(rpc_auth)) = (self.addr.valid, rpc_auth) { let mut daemon_config = daemon.config().cloned().unwrap(); daemon_config.bitcoind_config = Some(liana::config::BitcoindConfig { - cookie_path: new_path.unwrap(), + rpc_auth, addr: new_addr.unwrap(), }); self.processing = true; @@ -242,7 +288,8 @@ impl Setting for BitcoindSettings { self.bitcoin_config.network, cache.blockheight, &self.addr, - &self.cookie_path, + &self.rpc_auth_vals, + &self.selected_auth_type, self.processing, ) } else { @@ -342,6 +389,7 @@ impl Setting for RescanSetting { Message::StartRescan, ); } + _ => {} }; Command::none() } diff --git a/gui/src/app/view/message.rs b/gui/src/app/view/message.rs index 919441d2..44cd6043 100644 --- a/gui/src/app/view/message.rs +++ b/gui/src/app/view/message.rs @@ -1,4 +1,4 @@ -use crate::app::menu::Menu; +use crate::{app::menu::Menu, bitcoind::RpcAuthType}; use liana::miniscript::bitcoin::bip32::Fingerprint; #[derive(Debug, Clone)] @@ -75,6 +75,7 @@ pub enum SettingsMessage { pub enum SettingsEditMessage { Select, FieldEdited(&'static str, String), + BitcoindRpcAuthTypeSelected(RpcAuthType), Cancel, Confirm, } diff --git a/gui/src/app/view/settings.rs b/gui/src/app/view/settings.rs index 2b896b15..4618ad4b 100644 --- a/gui/src/app/view/settings.rs +++ b/gui/src/app/view/settings.rs @@ -3,11 +3,14 @@ use std::str::FromStr; use iced::{ alignment, - widget::{scrollable, Space}, + widget::{radio, scrollable, Space}, Alignment, Length, }; -use liana::miniscript::bitcoin::{bip32::Fingerprint, Network}; +use liana::{ + config::BitcoindRpcAuth, + miniscript::bitcoin::{bip32::Fingerprint, Network}, +}; use super::{dashboard, message::*}; @@ -26,6 +29,7 @@ use crate::{ menu::Menu, view::{hw, warning::warn}, }, + bitcoind::{RpcAuthType, RpcAuthValues}, hw::HardwareWallet, }; @@ -208,7 +212,8 @@ pub fn bitcoind_edit<'a>( network: Network, blockheight: i32, addr: &form::Value, - cookie_path: &form::Value, + rpc_auth_vals: &RpcAuthValues, + selected_auth_type: &RpcAuthType, processing: bool, ) -> Element<'a, SettingsEditMessage> { let mut col = Column::new().spacing(20); @@ -244,18 +249,60 @@ pub fn bitcoind_edit<'a>( col = col .push( - Column::new() - .push(text("Cookie file path:").bold().small()) + [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), + SettingsEditMessage::BitcoindRpcAuthTypeSelected, + )) + .spacing(30) + .align_items(Alignment::Center) + }, + ), + ) + .push(match selected_auth_type { + RpcAuthType::CookieFile => Column::new() .push( - form::Form::new_trimmed("Cookie file path", cookie_path, |value| { - SettingsEditMessage::FieldEdited("cookie_file_path", value) - }) + form::Form::new_trimmed( + "Cookie file path", + &rpc_auth_vals.cookie_path, + |value| SettingsEditMessage::FieldEdited("cookie_file_path", value), + ) .warning("Please enter a valid filesystem path") .size(20) .padding(5), ) .spacing(5), - ) + RpcAuthType::UserPass => Column::new() + .push( + Row::new() + .push( + form::Form::new_trimmed("User", &rpc_auth_vals.user, |value| { + SettingsEditMessage::FieldEdited("user", value) + }) + .warning("Please enter a valid user") + .size(20) + .padding(5), + ) + .push( + form::Form::new_trimmed("Password", &rpc_auth_vals.password, |value| { + SettingsEditMessage::FieldEdited("password", value) + }) + .warning("Please enter a valid password") + .size(20) + .padding(5), + ) + .spacing(10), + ) + .spacing(5), + }) .push( Column::new() .push(text("Socket address:").bold().small()) @@ -345,13 +392,17 @@ pub fn bitcoind<'a>( .push(separation().width(Length::Fill)); } - let rows = vec![ - ( - "Cookie file path:", - config.cookie_path.to_str().unwrap().to_string(), - ), - ("Socket address:", config.addr.to_string()), - ]; + let mut rows = vec![]; + match &config.rpc_auth { + BitcoindRpcAuth::CookieFile(path) => { + rows.push(("Cookie file path:", path.to_str().unwrap().to_string())); + } + BitcoindRpcAuth::UserPass(user, password) => { + rows.push(("User:", user.clone())); + rows.push(("Password:", password.clone())); + } + } + rows.push(("Socket address:", config.addr.to_string())); let mut col_fields = Column::new(); for (k, v) in rows { diff --git a/gui/src/bitcoind.rs b/gui/src/bitcoind.rs index f6a3f0d5..a04719d3 100644 --- a/gui/src/bitcoind.rs +++ b/gui/src/bitcoind.rs @@ -1,7 +1,9 @@ use liana::{ - config::BitcoindConfig, + config::{BitcoindConfig, BitcoindRpcAuth}, miniscript::bitcoin::{self, Network}, }; +use liana_ui::component::form; +use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::thread; @@ -222,6 +224,7 @@ impl Bitcoind { // We've started bitcoind in the background, however it may fail to start for whatever // reason. And we need its JSONRPC interface to be available to continue. Thus wait for it // to have created the cookie file, regularly checking it did not fail to start. + let cookie_path = internal_bitcoind_cookie_path(&bitcoind_datadir, network); loop { match process.try_wait() { Ok(None) => {} @@ -229,11 +232,11 @@ impl Bitcoind { Ok(Some(status)) => { log::error!("Bitcoind exited with status '{}'", status); return Err(StartInternalBitcoindError::CookieFileNotFound( - config.cookie_path.to_string_lossy().into_owned(), + cookie_path.to_string_lossy().into_owned(), )); } } - if config.cookie_path.exists() { + if cookie_path.exists() { log::info!("Bitcoind seems to have successfully started."); break; } @@ -241,9 +244,10 @@ impl Bitcoind { thread::sleep(time::Duration::from_millis(500)); } - config.cookie_path = config.cookie_path.canonicalize().map_err(|e| { + config.rpc_auth = BitcoindRpcAuth::CookieFile(cookie_path.canonicalize().map_err(|e| { StartInternalBitcoindError::CouldNotCanonicalizeCookiePath(e.to_string()) - })?; + })?); + liana::BitcoinD::new(&config, "internal_bitcoind_start".to_string()) .map_err(|e| StartInternalBitcoindError::BitcoinDError(e.to_string()))?; @@ -273,3 +277,25 @@ pub fn stop_bitcoind(config: &BitcoindConfig) -> bool { } } } + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum RpcAuthType { + CookieFile, + UserPass, +} + +impl fmt::Display for RpcAuthType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + RpcAuthType::CookieFile => write!(f, "Cookie file path"), + RpcAuthType::UserPass => write!(f, "User and password"), + } + } +} + +#[derive(Debug, Clone, Default)] +pub struct RpcAuthValues { + pub cookie_path: form::Value, + pub user: form::Value, + pub password: form::Value, +} diff --git a/gui/src/installer/step/bitcoind.rs b/gui/src/installer/step/bitcoind.rs index 544cb415..eb3461dc 100644 --- a/gui/src/installer/step/bitcoind.rs +++ b/gui/src/installer/step/bitcoind.rs @@ -554,7 +554,7 @@ impl Step for DefineBitcoind { } (Ok(path), Ok(addr)) => { ctx.bitcoind_config = Some(BitcoindConfig { - cookie_path: path, + rpc_auth: liana::config::BitcoindRpcAuth::CookieFile(path), addr, }); true @@ -803,7 +803,7 @@ impl Step for InternalBitcoindStep { match Bitcoind::start( &self.network, BitcoindConfig { - cookie_path, + rpc_auth: liana::config::BitcoindRpcAuth::CookieFile(cookie_path), addr: internal_bitcoind_address(rpc_port), }, &self.liana_datadir,