Skip to content

auth: serve /.well-known/did/nostr/<pubkey>.json so the pod is its own DID resolver #407

@melvincarvalho

Description

@melvincarvalho

Today the "Sign in with Schnorr" button (#403/#405) requires the user to type their username because the IdP login URL has no resource owner to derive the WebID from. As discussed: the cleanest architectural answer is that the local index and the global resolver are the same mechanism, sharded by host. Each JSS pod can serve its own users' DID documents at the did:nostr HTTP-resolution path the spec already defines:

https://<pod-host>/.well-known/did/nostr/<pubkey>.json

(per did:nostr spec § HTTP Resolution — same path nostr.social and nostr.rocks already implement.)

Once a pod publishes its users' DID docs at that path, JSS's existing src/auth/did-nostr.js resolver finds them via the same code path it uses for nostr.social — just one HTTP hop earlier when the pod is the authority for that user. The "type your username" UX hack goes away.

Plan

  1. Server side: add GET /.well-known/did/nostr/:pubkey.json route. For any local account whose profile has a Nostr Multikey VM matching that pubkey (referenced from authentication, with valid controller), generate the DID doc on the fly:
    • @context: ["https://w3id.org/did", "https://w3id.org/nostr/context"]
    • id: did:nostr:<pubkey>
    • type: "DIDNostr"
    • alsoKnownAs: [<webId>] — JSS knows it from the account record
    • verificationMethod, authentication, assertionMethod per the spec example
    • Headers per spec: Content-Type: application/did+json, Cache-Control: max-age=3600, Nostr-Timestamp, Last-Modified
    • 404 when no local account claims that pubkey.
  2. Pubkey → account index: small in-memory map, rebuilt on TTL miss (5 min) by scanning the existing _webid_index.json and reading each account's profile/card.jsonld for f-form Multikey or JsonWebKey (kty:EC, crv:secp256k1) entries. Real production wants a PATCH-time hook on the LDP write path; out of scope for this MVP.
  3. Resolver side: tweak resolveDidNostrToWebId to try <request-host>/.well-known/did/nostr/<pubkey>.json BEFORE the configured fallback (nostr.social). For same-pod auth this becomes a zero-network self-resolve.
  4. handleSchnorrLogin: with the resolver picking up local users automatically, the typed-username fallback (idp: "Sign in with Schnorr" resolves user via typed username + CID verificationMethod #403) becomes a third-tier safety net rather than the primary mechanism.

Acceptance

  • GET /.well-known/did/nostr/<pubkey>.json returns a CID-shaped DID doc for any account with a matching Nostr Multikey VM in its profile
  • Returns 404 (with proper headers) for unknown pubkeys
  • Existing Sign-in-with-Schnorr flow works without typing a username for local accounts
  • Cross-pod Schnorr auth still works via the configured fallback resolvers (nostr.social, etc.)
  • Pubkey index rebuilds correctly when a profile is updated (caveat: only on TTL expiry in this MVP — a write-path hook is filed as a follow-up)

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestnostrNostr relay, did:nostr auth, NIP-related

    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