harden proxy auth and event ordering
This commit is contained in:
@@ -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
-14
@@ -403,7 +403,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 +415,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 +429,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))
|
||||
}
|
||||
|
||||
+33
-8
@@ -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;
|
||||
@@ -114,15 +116,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 +155,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 {
|
||||
@@ -445,8 +463,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)
|
||||
@@ -709,6 +733,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");
|
||||
|
||||
Reference in New Issue
Block a user