diff --git a/core/archipelago/src/content_indeehub.rs b/core/archipelago/src/content_indeehub.rs index 043c60b3..8ff96ce5 100644 --- a/core/archipelago/src/content_indeehub.rs +++ b/core/archipelago/src/content_indeehub.rs @@ -157,6 +157,16 @@ async fn nostr_session(client: &reqwest::Client, data_dir: &Path) -> Result Result { let identity_dir = data_dir.join("identity"); + // Prove the identity this node already has; never mint one here. + // `get_nostr_pubkey` goes through `load_or_create_nostr_keys`, so on a node + // without an identity it would GENERATE a keypair, sign with it, and write + // the secret to disk — an HTTP auth header quietly creating a new node + // identity, and authenticating to IndeeHub as a stranger with a key nobody + // has ever seen. The `.context("node has no Nostr identity")` below could + // never fire because of it. + if !nostr_discovery::nostr_identity_exists(&identity_dir).await { + anyhow::bail!("node has no Nostr identity"); + } let pubkey = nostr_discovery::get_nostr_pubkey(&identity_dir) .await .context("node has no Nostr identity")?; diff --git a/core/archipelago/src/nostr_discovery.rs b/core/archipelago/src/nostr_discovery.rs index 13a84753..f37b89ff 100644 --- a/core/archipelago/src/nostr_discovery.rs +++ b/core/archipelago/src/nostr_discovery.rs @@ -26,6 +26,20 @@ const D_TAG: &str = "archipelago-node"; /// Relays we previously published to (for one-time revocation overwrite only) const LEGACY_RELAYS: &[&str] = &["wss://relay.damus.io", "wss://relay.nostr.info"]; +/// Does this node already have a Nostr identity on disk? +/// +/// `load_or_create_nostr_keys` mints one when it finds none. That is right at +/// bootstrap and wrong for anything that AUTHENTICATES as this node: signing +/// with a key generated on the spot proves nothing, presents the node as a +/// stranger to whatever it is authenticating to, and writes a new secret to +/// disk as a side effect of what the caller thought was a read. Callers that +/// must prove an EXISTING identity gate on this first and fail loudly. +pub(crate) async fn nostr_identity_exists(identity_dir: &Path) -> bool { + fs::try_exists(identity_dir.join(NOSTR_SECRET_FILE)) + .await + .unwrap_or(false) +} + /// Load or create Nostr keys (secp256k1) for node discovery. pub(crate) async fn load_or_create_nostr_keys(identity_dir: &Path) -> Result { let secret_path = identity_dir.join(NOSTR_SECRET_FILE);