Files
ngit-grasp/tests/nip05_identity.rs
T
DanConwayDev 7bca6e2482 feat(http): serve NIP-05 root identity
Relay operators already have a stable Nostr identity for NIP-11 and NIP-42, but clients cannot resolve the domain itself to that key. Serve the NIP-05 root identifier so _@domain identifies a root-mounted relay without another key source.

Add an exact root-level /.well-known/nostr.json route for GET and HEAD, serialize the relay-owner public key as lowercase hex under names._, retain permissive CORS, and give this route precedence over NIP-11 content negotiation. Derive NIP-11 support from the relay request path: only / advertises NIP-05, while nested relay paths omit it.

Keep the changelog, architecture, README, and relay-owner configuration descriptions synchronized. The path check assumes the HTTP request path is the relay's public mount path; a future explicit base-path setting should remain the source for this decision if proxy routing hides that path. Named identities and recommended relay-hint mappings remain outside this change.

Validated with 760 library tests, the root/non-root NIP-05 server integration test, strict all-target Clippy, cargo fmt, git diff checks, and Nix module parsing.
2026-08-14 16:57:20 +00:00

87 lines
2.7 KiB
Rust

//! NIP-05 root-identifier integration tests.
mod common;
use common::TestRelay;
#[tokio::test]
async fn well_known_root_identity_matches_relay_pubkey() {
let relay = TestRelay::start().await;
let client = reqwest::Client::new();
let response = client
.get(format!(
"http://{}/.well-known/nostr.json?name=_",
relay.domain()
))
.header("Accept", "application/nostr+json, application/json")
.send()
.await
.expect("request NIP-05 root identity");
assert_eq!(response.status(), reqwest::StatusCode::OK);
assert_eq!(
response
.headers()
.get(reqwest::header::ACCESS_CONTROL_ALLOW_ORIGIN)
.and_then(|value| value.to_str().ok()),
Some("*")
);
assert!(response
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.is_some_and(|value| value.starts_with("application/json")));
let nip05: serde_json::Value = response.json().await.expect("parse NIP-05 document");
let root_pubkey = nip05["names"]["_"]
.as_str()
.expect("NIP-05 document should map the root identifier");
assert_eq!(root_pubkey.len(), 64);
assert!(root_pubkey
.chars()
.all(|character| character.is_ascii_digit() || ('a'..='f').contains(&character)));
let nip11: serde_json::Value = client
.get(format!("http://{}", relay.domain()))
.header("Accept", "application/nostr+json")
.send()
.await
.expect("request NIP-11 document")
.json()
.await
.expect("parse NIP-11 document");
assert_eq!(nip11["pubkey"], root_pubkey);
assert!(nip11["supported_nips"]
.as_array()
.expect("supported_nips should be an array")
.contains(&serde_json::json!(5)));
let nested_nip11: serde_json::Value = client
.get(format!("http://{}/relay", relay.domain()))
.header("Accept", "application/nostr+json")
.send()
.await
.expect("request NIP-11 document at a non-root relay path")
.json()
.await
.expect("parse nested-path NIP-11 document");
assert!(!nested_nip11["supported_nips"]
.as_array()
.expect("nested-path supported_nips should be an array")
.contains(&serde_json::json!(5)));
let nested_response = client
.get(format!(
"http://{}/repository/.well-known/nostr.json?name=_",
relay.domain()
))
.send()
.await
.expect("request a non-root well-known path");
assert_eq!(nested_response.status(), reqwest::StatusCode::NOT_FOUND);
relay.stop().await;
}