Merge #1748: installer: reuse existing accounts
e5a68c359f7fc7833fded7f802802f91829f401f installer: reuse existing accounts (edouardparis) Pull request description:  ACKs for top commit: edouardparis: Self-ACK e5a68c359f7fc7833fded7f802802f91829f401f Tree-SHA512: 84551f8d5be6806efbfb1a46b5822029be6341f0b1801a9dbc8ed7677da5083f54cd203b69414cd4a3bec16c25b477a56f452ddd527cf8a7d3c87ca5636c84e2
This commit is contained in:
commit
e6dcfc4f74
@ -102,6 +102,8 @@ pub enum SelectBackend {
|
||||
// Commands messages
|
||||
OTPRequested(Result<(AuthClient, String), Error>),
|
||||
OTPResent(Result<(), Error>),
|
||||
ExistingConnectAccounts(Vec<String>),
|
||||
SelectConnectAccount(String),
|
||||
Connected(Result<context::RemoteBackend, Error>),
|
||||
}
|
||||
|
||||
|
||||
@ -137,7 +137,8 @@ impl Installer {
|
||||
BackupDescriptor::default().into(),
|
||||
RegisterDescriptor::new_create_wallet().into(),
|
||||
ChooseBackend::new(network).into(),
|
||||
RemoteBackendLogin::new(network).into(),
|
||||
RemoteBackendLogin::new(network, destination_path.network_directory(network))
|
||||
.into(),
|
||||
SelectBitcoindTypeStep::new().into(),
|
||||
InternalBitcoindStep::new(&context.liana_directory).into(),
|
||||
DefineNode::default().into(),
|
||||
@ -147,7 +148,8 @@ impl Installer {
|
||||
UserFlow::ShareXpubs => vec![ShareXpubs::new(network, signer.clone()).into()],
|
||||
UserFlow::AddWallet => vec![
|
||||
ChooseBackend::new(network).into(),
|
||||
RemoteBackendLogin::new(network).into(),
|
||||
RemoteBackendLogin::new(network, destination_path.network_directory(network))
|
||||
.into(),
|
||||
ImportRemoteWallet::new(network).into(),
|
||||
ImportDescriptor::new(network).into(),
|
||||
RecoverMnemonic::default().into(),
|
||||
|
||||
@ -7,6 +7,7 @@ use liana_ui::{component::form, widget::Element};
|
||||
|
||||
use crate::{
|
||||
daemon::DaemonError,
|
||||
dir::NetworkDirectory,
|
||||
hw::HardwareWallets,
|
||||
installer::{
|
||||
context::{self, Context, RemoteBackend},
|
||||
@ -18,6 +19,7 @@ use crate::{
|
||||
self,
|
||||
auth::{AuthClient, AuthError},
|
||||
backend::{api, BackendClient},
|
||||
cache,
|
||||
},
|
||||
};
|
||||
|
||||
@ -97,6 +99,8 @@ pub enum ConnectionStep {
|
||||
|
||||
pub struct RemoteBackendLogin {
|
||||
network: Network,
|
||||
network_dir: NetworkDirectory,
|
||||
connect_accounts: Vec<String>,
|
||||
processing: bool,
|
||||
step: ConnectionStep,
|
||||
connection_error: Option<Error>,
|
||||
@ -104,9 +108,11 @@ pub struct RemoteBackendLogin {
|
||||
}
|
||||
|
||||
impl RemoteBackendLogin {
|
||||
pub fn new(network: Network) -> Self {
|
||||
pub fn new(network: Network, network_dir: NetworkDirectory) -> Self {
|
||||
Self {
|
||||
network,
|
||||
network_dir,
|
||||
connect_accounts: Vec::new(),
|
||||
step: ConnectionStep::EnterEmail {
|
||||
email: form::Value::default(),
|
||||
},
|
||||
@ -140,6 +146,21 @@ impl Step for RemoteBackendLogin {
|
||||
.is_ok();
|
||||
email.value = value;
|
||||
}
|
||||
Message::SelectBackend(message::SelectBackend::ExistingConnectAccounts(
|
||||
accounts,
|
||||
)) => {
|
||||
self.connect_accounts = accounts;
|
||||
}
|
||||
Message::SelectBackend(message::SelectBackend::SelectConnectAccount(email)) => {
|
||||
return Task::perform(
|
||||
connect_with_existing_account(
|
||||
email,
|
||||
self.network,
|
||||
self.network_dir.clone(),
|
||||
),
|
||||
|msg| Message::SelectBackend(message::SelectBackend::Connected(msg)),
|
||||
)
|
||||
}
|
||||
Message::SelectBackend(message::SelectBackend::RequestOTP) => {
|
||||
if email.value.is_empty() {
|
||||
email.valid = false;
|
||||
@ -189,6 +210,32 @@ impl Step for RemoteBackendLogin {
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::SelectBackend(message::SelectBackend::Connected(res)) => {
|
||||
self.processing = false;
|
||||
match res {
|
||||
Ok(remote_backend) => {
|
||||
self.step = ConnectionStep::Connected {
|
||||
email: remote_backend
|
||||
.user_email()
|
||||
.expect("Gui connected to Liana backend")
|
||||
.to_string(),
|
||||
remote_backend,
|
||||
};
|
||||
return Task::perform(async move {}, |_| Message::Next);
|
||||
}
|
||||
Err(e) => {
|
||||
if let Error::Auth(AuthError { http_status, .. }) = e {
|
||||
if http_status == Some(403) {
|
||||
self.auth_error = Some("Token has expired or is invalid")
|
||||
} else {
|
||||
self.connection_error = Some(e);
|
||||
}
|
||||
} else {
|
||||
self.connection_error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
ConnectionStep::EnterOtp {
|
||||
@ -243,7 +290,6 @@ impl Step for RemoteBackendLogin {
|
||||
.map(Message::SelectBackend);
|
||||
}
|
||||
}
|
||||
|
||||
Message::SelectBackend(message::SelectBackend::Connected(res)) => {
|
||||
self.processing = false;
|
||||
match res {
|
||||
@ -281,6 +327,27 @@ impl Step for RemoteBackendLogin {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn load(&self) -> Task<Message> {
|
||||
if let Ok(cache) = cache::ConnectCache::from_file(&self.network_dir) {
|
||||
Task::perform(
|
||||
async move {
|
||||
cache
|
||||
.accounts
|
||||
.into_iter()
|
||||
.map(|a| a.email)
|
||||
.collect::<Vec<String>>()
|
||||
},
|
||||
|accounts| {
|
||||
Message::SelectBackend(message::SelectBackend::ExistingConnectAccounts(
|
||||
accounts,
|
||||
))
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
fn apply(&mut self, ctx: &mut Context) -> bool {
|
||||
if let ConnectionStep::Connected { remote_backend, .. } = &self.step {
|
||||
ctx.remote_backend = remote_backend.clone();
|
||||
@ -310,6 +377,7 @@ impl Step for RemoteBackendLogin {
|
||||
email,
|
||||
self.processing,
|
||||
self.connection_error.as_ref(),
|
||||
&self.connect_accounts,
|
||||
self.auth_error,
|
||||
),
|
||||
ConnectionStep::EnterOtp { email, otp, .. } => view::connection_step_enter_otp(
|
||||
@ -330,6 +398,36 @@ impl Step for RemoteBackendLogin {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect_with_existing_account(
|
||||
email: String,
|
||||
network: Network,
|
||||
network_dir: NetworkDirectory,
|
||||
) -> Result<context::RemoteBackend, Error> {
|
||||
let config = client::get_service_config(network).await.map_err(|e| {
|
||||
if e.status() == Some(reqwest::StatusCode::NOT_FOUND) {
|
||||
Error::Unexpected("Remote servers are unresponsive".to_string())
|
||||
} else {
|
||||
Error::Unexpected(e.to_string())
|
||||
}
|
||||
})?;
|
||||
|
||||
let client = AuthClient::new(config.auth_api_url, config.auth_api_public_key, email);
|
||||
|
||||
let mut tokens = cache::Account::from_cache(&network_dir, &client.email)
|
||||
.map_err(|_| Error::Unexpected("Account must be in cache".to_string()))?
|
||||
.ok_or(Error::Unexpected("Account must be in cache".to_string()))?
|
||||
.tokens;
|
||||
|
||||
if tokens.expires_at < chrono::Utc::now().timestamp() {
|
||||
tokens = cache::update_connect_cache(&network_dir, &tokens, &client, true)
|
||||
.await
|
||||
.map_err(|e| Error::Unexpected(format!("Failed to update cache: {}", e)))?;
|
||||
}
|
||||
|
||||
let client = BackendClient::connect(client, config.backend_api_url, tokens, network).await?;
|
||||
Ok(RemoteBackend::WithoutWallet(client))
|
||||
}
|
||||
|
||||
pub async fn connect(
|
||||
auth: AuthClient,
|
||||
token: String,
|
||||
|
||||
@ -2102,18 +2102,40 @@ pub fn login(progress: (usize, usize), connection_step: Element<Message>) -> Ele
|
||||
}
|
||||
|
||||
pub fn connection_step_enter_email<'a>(
|
||||
email: &form::Value<String>,
|
||||
email: &'a form::Value<String>,
|
||||
processing: bool,
|
||||
connection_error: Option<&Error>,
|
||||
auth_error: Option<&'static str>,
|
||||
connection_error: Option<&'a Error>,
|
||||
accounts: &'a [String],
|
||||
auth_error: Option<&'a str>,
|
||||
) -> Element<'a, Message> {
|
||||
Column::new()
|
||||
.spacing(20)
|
||||
.push_maybe(if !accounts.is_empty() {
|
||||
Some(text("Choose an account you are already using:"))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.push(
|
||||
accounts
|
||||
.iter()
|
||||
.fold(Row::new().spacing(10), |row, a| {
|
||||
row.push(
|
||||
Button::new(Container::new(p1_regular(a)).padding(5))
|
||||
.style(theme::button::secondary)
|
||||
.on_press(Message::SelectBackend(
|
||||
message::SelectBackend::SelectConnectAccount(a.clone()),
|
||||
)),
|
||||
)
|
||||
})
|
||||
.wrap(),
|
||||
)
|
||||
.push_maybe(connection_error.map(|e| text(e.to_string()).style(theme::text::warning)))
|
||||
.push_maybe(auth_error.map(|e| text(e.to_string()).style(theme::text::warning)))
|
||||
.push(text(
|
||||
"Enter the email you want to associate with the wallet:",
|
||||
))
|
||||
.push(if accounts.is_empty() {
|
||||
text("Enter an email you want to associate with the wallet:")
|
||||
} else {
|
||||
text("Or enter a new email you want to associate with the wallet:")
|
||||
})
|
||||
.push(
|
||||
form::Form::new_trimmed("email", email, |msg| {
|
||||
Message::SelectBackend(message::SelectBackend::EmailEdited(msg))
|
||||
@ -2123,13 +2145,15 @@ pub fn connection_step_enter_email<'a>(
|
||||
.warning("Email is not valid"),
|
||||
)
|
||||
.push(
|
||||
button::secondary(None, "Next")
|
||||
.on_press_maybe(if processing || !email.valid {
|
||||
None
|
||||
} else {
|
||||
Some(Message::SelectBackend(message::SelectBackend::RequestOTP))
|
||||
})
|
||||
.width(Length::Fixed(200.0)),
|
||||
Row::new().push(Space::with_width(Length::Fill)).push(
|
||||
button::secondary(None, "Send token")
|
||||
.on_press_maybe(if processing || !email.valid {
|
||||
None
|
||||
} else {
|
||||
Some(Message::SelectBackend(message::SelectBackend::RequestOTP))
|
||||
})
|
||||
.width(Length::Fixed(200.0)),
|
||||
),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
@ -27,19 +27,8 @@ impl ConnectCache {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Account {
|
||||
pub email: String,
|
||||
pub tokens: AccessTokenResponse,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn from_cache(
|
||||
network_dir: &NetworkDirectory,
|
||||
email: &str,
|
||||
) -> Result<Option<Self>, ConnectCacheError> {
|
||||
pub fn from_file(network_dir: &NetworkDirectory) -> Result<Self, ConnectCacheError> {
|
||||
let mut path = network_dir.path().to_path_buf();
|
||||
path.push(CONNECT_CACHE_FILENAME);
|
||||
|
||||
@ -53,6 +42,21 @@ impl Account {
|
||||
ConnectCacheError::ReadingFile(format!("Parsing settings file: {}", e))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Account {
|
||||
pub email: String,
|
||||
pub tokens: AccessTokenResponse,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn from_cache(
|
||||
network_dir: &NetworkDirectory,
|
||||
email: &str,
|
||||
) -> Result<Option<Self>, ConnectCacheError> {
|
||||
ConnectCache::from_file(network_dir)
|
||||
.map(|cache| cache.accounts.into_iter().find(|c| c.email == email))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user