diff --git a/liana-gui/src/delete.rs b/liana-gui/src/delete.rs index 1caf53d7..57620e30 100644 --- a/liana-gui/src/delete.rs +++ b/liana-gui/src/delete.rs @@ -143,7 +143,7 @@ pub async fn delete_wallet( .await .map_err(|e| DeleteError::Connect(e.to_string()))? { - tracing::info!("Deleting wallet on Liana-Connect {} plateform", network); + tracing::info!("Deleting wallet on Liana-Connect {} backend", network); client .delete_wallet() .await diff --git a/liana-gui/src/launcher.rs b/liana-gui/src/launcher.rs index bed87363..2dae5e17 100644 --- a/liana-gui/src/launcher.rs +++ b/liana-gui/src/launcher.rs @@ -16,11 +16,15 @@ use tokio::runtime::Handle; use crate::{ app::{ self, - settings::{self, WalletId, WalletSettings}, + settings::{self, AuthConfig, WalletId, WalletSettings}, }, delete::{delete_wallet, DeleteError}, dir::{LianaDirectory, NetworkDirectory}, installer::UserFlow, + services::connect::{ + client::{auth::AuthClient, backend::api::UserRole, get_service_config}, + login::{connect_with_credentials, BackendState}, + }, }; const NETWORKS: [Network; 4] = [ @@ -437,6 +441,7 @@ struct DeleteWalletModal { warning: Option, deleted: bool, delete_liana_connect: bool, + user_role: Option, // `None` means we were not able to determine whether wallet uses internal bitcoind. internal_bitcoind: Option, } @@ -448,7 +453,7 @@ impl DeleteWalletModal { wallet_settings: WalletSettings, internal_bitcoind: Option, ) -> Self { - Self { + let mut modal = Self { network, wallet_settings, network_directory, @@ -456,7 +461,23 @@ impl DeleteWalletModal { deleted: false, delete_liana_connect: false, internal_bitcoind, + user_role: None, + }; + if let Some(auth) = &modal.wallet_settings.remote_backend_auth { + match Handle::current().block_on(check_membership( + modal.network, + &modal.network_directory, + auth, + )) { + Err(e) => { + modal.warning = Some(e); + } + Ok(user_role) => { + modal.user_role = user_role; + } + } } + modal } fn update(&mut self, message: Message) -> Task { @@ -500,18 +521,22 @@ impl DeleteWalletModal { )); } // Use separate `Row`s for help text in order to have better spacing. - let help_text_1 = if let Some(alias) = &self.wallet_settings.alias { - format!( - "Are you sure you want to delete the configuration and all associated data for the wallet {} (Liana-{})?", - alias, - self.wallet_settings.descriptor_checksum - ) - } else { - format!( - "Are you sure you want to delete the configuration and all associated data for the wallet Liana-{}?", - &self.wallet_settings.descriptor_checksum, - ) - }; + let help_text_1 = format!( + "Are you sure you want to {} for the wallet {}", + if self.wallet_settings.remote_backend_auth.is_some() { + "delete locally the configuration" + } else { + "delete the configuration and all associated data" + }, + if let Some(alias) = &self.wallet_settings.alias { + format!( + "{} (Liana-{})?", + alias, self.wallet_settings.descriptor_checksum + ) + } else { + format!("Liana-{}?", &self.wallet_settings.descriptor_checksum) + } + ); let help_text_2 = match self.internal_bitcoind { Some(true) => Some("(The Liana-managed Bitcoin node for this network will not be affected by this action.)"), Some(false) => None, @@ -538,20 +563,28 @@ impl DeleteWalletModal { .style(theme::text::destructive) .width(Length::Fill), )) - .push_maybe(self.wallet_settings.remote_backend_auth.as_ref().map(|_| { - checkbox("Delete also on Liana-Connect", self.delete_liana_connect) - .on_toggle(|v| { - ViewMessage::DeleteWallet(DeleteWalletMessage::DeleteLianaConnect( - v, - )) - }) - })) .push(Row::new().push(text(help_text_1))) .push_maybe( help_text_2 .map(|t| Row::new().push(p1_regular(t).style(theme::text::secondary))), ) .push(Row::new()) + .push_maybe(self.wallet_settings.remote_backend_auth.as_ref().map(|a| { + checkbox( + match self.user_role { + Some(UserRole::Owner) | None => "Also permanently delete this wallet from Liana Connect (for all members).".to_string(), + Some(UserRole::Member) => format!("Also disassociate {} from this Liana Connect wallet.", a.email), + }, + self.delete_liana_connect, + ) + .on_toggle_maybe(if !self.deleted { + Some(|v| { + ViewMessage::DeleteWallet(DeleteWalletMessage::DeleteLianaConnect(v)) + }) + } else { + None + }) + })) .push(Row::new().push(text(help_text_3))) .push_maybe(self.warning.as_ref().map(|w| { notification::warning(w.to_string(), w.to_string()).width(Length::Fill) @@ -577,6 +610,40 @@ impl DeleteWalletModal { } } +pub async fn check_membership( + network: Network, + network_dir: &NetworkDirectory, + auth: &AuthConfig, +) -> Result, DeleteError> { + let service_config = get_service_config(network) + .await + .map_err(|e| DeleteError::Connect(e.to_string()))?; + + if let BackendState::WalletExists(client, _, _) = connect_with_credentials( + AuthClient::new( + service_config.auth_api_url, + service_config.auth_api_public_key, + auth.email.to_string(), + ), + auth.wallet_id.clone(), + service_config.backend_api_url, + network, + network_dir, + ) + .await + .map_err(|e| DeleteError::Connect(e.to_string()))? + { + Ok(Some( + client + .user_wallet_membership() + .await + .map_err(|e| DeleteError::Connect(e.to_string()))?, + )) + } else { + Ok(None) + } +} + async fn check_network_datadir(path: NetworkDirectory) -> Result { let mut config_path = path.clone().path().to_path_buf(); config_path.push(app::config::DEFAULT_FILE_NAME); diff --git a/liana-gui/src/services/connect/client/backend/api.rs b/liana-gui/src/services/connect/client/backend/api.rs index 48581d04..2d02c425 100644 --- a/liana-gui/src/services/connect/client/backend/api.rs +++ b/liana-gui/src/services/connect/client/backend/api.rs @@ -122,6 +122,24 @@ pub struct ListWallets { pub wallets: Vec, } +#[derive(Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum UserRole { + Owner, + Member, +} + +#[derive(Deserialize)] +pub struct ListWalletMembers { + pub members: Vec, +} + +#[derive(Deserialize)] +pub struct Member { + pub user_id: String, + pub role: UserRole, +} + #[derive(Debug, Clone, Deserialize)] pub struct Provider { pub uuid: String, diff --git a/liana-gui/src/services/connect/client/backend/mod.rs b/liana-gui/src/services/connect/client/backend/mod.rs index 6e6e9d39..46e0f9dd 100644 --- a/liana-gui/src/services/connect/client/backend/mod.rs +++ b/liana-gui/src/services/connect/client/backend/mod.rs @@ -468,6 +468,27 @@ impl BackendWalletClient { .await } + pub async fn user_wallet_membership(&self) -> Result { + let list: api::ListWalletMembers = self + .inner + .request( + Method::GET, + &format!("{}/v1/wallets/{}/members", self.inner.url, self.wallet_uuid), + ) + .await + .send() + .await? + .check_success() + .await? + .json_or_error() + .await?; + list.members + .into_iter() + .find(|m| m.user_id == self.user_id()) + .map(|m| m.role) + .ok_or(DaemonError::Unexpected("Membership not found".to_string())) + } + pub async fn auth(&self) -> AccessTokenResponse { self.inner.auth.read().await.clone() }