wip: rework identity

This commit is contained in:
2026-07-24 09:03:06 +07:00
parent 40166fd071
commit f1e4428109
9 changed files with 132 additions and 35 deletions

View File

@@ -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;

View File

@@ -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<Self>) {
/// Check the user's credential and set the signer if valid
fn get_user_credential(&mut self, cx: &mut Context<Self>) {
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<Self>) -> Task<Keys> {
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<Result<PublicKey, Error>> {
let client = self.client();

View File

@@ -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<Result<(), nostr_connect::error::Error>> {
Box::pin(async move {
webbrowser::open(auth_url.as_str()).unwrap();
Ok(())
})
}
}