Skip to content

Support HTTP /api/nostr signing for team authorizations (stored_keys + authorizations) #5

@alejandro-runner

Description

@alejandro-runner

Summary

Synvya/server is being refactored so restaurant identities use Keycast team keys via bunker URLs today. That is the correct secure boundary because restaurant private keys remain in Keycast, but it means the server must communicate with Keycast over NIP-46 relay events for team-owned restaurant keys.

Keycast already supports two authorization models today:

  • Personal/OAuth authorizations: oauth_authorizations + personal_keys, usable via POST /api/nostr with UCAN bearer tokens.
  • Team authorizations: authorizations + stored_keys, usable via NIP-46 bunker URLs handled by the signer daemon.

The missing piece is an HTTP RPC path for team authorizations, analogous to the existing OAuth/personal-key HTTP RPC path.

Problem

POST /api/nostr currently only resolves bearer tokens into signing sessions for:

  • OAuth tokens with bunker_pubkey that load from oauth_authorizations
  • preloaded-user server-signed UCANs

It does not support team authorizations backed by stored_keys, even though the signer daemon already supports those keys for bunker/NIP-46 usage.

Goal

Allow first-party services such as Synvya/server to securely request signing/decryption on behalf of a team-owned restaurant key via HTTP, without the private key ever leaving Keycast.

Requirements

  1. Keep restaurant/team private keys inside Keycast only.
  2. Preserve per-authorization policy enforcement.
  3. Do not introduce a shared super-token that can sign as arbitrary team keys without an explicit authorization boundary.
  4. Keep support for the existing NIP-46 bunker path; this issue is about adding an HTTP path, not replacing bunker support.

Suggested design

  1. Add a first-party endpoint to mint a short-lived UCAN for a specific team authorization.
    Example shape:

    • POST /api/server/authorizations/:id/token
      or similar first-party/admin-scoped endpoint
  2. The endpoint should:

    • authenticate the caller as a trusted first-party service
    • verify the caller is allowed to use the target team authorization
    • load the authorizations row
    • mint a short-lived server-signed UCAN whose audience is the restaurant/team signing pubkey
    • include facts sufficient for /api/nostr to resolve the authorization, likely including:
      • tenant_id
      • redirect_origin (for traceability / app identity)
      • bunker_pubkey
      • optionally authorization_id
  3. Extend POST /api/nostr handler loading so a bearer token with bunker_pubkey can resolve against either:

    • oauth_authorizations + personal_keys
    • authorizations + stored_keys
  4. Reuse the same permission enforcement model already used by the signer daemon for team authorizations.

Implementation notes

Relevant files today:

  • api/src/api/http/nostr_rpc.rs only loads HTTP handlers from oauth_authorizations or preloaded users.
  • signer/src/signer_daemon.rs already supports loading both OAuth and regular team authorizations.
  • api/src/api/http/teams.rs already creates team authorizations and returns bunker URLs.

A reasonable implementation path would be:

  • factor the common team-authorization loading/signing logic so HTTP RPC and the signer daemon can share it
  • add the first-party token issuance endpoint
  • extend HTTP RPC get_handler() to resolve team authorizations

Acceptance criteria

  • A team-owned stored key can be authorized for a first-party service without exposing the private key.
  • The first-party service can call POST /api/nostr for get_public_key, sign_event, nip44_encrypt, and nip44_decrypt using a short-lived UCAN tied to that team authorization.
  • Policy restrictions from the team authorization are enforced.
  • Existing OAuth HTTP RPC behavior remains unchanged.
  • Existing NIP-46 bunker behavior for team authorizations remains unchanged.

Why this matters

This would let Synvya/server keep the correct security model while using a simpler and lower-latency HTTP path for restaurant identities. Until this exists, Synvya/server will use the existing bunker/NIP-46 path for team restaurant keys.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions