Implement NIP-98 HTTP auth for GET/PUT/DELETE /subscription/email #9

Merged
matt merged 2 commits from mailship-uxf-implement-nip-98-auth-for-get-put-delete-9b9 into main 2026-09-14 17:33:48 +00:00
Collaborator

mailship-uxf

Three browser-facing subscription endpoints now require a NIP-98 kind-27235 HTTP auth event proving the caller controls the pubkey. The server-side verification decodes the Authorization: Nostr <base64> header, parses the event, checks kind, verifies the signature via verifyEvent (nostr-tools/pure), then validates u/method/payload tags against the request URL/method/body.

Changes:

  • GET /subscription/email — no longer reads pubkey from query param. The authenticated pubkey is extracted from the NIP-98 auth header and used to look up the subscription.
  • PUT /subscription/email — no longer trusts a client-supplied pubkey in the body. The pubkey comes from the auth header; body only carries email and frequency.
  • DELETE /subscription/:key — verifies the auth header pubkey matches the subscription owner, returning 403 on mismatch. Returns 401 when auth is missing.
  • README.md — updated API docs from "planned" to "implemented" NIP-98 auth, with corrected request shapes.
  • script/nip98-auth-header.mjs — test helper that generates a Nostr <base64> header using makeHttpAuth + Nip01Signer.sign + makeHttpAuthHeader from @welshman/util.

The existing server-to-server routes (/notify/:id, /confirm, /unsubscribe) remain secret-authenticated as before.

How to test:

Run bash test/integration.sh — 15 tests exercise both success paths (authenticated PUT/GET/DELETE) and rejection paths (missing auth → 401, wrong-owner → 403). The NIP-98 auth headers are generated dynamically from a random test keypair via the script helper.

mailship-uxf Three browser-facing subscription endpoints now require a NIP-98 kind-27235 HTTP auth event proving the caller controls the pubkey. The server-side verification decodes the `Authorization: Nostr <base64>` header, parses the event, checks kind, verifies the signature via `verifyEvent` (nostr-tools/pure), then validates `u`/`method`/`payload` tags against the request URL/method/body. **Changes:** - **GET /subscription/email** — no longer reads `pubkey` from query param. The authenticated pubkey is extracted from the NIP-98 auth header and used to look up the subscription. - **PUT /subscription/email** — no longer trusts a client-supplied `pubkey` in the body. The pubkey comes from the auth header; body only carries `email` and `frequency`. - **DELETE /subscription/:key** — verifies the auth header pubkey matches the subscription owner, returning 403 on mismatch. Returns 401 when auth is missing. - **README.md** — updated API docs from "planned" to "implemented" NIP-98 auth, with corrected request shapes. - **script/nip98-auth-header.mjs** — test helper that generates a `Nostr <base64>` header using `makeHttpAuth` + `Nip01Signer.sign` + `makeHttpAuthHeader` from @welshman/util. The existing server-to-server routes (`/notify/:id`, `/confirm`, `/unsubscribe`) remain secret-authenticated as before. **How to test:** Run `bash test/integration.sh` — 15 tests exercise both success paths (authenticated PUT/GET/DELETE) and rejection paths (missing auth → 401, wrong-owner → 403). The NIP-98 auth headers are generated dynamically from a random test keypair via the script helper.
hudson added 2 commits 2026-09-14 17:09:42 +00:00
Three browser-facing endpoints now require a kind-27235 HTTP auth event
(NIP-98) proving the caller controls the pubkey:

- GET  /subscription/email — pubkey extracted from auth header instead
       of query param; returns subscription for the authed pubkey.
- PUT  /subscription/email — pubkey extracted from auth header instead
       of trusting a client-supplied body field.
- DELETE /subscription/:key — verifies auth pubkey matches subscription
       owner (returns 403 if mismatch).

Server-side: decode base64 'Nostr <b64>' Authorization header, JSON.parse,
check kind === 27235, verifyEvent (nostr-tools/pure), then check u /
method / payload tags against the request URL / method / body.

README updated to reflect 'implemented' auth (not 'planned').
Integration test updated to generate NIP-98 auth headers via a new helper
script (script/nip98-auth-header.mjs).
The test was failing because src/env.ts requires CORS_ORIGIN to be set,
but the integration test never exported it. This is a pre-existing setup
gap exposed by running the test — not a NIP-98 regression.
matt approved these changes 2026-09-14 17:33:41 +00:00
matt merged commit d98d034989 into main 2026-09-14 17:33:48 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: matt/mailship#9
No description provided.