upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/login.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/login.rs')
-rw-r--r--src/login.rs153
1 files changed, 97 insertions, 56 deletions
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 @@
1use anyhow::{bail, Context, Result}; 1use anyhow::{bail, Context, Result};
2use nostr::prelude::{FromSkStr, ToBech32}; 2use nostr::{prelude::FromSkStr, secp256k1::XOnlyPublicKey};
3use zeroize::Zeroize; 3use zeroize::Zeroize;
4 4
5#[cfg(not(test))]
6use crate::client::Client;
7#[cfg(test)]
8use crate::client::MockConnect;
5use crate::{ 9use crate::{
6 cli_interactor::{Interactor, InteractorPrompt, PromptPasswordParms}, 10 cli_interactor::{Interactor, InteractorPrompt, PromptPasswordParms},
7 config::{ConfigManagement, ConfigManager}, 11 config::{ConfigManagement, ConfigManager, UserRef},
8 key_handling::{ 12 key_handling::{
9 encryption::{EncryptDecrypt, Encryptor}, 13 encryption::{EncryptDecrypt, Encryptor},
10 users::{UserManagement, UserManager}, 14 users::{UserManagement, UserManager},
@@ -12,77 +16,114 @@ use crate::{
12}; 16};
13 17
14/// handles the encrpytion and storage of key material 18/// handles the encrpytion and storage of key material
15pub fn launch(nsec: &Option<String>, password: &Option<String>) -> Result<nostr::Keys> { 19pub async fn launch(
20 nsec: &Option<String>,
21 password: &Option<String>,
22 #[cfg(test)] client: Option<&MockConnect>,
23 #[cfg(not(test))] client: Option<&Client>,
24) -> Result<nostr::Keys> {
16 // if nsec parameter 25 // if nsec parameter
17 if let Some(nsec_unwrapped) = nsec { 26 let key = if let Some(nsec_unwrapped) = nsec {
18 // get key or fail without prompts 27 // get key or fail without prompts
19 let key = nostr::Keys::from_sk_str(nsec_unwrapped).context("invalid nsec parameter")?; 28 let key = nostr::Keys::from_sk_str(nsec_unwrapped).context("invalid nsec parameter")?;
20 println!(
21 "logged in as {}",
22 &key.public_key()
23 .to_bech32()
24 .context("public key should always produce bech32")?
25 );
26 29
27 // if password, add user to enable password login in future 30 // if password, add user to enable password login in future
28 if password.is_some() { 31 if password.is_some() {
29 UserManager::default() 32 UserManager::default()
30 .add(nsec, password) 33 .add(nsec, password)
31 .context("could not store identity")?; 34 .context("could not store identity")?;
35 } else {
36 UserManager::default().add_user_to_config(key.public_key(), None, false)?;
32 } 37 }
33 return Ok(key); 38 key
34 } 39 } else {
40 let cfg = ConfigManager
41 .load()
42 .context("failed to load application config")?;
43 // if encrypted nsec present
44 if cfg.users.last().is_some() && !cfg.users.last().unwrap().encrypted_key.is_empty() {
45 // unfortunately this line is unstable in rust:
46 // if let Some(user) = cfg.users.last() && !user.encrypted_key.is_empty() {
47 let user = cfg.users.last().unwrap();
48 let mut pass = if let Some(p) = password.clone() {
49 p
50 } else {
51 println!("login as {}", &user.metadata.name);
52 Interactor::default()
53 .password(PromptPasswordParms::default().with_prompt("password"))
54 .context("failed to get password input from interactor.password")?
55 };
35 56
36 // if encrypted nsec stored, attempt password 57 let key_result = Encryptor
37 let cfg = ConfigManager 58 .decrypt_key(&user.encrypted_key, pass.as_str())
38 .load() 59 .context("failed to decrypt key with provided password");
39 .context("failed to load application config")?; 60 pass.zeroize();
40 let key = if let Some(user) = cfg.users.last() {
41 let mut pass = if let Some(p) = password.clone() {
42 p
43 } else {
44 println!(
45 "login as {}",
46 &user
47 .public_key
48 .to_bech32()
49 .context("public key should always produce bech32")?
50 );
51 Interactor::default()
52 .password(PromptPasswordParms::default().with_prompt("password"))
53 .context("failed to get password input from interactor.password")?
54 };
55 61
56 let key_result = Encryptor 62 key_result.context(format!("failed to log in as {}", &user.metadata.name))?
57 .decrypt_key(&user.encrypted_key, pass.as_str()) 63 }
58 .context("failed to decrypt key with provided password"); 64 // no encrypted nsec present
59 pass.zeroize(); 65 else {
66 // no nsec but password supplied
67 if password.is_some() {
68 bail!("no nsec available to decrypt with specified password");
69 }
70 // otherwise add new user with nsec and password prompts
71 UserManager::default()
72 .add(nsec, password)
73 .context("failed to add user")?
74 }
75 };
60 76
61 key_result.context(format!( 77 // get user details
62 "failed to log in as {}", 78 let user_ref = if let Some(client) = client {
63 &user 79 get_user_details(&key.public_key(), client).await?
64 .public_key
65 .to_bech32()
66 .context("public key should always produce bech32")?
67 ))?
68 } else { 80 } else {
69 // no nsec but password supplied 81 // this will get user details with name as npub
70 if password.is_some() {
71 bail!("no nsec available to decrypt with specified password");
72 }
73 // otherwise add new user with nsec and password prompts
74 UserManager::default() 82 UserManager::default()
75 .add(nsec, password) 83 .get_user_from_cache(&key.public_key())?
76 .context("failed to add user")? 84 .clone()
77 }; 85 };
78 println!(
79 "logged in as {}",
80 &key.public_key()
81 .to_bech32()
82 .context("public key should always produce bech32")?
83 );
84 86
85 // fetching metdata 87 // print logged in
88 println!("logged in as {}", user_ref.metadata.name);
86 89
87 Ok(key) 90 Ok(key)
88} 91}
92
93async fn get_user_details(
94 public_key: &XOnlyPublicKey,
95 #[cfg(test)] client: &crate::client::MockConnect,
96 #[cfg(not(test))] client: &Client,
97) -> Result<UserRef> {
98 let term = console::Term::stdout();
99 term.write_line("searching for your details...")?;
100 let user_manager = UserManager::default();
101 let user_ref = user_manager
102 .get_user(
103 client,
104 public_key,
105 // 1 hour
106 60 * 60,
107 )
108 .await?;
109 term.clear_last_lines(1)?;
110 if user_ref.metadata.created_at.eq(&0) {
111 println!("cannot find your account metadata (name, etc) on relays",);
112 // TODO use secondary fallback list of relays.
113 // TODO better reporting of what relays were checked and what the user
114 // here is a starter:
115 // cannot find account details on relays:
116 // - purplepages.xyz
117 // - fallbackrelay1
118 // - ...
119 // would you like to:
120 // [-] proceed anyway
121 // - add custom fallback relays
122 } else if user_ref.relays.created_at.eq(&0) {
123 println!(
124 "cannot find your relay list. consider using another nostr client to create one to enhance your nostr experience."
125 );
126 // TODO better guidance on how to do this
127 }
128 Ok(user_ref)
129}