Persist encrypted peer approval delivery and bind discovery invite identities

This commit is contained in:
archipelago
2026-10-05 22:27:43 -04:00
parent e97f958f45
commit 10d31ae13c
8 changed files with 636 additions and 77 deletions
@@ -134,6 +134,32 @@ pub fn parse_invite(code: &str) -> Result<ParsedInvite> {
})
}
/// Bind a Nostr-discovery reply to the node the operator requested, and cap
/// its grant before any local node entry or callback is written. Legacy invites
/// default to Trusted, which must never transiently authorize discovery peers.
pub(crate) fn restrict_discovery_invite(code: &str, expected_did: &str) -> Result<String> {
use base64::Engine;
let parsed = parse_invite(code)?;
anyhow::ensure!(
!expected_did.is_empty() && parsed.did == expected_did,
"Peer invite does not match the requested node"
);
anyhow::ensure!(
crate::identity::did_key_from_pubkey_hex(&parsed.pubkey)? == parsed.did,
"Peer invite DID does not match its identity key"
);
let bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(
code.strip_prefix("fed1:")
.context("Invalid invite prefix")?,
)?;
let mut payload: serde_json::Value = serde_json::from_slice(&bytes)?;
payload["trust"] = serde_json::json!("observer");
Ok(format!(
"fed1:{}",
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(serde_json::to_vec(&payload)?)
))
}
/// Accept an invite: parse code, verify the remote node, add to federation.
pub async fn accept_invite(
data_dir: &Path,
@@ -621,3 +647,35 @@ mod tests {
assert_eq!(nodes.len(), 1, "re-accept should not duplicate");
}
}
#[cfg(test)]
mod discovery_invite_scope_tests {
use super::*;
use base64::Engine;
#[test]
fn discovery_reply_binds_identity_and_caps_legacy_trust_before_acceptance() {
let key = "33".repeat(32);
let did = crate::identity::did_key_from_pubkey_hex(&key).unwrap();
let payload =
serde_json::json!({"did":did,"pubkey":key,"onion":"test.onion","token":"test-token"});
let code = format!(
"fed1:{}",
base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(serde_json::to_vec(&payload).unwrap())
);
let restricted = restrict_discovery_invite(&code, &did).unwrap();
let parsed = parse_invite(&restricted).unwrap();
assert_eq!(parsed.trust_level, TrustLevel::Observer);
assert_eq!(parsed.token, "test-token");
assert!(restrict_discovery_invite(&code, "did:key:someone-else").is_err());
assert!(restrict_discovery_invite(&code, "").is_err());
let mut forged = payload;
forged["pubkey"] = serde_json::json!("44".repeat(32));
let forged = format!(
"fed1:{}",
base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(serde_json::to_vec(&forged).unwrap())
);
assert!(restrict_discovery_invite(&forged, &did).is_err());
}
}