feat: audit codebase (#72)

Reviewed-on: #72
This commit was merged in pull request #72.
This commit is contained in:
2026-11-14 01:27:25 +00:00
parent 8fa79aef33
commit 620cbaf89c
60 changed files with 3493 additions and 888 deletions
+1
View File
@@ -51,6 +51,7 @@ sqlite-wasm-vfs = "0.2"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
browser-signer-proxy = { path = "../browser-signer-proxy" }
nostr-lmdb.workspace = true
nostr-memory.workspace = true
smol.workspace = true
gpui_tokio.workspace = true
rustls = "0.23"
+12 -20
View File
@@ -376,12 +376,6 @@ impl DownloadedFile {
LocalFile::Url(_) => Ok(()),
}
}
/// Save the file to the user's downloads (web)
#[cfg(target_arch = "wasm32")]
pub fn save(&self) -> Result<(), Error> {
crate::web_file::download(&self.name, &self.mime, &self.data)
}
}
/// Download and decrypt a file attachment to the user's device
@@ -403,7 +397,7 @@ pub async fn download_and_decrypt_file(
.clone()
.unwrap_or_else(|| fallback_name(&file.url));
let local = store_file(&name, &file.mime, &data).await?;
let local = store_file(&file.mime, &data).await?;
Ok(DownloadedFile {
local,
@@ -415,10 +409,11 @@ pub async fn download_and_decrypt_file(
}
/// Persist decrypted bytes somewhere the platform can render or open them
async fn store_file(name: &str, mime: &str, data: &[u8]) -> Result<LocalFile, Error> {
async fn store_file(mime: &str, data: &[u8]) -> Result<LocalFile, Error> {
#[cfg(not(target_arch = "wasm32"))]
{
let digest = sha256_hex(name.as_bytes());
// Key the cache entry on the content hash, so same-named attachments can't collide
let digest = sha256_hex(data);
let extension = mime_guess::get_mime_extensions_str(mime)
.and_then(|extensions| extensions.first())
.copied()
@@ -428,24 +423,21 @@ async fn store_file(name: &str, mime: &str, data: &[u8]) -> Result<LocalFile, Er
.join("coop-files")
.join(format!("{digest}.{extension}"));
if smol::fs::metadata(&path).await.is_err() {
let Some(parent) = path.parent() else {
bail!("Invalid file path");
};
smol::fs::create_dir_all(parent).await?;
let Some(parent) = path.parent() else {
bail!("Invalid file path");
};
smol::fs::create_dir_all(parent).await?;
// Write under a temporary name first, so an interrupted download is never reused
let partial = path.with_extension("download");
smol::fs::write(&partial, data).await?;
smol::fs::rename(&partial, &path).await?;
}
// Write under a temporary name first, so an interrupted download is never reused
let partial = path.with_extension("download");
smol::fs::write(&partial, data).await?;
smol::fs::rename(&partial, &path).await?;
Ok(LocalFile::Path(path))
}
#[cfg(target_arch = "wasm32")]
{
let _ = name;
let url = crate::web_file::blob_url(mime, data)?;
Ok(LocalFile::Url(url))
}
+35 -17
View File
@@ -16,6 +16,8 @@ use nostr_connect::prelude::*;
use nostr_gossip_memory::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
use nostr_lmdb::prelude::*;
#[cfg(not(target_arch = "wasm32"))]
use nostr_memory::prelude::MemoryDatabase;
use nostr_sdk::prelude::*;
mod blossom;
@@ -89,8 +91,6 @@ pub struct NostrRegistry {
signer: UniversalSigner,
/// Current user's public key
current_user: Option<PublicKey>,
/// Whether the stored credential needs a passphrase to unlock
passphrase_required: bool,
/// Tasks for asynchronous operations
tasks: Vec<Task<Result<(), Error>>>,
}
@@ -114,15 +114,27 @@ impl NostrRegistry {
let signer = UniversalSigner::new(Keys::generate());
let authenticator = SignerAuthenticator::new(signer.clone());
// NostrLmdb::open schedules nothing back onto the GPUI executor, so blocking here can't deadlock
#[cfg(not(target_arch = "wasm32"))]
let database = cx.foreground_executor().block_on(async move {
NostrLmdb::open(config_dir().join("nostr"))
.await
.expect("Failed to initialize database")
});
let (database, database_error) = match cx
.foreground_executor()
.block_on(NostrLmdb::open(config_dir().join("nostr")))
{
Ok(database) => (Arc::new(database) as Arc<dyn NostrDatabase>, None),
Err(e) => {
log::error!("Failed to open the local database, falling back to memory: {e}");
(
Arc::new(MemoryDatabase::unbounded()) as Arc<dyn NostrDatabase>,
Some(format!(
"Local database unavailable, using an in-memory database instead: {e}"
)),
)
}
};
#[cfg(target_arch = "wasm32")]
let database = web_database::take();
let (database, database_error) = (web_database::take(), None::<String>);
// Construct the nostr client
let client = ClientBuilder::default()
@@ -141,6 +153,10 @@ impl NostrRegistry {
// Connect to bootstrap relays once the registry has been returned to the app
cx.defer(move |cx| {
let _ = entity.update(cx, |this, cx| {
if let Some(error) = database_error {
cx.emit(StateEvent::error(error));
}
this.connect_bootstrap_relays(cx);
if let Some(secret) = cli_key {
@@ -157,7 +173,6 @@ impl NostrRegistry {
client,
signer,
current_user: None,
passphrase_required: false,
tasks: vec![],
}
}
@@ -191,7 +206,6 @@ impl NostrRegistry {
this.update(cx, |this, cx| {
this.signer.swap_inner(new_signer);
this.current_user = Some(public_key);
this.passphrase_required = false;
cx.emit(StateEvent::SignerChanged);
cx.notify();
@@ -273,8 +287,7 @@ impl NostrRegistry {
this.set_signer(signer, cx);
})?;
} else if content.starts_with("ncryptsec1") {
this.update(cx, |this, cx| {
this.passphrase_required = true;
this.update(cx, |_this, cx| {
cx.emit(StateEvent::PassphraseRequired);
})?;
} else if content == "proxy" {
@@ -445,8 +458,14 @@ impl NostrRegistry {
};
let keys = Keys::generate();
let credential = with_master_key(&uri_string, &keys);
let write = credentials::write(cx, "bunker", credential.as_bytes());
// The URI embeds the client key, so never persist it where web stores it unencrypted
#[cfg(not(target_arch = "wasm32"))]
let write =
credentials::write(cx, "bunker", with_master_key(&uri_string, &keys).as_bytes());
#[cfg(target_arch = "wasm32")]
let write = Task::ready(Ok::<(), Error>(()));
cx.spawn(async move |this, cx| {
let signer = NostrConnect::builder(connect_uri, keys)
@@ -468,9 +487,8 @@ impl NostrRegistry {
/// Sign out and remove the stored credential
pub fn sign_out(&mut self, cx: &mut Context<Self>) {
self.current_user = None;
self.passphrase_required = false;
credentials::delete(cx).detach_and_log_err(cx);
cx.emit(StateEvent::SignerChanged);
cx.emit(StateEvent::NoSigner);
cx.notify();
@@ -495,7 +513,6 @@ impl NostrRegistry {
credentials::write(cx, "extension", b"extension").detach_and_log_err(cx);
this.signer.swap_inner(signer);
this.current_user = Some(public_key);
this.passphrase_required = false;
cx.emit(StateEvent::SignerChanged);
cx.notify();
@@ -709,6 +726,7 @@ fn extract_master_key(credential: &str) -> (&str, Keys) {
}
// Embed the master key in a bunker URI so reconnects reuse the same local key
#[cfg(not(target_arch = "wasm32"))]
fn with_master_key(uri: &str, keys: &Keys) -> String {
let separator = if uri.contains('?') { '&' } else { '?' };
let nsec = keys.secret_key().to_bech32().expect("infallible");