From 3ccdafbda235730ec95d6f2992808beb2eb51aa9 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Thu, 21 Dec 2023 13:55:50 +0000 Subject: [PATCH] gui: support user/password RPC authentication This change updates the GUI to support the latest lianad config file with the user/password option for bitcoind RPC authentication. The settings have been updated to set either cookie file path or user and password. The installer still only supports the cookie file and has only been updated with the changes required to compile. When starting, the internal bitcoind now looks for the cookie file in the usual location rather than relying on the config file. --- gui/Cargo.lock | 2 +- gui/src/app/state/settings/bitcoind.rs | 74 +++++++++++++++++++---- gui/src/app/view/message.rs | 3 +- gui/src/app/view/settings.rs | 83 +++++++++++++++++++++----- gui/src/bitcoind.rs | 36 +++++++++-- gui/src/installer/step/bitcoind.rs | 4 +- 6 files changed, 164 insertions(+), 38 deletions(-) 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,