Skip to content

feat: NIP-05 mapping at /.well-known/nostr.json for provisioned-key pods (#437 follow-up) #445

@melvincarvalho

Description

@melvincarvalho

Phase 2.5 / follow-up of #437. Auto-publish a NIP-05 mapping for any pod that ran with --provision-keys, so the pod owner's Nostr-flavoured identity becomes discoverable as name@domain by other Nostr clients.

Why

Phase 2 wired the provisioned key into the WebID profile and added the did:nostr: controller. NIP-05 is the standard "human-friendly identifier" layer on top of a Nostr pubkey: clients fetch https://<domain>/.well-known/nostr.json?name=<name> and expect { "names": { "<name>": "<pubkey hex>" } } (optionally with relays). JSS now has the pubkey and the domain; serving NIP-05 is one small route handler.

Shape

A single route handler at /.well-known/nostr.json. Returns a { names, relays?, nip46? } document built from whichever pods on this server have an owner key.

Per-pod vs shared aggregated

  • Subdomain mode (--subdomains + --base-domain): pods live at alice.example.com, bob.example.com. Each subdomain naturally serves its own .well-known/nostr.json — "one per pod" falls out for free. The handler reads the requesting Host, finds the matching pod, returns just that pod's mapping.
  • Path mode (default multi-user): pods live at example.com/alice/, example.com/bob/. There's only one origin, so only one .well-known/nostr.json. The handler aggregates: { names: { alice: "<pubkey-a>", bob: "<pubkey-b>", … } } for every pod with a provisioned key.
  • Single-user: trivially one mapping. Default name from --single-user-name (me by default), single entry.

The handler doesn't need separate code paths per mode — read the Host, list local pods, filter by host visibility, emit the mapping.

Design questions to settle

  1. Default name. For a pod at /alice/, default the NIP-05 name to alice (matches the pod name)? Or require an explicit --nip05-name (or nip05Name in pod-creation body) to opt in per-pod?
  2. Auto-include relays when --nostr is on? The pod's own /relay is an obvious entry. Could be [ "wss://<host>/relay" ]. Operators with their own relay infrastructure might want to override.
  3. Auto-include nip46 (remote signer) when applicable, or leave for a follow-up?
  4. Discovery boundary. NIP-05 names are public — surfacing every pod's name on /.well-known/nostr.json reveals the operator's pod inventory. Should multi-user mode default to opt-in per pod (operator-controlled) rather than auto-listing every pod?
  5. Source of truth. Read the pubkey from <pod>/private/privkey.jsonld (privileged) or from <pod>/profile/card.jsonld verificationMethod (public)? The latter is simpler — the public side is already exposed there since feat: --provision-keys Phase 2 — wire the owner key into the WebID profile, close the LWS-CID loop (#437) #443.

My defaults if no answer: default name from pod name, opt-out via config (not opt-in — most operators want the discovery), auto-include wss://<host>/relay when --nostr is on, skip nip46 for now, source from the WebID profile's VM (no need to read the secret file).

Acceptance

  • GET /.well-known/nostr.json?name=<name> returns { names: { <name>: <pubkey-hex> } } for any pod with a provisioned key visible to the requesting host.
  • Subdomain mode: per-host filtering — alice.example.com/.well-known/nostr.json returns only alice's mapping.
  • Path mode: aggregated — example.com/.well-known/nostr.json returns all pods.
  • relays field auto-populated with the local relay URL when --nostr is on.
  • ?name= query: when present, return only that name (per NIP-05); when absent, return everything.
  • Public read (no WAC) — NIP-05 verifiers don't authenticate.
  • Tests: route returns expected JSON for single-user, multi-user path mode, multi-user subdomain mode; query-filter; relay inclusion when --nostr on.

Out of scope

  • NIP-46 remote signer (could land in a follow-up if there's demand).
  • Per-pod overrides for relays / extra metadata. Phase 2.5 ships defaults; later iterations can add knobs.
  • Cross-domain proxying / CORS for NIP-05 verification beyond the standard Access-Control-Allow-Origin: *.

Refs #437.

Phased plan

  • MVP (sub-issue feat: NIP-05 MVP — /.well-known/nostr.json for single-user pods (#445) #446) — single-user only, single name, no extras. Just GET /.well-known/nostr.json{ names: { <single-user-name>: <hex> } } from the profile VM. No relays, no ?name= filter, no aggregation. Multi-user returns 404.
  • Phase 2 — multi-user aggregation: path mode emits one combined index, subdomain mode filters per host. Adds the ?name= query filter per NIP-05 §3.
  • Phase 3relays field auto-populated when --nostr is on; per-pod opt-out / custom name knobs; nip46 if there's demand.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions