From f1e442810941f281a70c6bcec81796f65b746779 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Fri, 24 Jul 2026 09:03:06 +0700 Subject: [PATCH] wip: rework identity --- Cargo.lock | 46 ++++++++++----- Cargo.toml | 3 + crates/state/Cargo.toml | 1 + crates/state/src/constants.rs | 3 +- crates/state/src/lib.rs | 85 +++++++++++++++++++++++---- crates/state/src/signer.rs | 14 +++++ crates/theme/src/lib.rs | 1 - crates/ui/src/button.rs | 10 ++-- crates/workspace/src/panels/backup.rs | 4 +- 9 files changed, 132 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c9ca19..baaaf2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1921,7 +1921,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2121,7 +2121,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4356,8 +4356,9 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "nostr" version = "0.45.0-alpha.4" -source = "git+https://github.com/rust-nostr/nostr#f564daaf4edd6279cd28f16ef8a017e912ef2ce9" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" dependencies = [ + "aes", "base64", "bech32", "bip39", @@ -4380,7 +4381,7 @@ dependencies = [ [[package]] name = "nostr-blossom" version = "0.45.0-alpha.4" -source = "git+https://github.com/rust-nostr/nostr#f564daaf4edd6279cd28f16ef8a017e912ef2ce9" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" dependencies = [ "base64", "nostr", @@ -4389,10 +4390,24 @@ dependencies = [ "serde", ] +[[package]] +name = "nostr-connect" +version = "0.45.0-alpha.4" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" +dependencies = [ + "async-utility", + "futures-core", + "nostr", + "nostr-sdk", + "opaquerr", + "tokio", + "tracing", +] + [[package]] name = "nostr-database" version = "0.45.0-alpha.4" -source = "git+https://github.com/rust-nostr/nostr#f564daaf4edd6279cd28f16ef8a017e912ef2ce9" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" dependencies = [ "btreecap", "flatbuffers", @@ -4403,7 +4418,7 @@ dependencies = [ [[package]] name = "nostr-gossip" version = "0.45.0-alpha.4" -source = "git+https://github.com/rust-nostr/nostr#f564daaf4edd6279cd28f16ef8a017e912ef2ce9" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" dependencies = [ "nostr", "opaquerr", @@ -4412,7 +4427,7 @@ dependencies = [ [[package]] name = "nostr-gossip-memory" version = "0.45.0-alpha.4" -source = "git+https://github.com/rust-nostr/nostr#f564daaf4edd6279cd28f16ef8a017e912ef2ce9" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" dependencies = [ "indexmap", "lru", @@ -4424,7 +4439,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.45.0-alpha.4" -source = "git+https://github.com/rust-nostr/nostr#f564daaf4edd6279cd28f16ef8a017e912ef2ce9" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" dependencies = [ "async-utility", "flume 0.12.0", @@ -4438,7 +4453,7 @@ dependencies = [ [[package]] name = "nostr-memory" version = "0.45.0-alpha.4" -source = "git+https://github.com/rust-nostr/nostr#f564daaf4edd6279cd28f16ef8a017e912ef2ce9" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" dependencies = [ "btreecap", "nostr", @@ -4449,7 +4464,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.45.0-alpha.4" -source = "git+https://github.com/rust-nostr/nostr#f564daaf4edd6279cd28f16ef8a017e912ef2ce9" +source = "git+https://github.com/rust-nostr/nostr#a48ab1cff0e1896537ac7bcdf16d828152f80841" dependencies = [ "async-utility", "async-wsocket", @@ -4496,7 +4511,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5593,7 +5608,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6149,7 +6164,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6855,6 +6870,7 @@ dependencies = [ "mime_guess", "nostr", "nostr-blossom", + "nostr-connect", "nostr-gossip-memory", "nostr-lmdb", "nostr-memory", @@ -7174,7 +7190,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8535,7 +8551,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c9738f4..1294da3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" } nostr-memory = { git = "https://github.com/rust-nostr/nostr" } nostr-blossom = { git = "https://github.com/rust-nostr/nostr" } nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" } +nostr-connect = { git = "https://github.com/rust-nostr/nostr" } +nostr-browser-signer = { git = "https://github.com/rust-nostr/nostr" } +nostr-browser-signer-proxy = { git = "https://github.com/rust-nostr/nostr" } nostr-sdk = { git = "https://github.com/rust-nostr/nostr" } nostr = { git = "https://github.com/rust-nostr/nostr", features = [ "nip59", "nip49", "nip44" ] } diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index 5001eeb..2eb8907 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -11,6 +11,7 @@ nostr.workspace = true nostr-sdk.workspace = true nostr-gossip-memory.workspace = true nostr-blossom.workspace = true +nostr-connect.workspace = true gpui.workspace = true flume.workspace = true diff --git a/crates/state/src/constants.rs b/crates/state/src/constants.rs index 827ba0a..c0473cf 100644 --- a/crates/state/src/constants.rs +++ b/crates/state/src/constants.rs @@ -8,7 +8,8 @@ pub const COOP_PUBKEY: &str = "npub1j3rz3ndl902lya6ywxvy5c983lxs8mpukqnx4pa4lt5w pub const APP_ID: &str = "su.reya.coop"; /// Keyring name -pub const KEYRING: &str = "Coop Safe Storage"; +pub const MASTER_KEYRING: &str = "Coop Master Key"; +pub const USER_KEYRING: &str = "Coop User Credential"; /// Default timeout for subscription pub const TIMEOUT: u64 = 2; diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index 08a1834..beaf610 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -4,6 +4,7 @@ use std::time::Duration; use anyhow::{Error, anyhow}; use common::config_dir; use gpui::{App, AppContext, Context, Entity, EventEmitter, Global, Task, Window}; +use nostr_connect::prelude::*; use nostr_gossip_memory::prelude::*; #[cfg(not(target_arch = "wasm32"))] use nostr_lmdb::prelude::*; @@ -21,7 +22,7 @@ pub use blossom::*; pub use constants::*; pub use nip4e::*; pub use nip05::*; -pub use signer::UniversalSigner; +pub use signer::{CoopAuthUrlHandler, UniversalSigner}; pub fn init(window: &mut Window, cx: &mut App) { // rustls uses the `aws_lc_rs` provider by default @@ -137,7 +138,7 @@ impl NostrRegistry { // Connect to bootstrap relays after the window is ready cx.defer_in(window, |this, _window, cx| { this.connect_bootstrap_relays(cx); - this.check_credential(cx); + this.get_user_credential(cx); }); Self { @@ -179,16 +180,16 @@ impl NostrRegistry { this.current_user = Some(public_key); cx.emit(StateEvent::SignerChanged); cx.notify(); - }) - .ok(); + })?; } Err(e) => { this.update(cx, |_this, cx| { cx.emit(StateEvent::error(e.to_string())); - }) - .ok(); + })?; } }; + + Ok::<(), anyhow::Error>(()) }) .detach(); } @@ -235,17 +236,79 @@ impl NostrRegistry { })); } - fn check_credential(&mut self, cx: &mut Context) { + /// Check the user's credential and set the signer if valid + fn get_user_credential(&mut self, cx: &mut Context) { + let user_keyring = cx.read_credentials(USER_KEYRING); + let master_keyring = self.get_master_key(cx); + self.tasks.push(cx.spawn(async move |this, cx| { - this.update(cx, |this, cx| { - // TODO: check credential - cx.notify(); - })?; + match user_keyring.await { + Ok(Some((_username, secret))) => { + let content = String::from_utf8(secret)?; + + if content.starts_with("nsec1") { + let secret_key = SecretKey::parse(&content)?; + let keys = Keys::new(secret_key); + + this.update(cx, |this, cx| { + this.set_signer(keys, cx); + cx.notify(); + })?; + } else if content.starts_with("bunker://") { + let keys = master_keyring.await; + let timeout = Duration::from_secs(30); + let uri = NostrConnectUri::parse(content)?; + + // Construct the nostr connect signer + let mut signer = NostrConnect::new(uri, keys, timeout, None)?; + + // Handle auth url with the default browser + signer.auth_url_handler(CoopAuthUrlHandler); + + this.update(cx, |this, cx| { + this.set_signer(signer, cx); + cx.notify(); + })?; + } + } + _ => { + this.update(cx, |_, cx| { + cx.notify(); + })?; + } + } Ok(()) })); } + fn get_master_key(&mut self, cx: &mut Context) -> Task { + let task = cx.read_credentials(MASTER_KEYRING); + + cx.spawn(async move |_, cx| { + let (keys, new_key) = match task.await { + Ok(Some((_user, secret))) => match SecretKey::from_slice(&secret) { + Ok(secret_key) => (Keys::new(secret_key), false), + _ => (Keys::generate(), true), + }, + _ => (Keys::generate(), true), + }; + + if new_key { + let keys_clone = keys.clone(); + let username = keys_clone.public_key().to_hex(); + let password = keys_clone.secret_key().to_secret_bytes(); + + cx.update(|cx| { + let task = cx.write_credentials(MASTER_KEYRING, &username, &password); + cx.background_spawn(async move { task.await.ok() }).detach(); + }); + } + + keys + }) + } + /// Get the public key of a NIP-05 address pub fn query_address(&self, addr: Nip05Address, cx: &App) -> Task> { let client = self.client(); diff --git a/crates/state/src/signer.rs b/crates/state/src/signer.rs index 4a48348..c3e78fd 100644 --- a/crates/state/src/signer.rs +++ b/crates/state/src/signer.rs @@ -2,6 +2,7 @@ use std::error::Error; use std::fmt; use std::sync::{Arc, RwLock}; +use nostr_connect::client::AuthUrlHandler; use nostr_sdk::prelude::*; #[derive(Debug)] @@ -180,3 +181,16 @@ impl AsyncNip44 for UniversalSigner { Box::pin(async move { inner.nip44_decrypt_async(public_key, payload).await }) } } + +#[derive(Debug, Clone)] +pub struct CoopAuthUrlHandler; + +impl AuthUrlHandler for CoopAuthUrlHandler { + #[allow(mismatched_lifetime_syntaxes)] + fn on_auth_url(&self, auth_url: Url) -> BoxedFuture> { + Box::pin(async move { + webbrowser::open(auth_url.as_str()).unwrap(); + Ok(()) + }) + } +} diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index 3a65403..e11adcd 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -192,7 +192,6 @@ impl From for Theme { let mode = ThemeMode::default(); // Define the font family based on the platform. - // TODO: Use native fonts on Linux too. let font_family = match platform { PlatformKind::Linux => "Inter", _ => ".SystemUIFont", diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs index 5441e20..2647ccc 100644 --- a/crates/ui/src/button.rs +++ b/crates/ui/src/button.rs @@ -2,15 +2,15 @@ use std::rc::Rc; use gpui::prelude::FluentBuilder as _; use gpui::{ - div, relative, AnyElement, App, ClickEvent, Div, ElementId, Hsla, InteractiveElement, - IntoElement, ParentElement, RenderOnce, SharedString, Stateful, - StatefulInteractiveElement as _, StyleRefinement, Styled, Window, + AnyElement, App, ClickEvent, Div, ElementId, Hsla, InteractiveElement, IntoElement, + ParentElement, RenderOnce, SharedString, Stateful, StatefulInteractiveElement as _, + StyleRefinement, Styled, Window, div, relative, }; use theme::ActiveTheme; use crate::indicator::Indicator; use crate::tooltip::Tooltip; -use crate::{h_flex, Disableable, Icon, IconName, Selectable, Sizable, Size, StyledExt}; +use crate::{Disableable, Icon, IconName, Selectable, Sizable, Size, StyledExt, h_flex}; #[derive(Clone, Copy, PartialEq, Eq)] pub struct ButtonCustomVariant { @@ -617,7 +617,7 @@ impl ButtonVariant { }; let fg = match self { - ButtonVariant::Primary => cx.theme().text_muted, // TODO: use a different color? + ButtonVariant::Primary => cx.theme().text_muted, _ => cx.theme().text_muted, }; diff --git a/crates/workspace/src/panels/backup.rs b/crates/workspace/src/panels/backup.rs index 3ba18d3..498ba46 100644 --- a/crates/workspace/src/panels/backup.rs +++ b/crates/workspace/src/panels/backup.rs @@ -6,7 +6,7 @@ use gpui::{ Focusable, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window, div, }; use nostr_sdk::prelude::*; -use state::KEYRING; +use state::USER_KEYRING; use theme::ActiveTheme; use ui::button::{Button, ButtonVariants}; use ui::dock::{Panel, PanelEvent}; @@ -59,7 +59,7 @@ impl BackupPanel { } fn load(&mut self, window: &mut Window, cx: &mut Context) { - let keyring = cx.read_credentials(KEYRING); + let keyring = cx.read_credentials(USER_KEYRING); self.tasks.push(cx.spawn_in(window, async move |this, cx| { if let Some((_, secret)) = keyring.await? {