From e237328ec611a5891586530c1d3cb26c16c1093b Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Sun, 1 Oct 2023 00:00:00 +0100 Subject: feat(login) fetch user relays and metadata get user relay list and metadata events from relays when keys are used and last fetch attempt was more than an hour ago uses user's write relays if known, otherwise uses fallback relays to achieve this a method for intergration testing event fetching from relays was added --- src/login.rs | 153 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 97 insertions(+), 56 deletions(-) (limited to 'src/login.rs') diff --git a/src/login.rs b/src/login.rs index 12fe76e..e73373a 100644 --- a/src/login.rs +++ b/src/login.rs @@ -1,10 +1,14 @@ use anyhow::{bail, Context, Result}; -use nostr::prelude::{FromSkStr, ToBech32}; +use nostr::{prelude::FromSkStr, secp256k1::XOnlyPublicKey}; use zeroize::Zeroize; +#[cfg(not(test))] +use crate::client::Client; +#[cfg(test)] +use crate::client::MockConnect; use crate::{ cli_interactor::{Interactor, InteractorPrompt, PromptPasswordParms}, - config::{ConfigManagement, ConfigManager}, + config::{ConfigManagement, ConfigManager, UserRef}, key_handling::{ encryption::{EncryptDecrypt, Encryptor}, users::{UserManagement, UserManager}, @@ -12,77 +16,114 @@ use crate::{ }; /// handles the encrpytion and storage of key material -pub fn launch(nsec: &Option, password: &Option) -> Result { +pub async fn launch( + nsec: &Option, + password: &Option, + #[cfg(test)] client: Option<&MockConnect>, + #[cfg(not(test))] client: Option<&Client>, +) -> Result { // if nsec parameter - if let Some(nsec_unwrapped) = nsec { + let key = if let Some(nsec_unwrapped) = nsec { // get key or fail without prompts let key = nostr::Keys::from_sk_str(nsec_unwrapped).context("invalid nsec parameter")?; - println!( - "logged in as {}", - &key.public_key() - .to_bech32() - .context("public key should always produce bech32")? - ); // if password, add user to enable password login in future if password.is_some() { UserManager::default() .add(nsec, password) .context("could not store identity")?; + } else { + UserManager::default().add_user_to_config(key.public_key(), None, false)?; } - return Ok(key); - } + key + } else { + let cfg = ConfigManager + .load() + .context("failed to load application config")?; + // if encrypted nsec present + if cfg.users.last().is_some() && !cfg.users.last().unwrap().encrypted_key.is_empty() { + // unfortunately this line is unstable in rust: + // if let Some(user) = cfg.users.last() && !user.encrypted_key.is_empty() { + let user = cfg.users.last().unwrap(); + let mut pass = if let Some(p) = password.clone() { + p + } else { + println!("login as {}", &user.metadata.name); + Interactor::default() + .password(PromptPasswordParms::default().with_prompt("password")) + .context("failed to get password input from interactor.password")? + }; - // if encrypted nsec stored, attempt password - let cfg = ConfigManager - .load() - .context("failed to load application config")?; - let key = if let Some(user) = cfg.users.last() { - let mut pass = if let Some(p) = password.clone() { - p - } else { - println!( - "login as {}", - &user - .public_key - .to_bech32() - .context("public key should always produce bech32")? - ); - Interactor::default() - .password(PromptPasswordParms::default().with_prompt("password")) - .context("failed to get password input from interactor.password")? - }; + let key_result = Encryptor + .decrypt_key(&user.encrypted_key, pass.as_str()) + .context("failed to decrypt key with provided password"); + pass.zeroize(); - let key_result = Encryptor - .decrypt_key(&user.encrypted_key, pass.as_str()) - .context("failed to decrypt key with provided password"); - pass.zeroize(); + key_result.context(format!("failed to log in as {}", &user.metadata.name))? + } + // no encrypted nsec present + else { + // no nsec but password supplied + if password.is_some() { + bail!("no nsec available to decrypt with specified password"); + } + // otherwise add new user with nsec and password prompts + UserManager::default() + .add(nsec, password) + .context("failed to add user")? + } + }; - key_result.context(format!( - "failed to log in as {}", - &user - .public_key - .to_bech32() - .context("public key should always produce bech32")? - ))? + // get user details + let user_ref = if let Some(client) = client { + get_user_details(&key.public_key(), client).await? } else { - // no nsec but password supplied - if password.is_some() { - bail!("no nsec available to decrypt with specified password"); - } - // otherwise add new user with nsec and password prompts + // this will get user details with name as npub UserManager::default() - .add(nsec, password) - .context("failed to add user")? + .get_user_from_cache(&key.public_key())? + .clone() }; - println!( - "logged in as {}", - &key.public_key() - .to_bech32() - .context("public key should always produce bech32")? - ); - // fetching metdata + // print logged in + println!("logged in as {}", user_ref.metadata.name); Ok(key) } + +async fn get_user_details( + public_key: &XOnlyPublicKey, + #[cfg(test)] client: &crate::client::MockConnect, + #[cfg(not(test))] client: &Client, +) -> Result { + let term = console::Term::stdout(); + term.write_line("searching for your details...")?; + let user_manager = UserManager::default(); + let user_ref = user_manager + .get_user( + client, + public_key, + // 1 hour + 60 * 60, + ) + .await?; + term.clear_last_lines(1)?; + if user_ref.metadata.created_at.eq(&0) { + println!("cannot find your account metadata (name, etc) on relays",); + // TODO use secondary fallback list of relays. + // TODO better reporting of what relays were checked and what the user + // here is a starter: + // cannot find account details on relays: + // - purplepages.xyz + // - fallbackrelay1 + // - ... + // would you like to: + // [-] proceed anyway + // - add custom fallback relays + } else if user_ref.relays.created_at.eq(&0) { + println!( + "cannot find your relay list. consider using another nostr client to create one to enhance your nostr experience." + ); + // TODO better guidance on how to do this + } + Ok(user_ref) +} -- cgit v1.2.3