upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/lib/login/user.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2024-11-21 16:53:17 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2024-11-21 16:53:17 +0000
commitf79014235e85554e3661b3f2a02b8fa88bc192ff (patch)
treefceec3ff2df212148a3420af7cef81a3f818463e /src/lib/login/user.rs
parent91b0eac4daf92b7b740267ef203a1a8ba591974b (diff)
feat(login): overhaul login experience
* simplify login menu, making it more accessable to newcomers and easier to select remote signer options * enable `ngit login` to work from anywhere (not just a git repo) * assume fresh login details saved to global git config but fallback to local repository * maintain local repository login via `ngit login --local` * maintain login via CLI arguments eg `ngit send --nsec nsec123` * nudge users to remember nsec when pasting in ncryptsec for a better UX, whilst maintaining the option to be prompted for password everytime * create placeholder menu items for help menu and create account
Diffstat (limited to 'src/lib/login/user.rs')
-rw-r--r--src/lib/login/user.rs155
1 files changed, 154 insertions, 1 deletions
diff --git a/src/lib/login/user.rs b/src/lib/login/user.rs
index 46652db..4456308 100644
--- a/src/lib/login/user.rs
+++ b/src/lib/login/user.rs
@@ -1,7 +1,16 @@
1use std::{collections::HashSet, path::Path};
2
3use anyhow::{bail, Context, Result};
1use nostr::PublicKey; 4use nostr::PublicKey;
2use nostr_sdk::Timestamp; 5use nostr_sdk::{Alphabet, JsonUtil, Kind, SingleLetterTag, Timestamp, ToBech32};
3use serde::{self, Deserialize, Serialize}; 6use serde::{self, Deserialize, Serialize};
4 7
8#[cfg(not(test))]
9use crate::client::Client;
10#[cfg(test)]
11use crate::client::MockConnect;
12use crate::client::{get_event_from_global_cache, Connect};
13
5#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] 14#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
6pub struct UserRef { 15pub struct UserRef {
7 pub public_key: PublicKey, 16 pub public_key: PublicKey,
@@ -37,3 +46,147 @@ pub struct UserRelayRef {
37 pub read: bool, 46 pub read: bool,
38 pub write: bool, 47 pub write: bool,
39} 48}
49
50pub async fn get_user_details(
51 public_key: &PublicKey,
52 #[cfg(test)] client: Option<&MockConnect>,
53 #[cfg(not(test))] client: Option<&Client>,
54 git_repo_path: Option<&Path>,
55 cache_only: bool,
56) -> Result<UserRef> {
57 if let Ok(user_ref) = get_user_ref_from_cache(git_repo_path, public_key).await {
58 Ok(user_ref)
59 } else {
60 let empty = UserRef {
61 public_key: public_key.to_owned(),
62 metadata: extract_user_metadata(public_key, &[])?,
63 relays: extract_user_relays(public_key, &[]),
64 };
65 if cache_only {
66 Ok(empty)
67 } else if let Some(client) = client {
68 let term = console::Term::stderr();
69 term.write_line("searching for profile...")?;
70 let (_, progress_reporter) = client
71 .fetch_all(
72 git_repo_path,
73 &HashSet::new(),
74 &HashSet::from_iter(vec![*public_key]),
75 )
76 .await?;
77 if let Ok(user_ref) = get_user_ref_from_cache(git_repo_path, public_key).await {
78 progress_reporter.clear()?;
79 // if std::env::var("NGITTEST").is_err() {term.clear_last_lines(1)?;}
80 Ok(user_ref)
81 } else {
82 Ok(empty)
83 }
84 } else {
85 Ok(empty)
86 }
87 }
88}
89
90pub async fn get_user_ref_from_cache(
91 git_repo_path: Option<&Path>,
92 public_key: &PublicKey,
93) -> Result<UserRef> {
94 let filters = vec![
95 nostr::Filter::default()
96 .author(*public_key)
97 .kind(Kind::Metadata),
98 nostr::Filter::default()
99 .author(*public_key)
100 .kind(Kind::RelayList),
101 ];
102
103 let events = get_event_from_global_cache(git_repo_path, filters.clone()).await?;
104
105 if events.is_empty() {
106 bail!("no metadata and profile list in cache for selected public key");
107 }
108 Ok(UserRef {
109 public_key: public_key.to_owned(),
110 metadata: extract_user_metadata(public_key, &events)?,
111 relays: extract_user_relays(public_key, &events),
112 })
113}
114
115pub fn extract_user_metadata(
116 public_key: &nostr::PublicKey,
117 events: &[nostr::Event],
118) -> Result<UserMetadata> {
119 let event = events
120 .iter()
121 .filter(|e| e.kind.eq(&nostr::Kind::Metadata) && e.pubkey.eq(public_key))
122 .max_by_key(|e| e.created_at);
123
124 let metadata: Option<nostr::Metadata> = if let Some(event) = event {
125 Some(
126 nostr::Metadata::from_json(event.content.clone())
127 .context("metadata cannot be found in kind 0 event content")?,
128 )
129 } else {
130 None
131 };
132
133 Ok(UserMetadata {
134 name: if let Some(metadata) = metadata {
135 if let Some(n) = metadata.name {
136 n
137 } else if let Some(n) = metadata.custom.get("displayName") {
138 // strip quote marks that custom.get() adds
139 let binding = n.to_string();
140 let mut chars = binding.chars();
141 chars.next();
142 chars.next_back();
143 chars.as_str().to_string()
144 } else if let Some(n) = metadata.display_name {
145 n
146 } else {
147 public_key.to_bech32()?
148 }
149 } else {
150 public_key.to_bech32()?
151 },
152 created_at: if let Some(event) = event {
153 event.created_at
154 } else {
155 Timestamp::from(0)
156 },
157 })
158}
159
160pub fn extract_user_relays(public_key: &nostr::PublicKey, events: &[nostr::Event]) -> UserRelays {
161 let event = events
162 .iter()
163 .filter(|e| e.kind.eq(&nostr::Kind::RelayList) && e.pubkey.eq(public_key))
164 .max_by_key(|e| e.created_at);
165
166 UserRelays {
167 relays: if let Some(event) = event {
168 event
169 .tags
170 .iter()
171 .filter(|t| {
172 t.kind()
173 .eq(&nostr::TagKind::SingleLetter(SingleLetterTag::lowercase(
174 Alphabet::R,
175 )))
176 })
177 .map(|t| UserRelayRef {
178 url: t.as_slice()[1].clone(),
179 read: t.as_slice().len() == 2 || t.as_slice()[2].eq("read"),
180 write: t.as_slice().len() == 2 || t.as_slice()[2].eq("write"),
181 })
182 .collect()
183 } else {
184 vec![]
185 },
186 created_at: if let Some(event) = event {
187 event.created_at
188 } else {
189 Timestamp::from(0)
190 },
191 }
192}