1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
use anyhow::{bail, Context, Result};
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, UserRef},
key_handling::{
encryption::{EncryptDecrypt, Encryptor},
users::{UserManagement, UserManager},
},
};
/// handles the encrpytion and storage of key material
pub async fn launch(
nsec: &Option<String>,
password: &Option<String>,
#[cfg(test)] client: Option<&MockConnect>,
#[cfg(not(test))] client: Option<&Client>,
) -> Result<(nostr::Keys, UserRef)> {
// if nsec parameter
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")?;
// 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)?;
}
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")?
};
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")?
}
};
// get user details
let user_ref = if let Some(client) = client {
get_user_details(&key.public_key(), client).await?
} else {
// this will get user details with name as npub
UserManager::default()
.get_user_from_cache(&key.public_key())?
.clone()
};
// print logged in
println!("logged in as {}", user_ref.metadata.name);
Ok((key, user_ref.clone()))
}
async fn get_user_details(
public_key: &XOnlyPublicKey,
#[cfg(test)] client: &crate::client::MockConnect,
#[cfg(not(test))] client: &Client,
) -> Result<UserRef> {
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,
// use cache for 3 minutes
3 * 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)
}
|