upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib/login/existing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/login/existing.rs')
-rw-r--r--src/lib/login/existing.rs212
1 files changed, 212 insertions, 0 deletions
diff --git a/src/lib/login/existing.rs b/src/lib/login/existing.rs
new file mode 100644
index 0000000..e388a34
--- /dev/null
+++ b/src/lib/login/existing.rs
@@ -0,0 +1,212 @@
1use std::{str::FromStr, sync::Arc, time::Duration};
2
3use anyhow::{bail, Context, Result};
4use nostr::nips::nip46::NostrConnectURI;
5use nostr_connect::client::NostrConnect;
6use nostr_sdk::{NostrSigner, PublicKey};
7
8use super::{
9 key_encryption::decrypt_key,
10 print_logged_in_as,
11 user::{get_user_details, UserRef},
12 SignerInfo, SignerInfoSource,
13};
14#[cfg(not(test))]
15use crate::client::Client;
16#[cfg(test)]
17use crate::client::MockConnect;
18use crate::{
19 cli_interactor::{Interactor, InteractorPrompt, PromptPasswordParms},
20 client::fetch_public_key,
21 git::{get_git_config_item, Repo, RepoActions},
22};
23
24/// load signer from git config and UserProfile from cache or relays
25///
26/// # Parameters
27/// - `client`: include client to fetch profiles from relays that are missing
28/// from cache
29/// - `silent`: do not print outcome in termianl
30pub async fn load_existing_login(
31 git_repo: &Option<&Repo>,
32 signer_info: &Option<SignerInfo>,
33 password: &Option<String>,
34 source: &Option<SignerInfoSource>,
35 #[cfg(test)] client: Option<&MockConnect>,
36 #[cfg(not(test))] client: Option<&Client>,
37 silent: bool,
38 prompt_for_password: bool,
39) -> Result<(Arc<dyn NostrSigner>, UserRef, SignerInfoSource)> {
40 let (signer_info, source) = get_signer_info(git_repo, signer_info, password, source)?;
41
42 let (signer, public_key) = get_signer(&signer_info, prompt_for_password).await?;
43
44 let user_ref = get_user_details(
45 &public_key,
46 client,
47 if let Some(git_repo) = git_repo {
48 Some(git_repo.get_path()?)
49 } else {
50 None
51 },
52 silent,
53 )
54 .await?;
55
56 if !silent {
57 print_logged_in_as(&user_ref, client.is_none(), &source)?;
58 }
59 Ok((signer, user_ref, source))
60}
61
62/// priority order: cli arguments, local git config, global git config
63fn get_signer_info(
64 git_repo: &Option<&Repo>,
65 signer_info: &Option<SignerInfo>,
66 password: &Option<String>,
67 source: &Option<SignerInfoSource>,
68) -> Result<(SignerInfo, SignerInfoSource)> {
69 Ok(match source {
70 None => {
71 let mut result = None;
72 for source in &[
73 SignerInfoSource::CommandLineArguments,
74 SignerInfoSource::GitLocal,
75 SignerInfoSource::GitGlobal,
76 ] {
77 if let Ok(res) =
78 get_signer_info(git_repo, signer_info, password, &Some(source.clone()))
79 {
80 result = Some(res);
81 break;
82 }
83 }
84 result.context("cannot get or find signer info in cli arguments, local git config or global git config")?
85 }
86 Some(SignerInfoSource::CommandLineArguments) => {
87 if let Some(signer_info) = signer_info {
88 (signer_info.clone(), SignerInfoSource::CommandLineArguments)
89 } else {
90 bail!("cannot get signer from cli signer arguments because none were specified")
91 }
92 }
93 Some(SignerInfoSource::GitLocal) => {
94 let git_repo =
95 git_repo.context("failed to get local git config as no git_repo supplied")?;
96 if let Ok(nsec) = get_git_config_item(&Some(git_repo), "nostr.nsec")
97 .context("failed get local git config")?
98 .context("git local config item nostr.nsec doesn't exist")
99 {
100 (
101 SignerInfo::Nsec {
102 nsec: nsec.to_string(),
103 password: password.clone(),
104 npub: get_git_config_item(&Some(git_repo), "nostr.npub")
105 .context("failed get local git config")?,
106 },
107 SignerInfoSource::GitLocal,
108 )
109 } else if let Ok(bunker_uri) = get_git_config_item(&Some(git_repo), "nostr.bunker-uri")
110 .context("failed get local git config")?
111 .context("git local config item nostr.bunker-uri doesn't exist")
112 {
113 (SignerInfo::Bunker {
114 bunker_uri, bunker_app_key: get_git_config_item(&Some(git_repo), "nostr.bunker-app-key")
115 .context("failed get local git config")?
116 .context("git local config item nostr.bunker-uri exists but nostr.bunker-app-key doesn't")?,
117 npub: get_git_config_item(&Some(git_repo), "nostr.npub")
118 .context("failed get local git config")?,
119 }, SignerInfoSource::GitLocal)
120 } else {
121 bail!("no signer info in local git config")
122 }
123 }
124 Some(SignerInfoSource::GitGlobal) => {
125 if let Some(nsec) = get_git_config_item(&None, "nostr.nsec")
126 .context("failed to get global git config")?
127 {
128 (
129 SignerInfo::Nsec {
130 nsec: nsec.to_string(),
131 password: password.clone(),
132 npub: get_git_config_item(&None, "nostr.npub")
133 .context("failed to get global git config")?,
134 },
135 SignerInfoSource::GitGlobal,
136 )
137 } else if let Some(bunker_uri) = get_git_config_item(&None, "nostr.bunker-uri")
138 .context("failed to get global git config")?
139 {
140 (SignerInfo::Bunker {
141 bunker_uri, bunker_app_key: get_git_config_item(&None, "nostr.bunker-app-key")
142 .context("failed get local git config")?
143 .context("git global config item nostr.bunker-uri exists but nostr.bunker-app-key doesn't")?,
144 npub: get_git_config_item(&None, "nostr.npub")
145 .context("failed get global git config")?,
146 }, SignerInfoSource::GitGlobal)
147 } else {
148 bail!("no signer info in global git config")
149 }
150 }
151 })
152}
153
154async fn get_signer(
155 signer_info: &SignerInfo,
156 prompt_for_ncryptsec_password: bool,
157) -> Result<(Arc<dyn NostrSigner>, PublicKey)> {
158 match signer_info {
159 SignerInfo::Nsec {
160 nsec,
161 password,
162 npub: _,
163 } => {
164 let keys = if nsec.contains("ncryptsec") {
165 // TODO get user details from npub
166 // TODO add retry loop
167 // TODO in retry loop give option to login again
168 let password = if let Some(password) = password {
169 password.clone()
170 } else {
171 if !prompt_for_ncryptsec_password {
172 bail!("cannot login without prompts a nsec is encrypted with a password");
173 }
174 Interactor::default()
175 .password(PromptPasswordParms::default().with_prompt("password"))
176 .context("failed to get password input from interactor.password")?
177 };
178 decrypt_key(nsec, password.clone().as_str())
179 .context("failed to decrypt key with provided password")
180 .context("failed to decrypt ncryptsec supplied as nsec with password")?
181 } else {
182 nostr::Keys::from_str(nsec).context("invalid nsec parameter")?
183 };
184 let public_key = keys.public_key();
185 Ok((Arc::new(keys), public_key))
186 }
187 SignerInfo::Bunker {
188 bunker_uri,
189 bunker_app_key,
190 npub,
191 } => {
192 let term = console::Term::stderr();
193 term.write_line("connecting to remote signer...")?;
194 let uri = NostrConnectURI::parse(bunker_uri)?;
195 let signer: Arc<dyn NostrSigner> = Arc::new(NostrConnect::new(
196 uri,
197 nostr::Keys::from_str(bunker_app_key).context("invalid app key")?,
198 Duration::from_secs(10 * 60),
199 None,
200 )?);
201 term.clear_last_lines(1)?;
202 let public_key = if let Some(pubic_key) =
203 npub.clone().and_then(|npub| PublicKey::parse(npub).ok())
204 {
205 pubic_key
206 } else {
207 fetch_public_key(&signer).await?
208 };
209 Ok((signer, public_key))
210 }
211 }
212}