From 911b8419189750d316119a432bf4bb761aded0e1 Mon Sep 17 00:00:00 2001 From: reya Date: Fri, 13 Feb 2026 18:31:29 +0700 Subject: [PATCH] refactor auth --- crates/relay_auth/src/lib.rs | 153 +++++++++++++++-------------------- crates/settings/src/lib.rs | 84 +++++++------------ 2 files changed, 95 insertions(+), 142 deletions(-) diff --git a/crates/relay_auth/src/lib.rs b/crates/relay_auth/src/lib.rs index 6314811..8f26d4d 100644 --- a/crates/relay_auth/src/lib.rs +++ b/crates/relay_auth/src/lib.rs @@ -63,14 +63,11 @@ impl Global for GlobalRelayAuth {} // Relay authentication #[derive(Debug)] pub struct RelayAuth { - /// Entity for managing auth requests - requests: HashSet>, + /// Tasks for asynchronous operations + tasks: SmallVec<[Task<()>; 2]>, /// Event subscriptions _subscriptions: SmallVec<[Subscription; 1]>, - - /// Tasks for asynchronous operations - _tasks: SmallVec<[Task<()>; 1]>, } impl RelayAuth { @@ -87,50 +84,27 @@ impl RelayAuth { /// Create a new relay auth instance fn new(window: &mut Window, cx: &mut Context) -> Self { let nostr = NostrRegistry::global(cx); - let client = nostr.read(cx).client(); - - // Get the current entity - let entity = cx.entity(); - // Channel for communication between nostr and gpui - let (tx, rx) = flume::bounded::(100); + let (tx, rx) = flume::bounded::>(100); let mut subscriptions = smallvec![]; let mut tasks = smallvec![]; subscriptions.push( // Observe the current state - cx.observe_in(&entity, window, |this, _state, window, cx| { - let settings = AppSettings::global(cx); - let mode = AppSettings::get_auth_mode(cx); - - for req in this.requests.iter() { - let trusted_relay = settings.read(cx).trusted_relay(req.url(), cx); - - if trusted_relay && mode == AuthMode::Auto { - // Automatically authenticate if the relay is authenticated before - this.response(req, window, cx); - } else { - // Otherwise open the auth request popup - this.ask_for_approval(req, window, cx); - } + cx.observe(&nostr, move |this, state, cx| { + if state.read(cx).connected() { + this.handle_notifications(tx.clone(), cx) } }), ); - tasks.push( - // Handle nostr notifications - cx.background_spawn(async move { - Self::handle_notifications(&client, &tx).await; - }), - ); - tasks.push( // Update GPUI states - cx.spawn(async move |this, cx| { - while let Ok(request) = rx.recv_async().await { - this.update(cx, |this, cx| { - this.add_request(request, cx); + cx.spawn_in(window, async move |this, cx| { + while let Ok(req) = rx.recv_async().await { + this.update_in(cx, |this, window, cx| { + this.handle_auth(&req, window, cx); }) .ok(); } @@ -138,66 +112,72 @@ impl RelayAuth { ); Self { - requests: HashSet::new(), + tasks, _subscriptions: subscriptions, - _tasks: tasks, } } // Handle nostr notifications - async fn handle_notifications(client: &Client, tx: &flume::Sender) { - let mut notifications = client.notifications(); - let mut challenges: HashSet> = HashSet::default(); + fn handle_notifications( + &mut self, + tx: flume::Sender>, + cx: &mut Context, + ) { + let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); - while let Some(notification) = notifications.next().await { - match notification { - ClientNotification::Message { relay_url, message } => { - match message { - RelayMessage::Auth { challenge } => { - if challenges.insert(challenge.clone()) { - let request = AuthRequest::new(challenge, relay_url); - tx.send_async(request).await.ok(); - } - } - RelayMessage::Ok { - event_id, message, .. - } => { - let msg = MachineReadablePrefix::parse(&message); - let mut tracker = tracker().write().await; + let task = cx.background_spawn(async move { + let mut notifications = client.notifications(); + let mut challenges: HashSet> = HashSet::default(); - // Handle authentication messages - if let Some(MachineReadablePrefix::AuthRequired) = msg { - // Keep track of events that need to be resent after authentication - tracker.add_to_pending(event_id, relay_url); - } else { - // Keep track of events sent by Coop - tracker.sent(event_id) + while let Some(notification) = notifications.next().await { + match notification { + ClientNotification::Message { relay_url, message } => { + match message { + RelayMessage::Auth { challenge } => { + if challenges.insert(challenge.clone()) { + let request = AuthRequest::new(challenge, relay_url); + tx.send_async(Arc::new(request)).await.ok(); + } } + RelayMessage::Ok { + event_id, message, .. + } => { + let msg = MachineReadablePrefix::parse(&message); + let mut tracker = tracker().write().await; + + // Handle authentication messages + if let Some(MachineReadablePrefix::AuthRequired) = msg { + // Keep track of events that need to be resent after authentication + tracker.add_to_pending(event_id, relay_url); + } else { + // Keep track of events sent by Coop + tracker.sent(event_id) + } + } + _ => {} } - _ => {} } + ClientNotification::Shutdown => break, + _ => {} } - ClientNotification::Shutdown => break, - _ => {} } - } + }); + + self.tasks.push(task); } - /// Add a new authentication request. - fn add_request(&mut self, request: AuthRequest, cx: &mut Context) { - self.requests.insert(Arc::new(request)); - cx.notify(); - } + fn handle_auth(&mut self, req: &Arc, window: &mut Window, cx: &mut Context) { + let settings = AppSettings::global(cx); + let trusted_relay = settings.read(cx).trusted_relay(req.url(), cx); + let mode = AppSettings::get_auth_mode(cx); - /// Get the number of pending requests. - pub fn pending_requests(&self, _cx: &App) -> usize { - self.requests.len() - } - - /// Reask for approval for all pending requests. - pub fn re_ask(&mut self, window: &mut Window, cx: &mut Context) { - for request in self.requests.iter() { - self.ask_for_approval(request, window, cx); + if trusted_relay && mode == AuthMode::Auto { + // Automatically authenticate if the relay is authenticated before + self.response(req, window, cx); + } else { + // Otherwise open the auth request popup + self.ask_for_approval(req, window, cx); } } @@ -268,20 +248,17 @@ impl RelayAuth { let result = task.await; let url = req.url(); - this.update_in(cx, |this, window, cx| { + this.update_in(cx, |_this, window, cx| { + window.clear_notification(challenge, cx); + match result { Ok(_) => { - window.clear_notification(challenge, cx); - window.push_notification(format!("{} has been authenticated", url), cx); - // Save the authenticated relay to automatically authenticate future requests settings.update(cx, |this, cx| { this.add_trusted_relay(url, cx); }); - // Remove the challenge from the list of pending authentications - this.requests.remove(&req); - cx.notify(); + window.push_notification(format!("{} has been authenticated", url), cx); } Err(e) => { window.push_notification(Notification::error(e.to_string()), cx); diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 0aa9bcf..0612939 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -118,10 +118,7 @@ pub struct AppSettings { values: Settings, /// Event subscriptions - _subscriptions: SmallVec<[Subscription; 1]>, - - /// Background tasks - tasks: SmallVec<[Task>; 1]>, + _subscriptions: SmallVec<[Subscription; 2]>, } impl AppSettings { @@ -136,9 +133,7 @@ impl AppSettings { } fn new(cx: &mut Context) -> Self { - let load_settings = Self::get_from_database(cx); - - let mut tasks = smallvec![]; + let nostr = NostrRegistry::global(cx); let mut subscriptions = smallvec![]; subscriptions.push( @@ -148,24 +143,15 @@ impl AppSettings { }), ); - tasks.push( - // Load the initial settings - cx.spawn(async move |this, cx| { - let settings = load_settings.await.unwrap_or(Settings::default()); - log::info!("Settings: {settings:?}"); - - // Update the settings state - this.update(cx, |this, cx| { - this.set_settings(settings, cx); - })?; - - Ok(()) + subscriptions.push( + // Observe and automatically save settings on changes + cx.observe(&nostr, |this, _state, cx| { + this.load(cx); }), ); Self { values: Settings::default(), - tasks, _subscriptions: subscriptions, } } @@ -176,50 +162,38 @@ impl AppSettings { cx.notify(); } - /// Get settings from the database - fn get_from_database(cx: &App) -> Task> { + /// Load settings + fn load(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - cx.background_spawn(async move { - // Construct a filter to get the latest settings - let mut filter = Filter::new() + let task: Task> = cx.background_spawn(async move { + let signer = client.signer().context("Signer not found")?; + let public_key = signer.get_public_key().await?; + + let filter = Filter::new() .kind(Kind::ApplicationSpecificData) .identifier(SETTINGS_IDENTIFIER) + .author(public_key) .limit(1); - // If the signer is available, get settings belonging to the current user - if let Some(signer) = client.signer() { - if let Ok(public_key) = signer.get_public_key().await { - // Push author to the filter - filter = filter.author(public_key); - } - } - - if let Some(event) = client.database().query(filter).await?.first_owned() { + if let Some(event) = client.database().query(filter).await?.last_owned() { Ok(serde_json::from_str(&event.content)?) } else { Err(anyhow!("Not found")) } + }); + + cx.spawn(async move |this, cx| { + let settings = task.await.unwrap_or(Settings::default()); + + // Update settings + this.update(cx, |this, cx| { + this.set_settings(settings, cx); + }) + .ok(); }) - } - - /// Load settings - pub fn load(&mut self, cx: &mut Context) { - let task = Self::get_from_database(cx); - - self.tasks.push( - // Run task in the background - cx.spawn(async move |this, cx| { - let settings = task.await?; - // Update settings - this.update(cx, |this, cx| { - this.set_settings(settings, cx); - })?; - - Ok(()) - }), - ); + .detach(); } /// Save settings @@ -228,7 +202,7 @@ impl AppSettings { let client = nostr.read(cx).client(); let settings = self.values.clone(); - self.tasks.push(cx.background_spawn(async move { + let task: Task> = cx.background_spawn(async move { let signer = client.signer().context("Signer not found")?; let public_key = signer.get_public_key().await?; let content = serde_json::to_string(&settings)?; @@ -243,7 +217,9 @@ impl AppSettings { client.database().save_event(&event).await?; Ok(()) - })); + }); + + task.detach(); } /// Check if the given relay is already authenticated