docs(auth): document intentional session-style NIP-98 model #16
+17
-1
@@ -60,7 +60,13 @@ See [spec](spec) for more details
|
||||
|
||||
## API Routes
|
||||
|
||||
All routes are NIP-98 protected.
|
||||
Most API routes are NIP-98 protected.
|
||||
|
||||
Public exceptions:
|
||||
|
||||
- `GET /plans`
|
||||
- `GET /plans/:id`
|
||||
- `POST /stripe/webhook` (validated with Stripe signatures instead)
|
||||
|
||||
- `GET /identity` — get auth identity (`pubkey`, `is_admin`)
|
||||
- `GET /tenants` — list tenants (admin)
|
||||
@@ -73,3 +79,13 @@ All routes are NIP-98 protected.
|
||||
- `PUT /relays/:id` — update relay (admin or relay tenant)
|
||||
- `POST /relays/:id/deactivate` — deactivate relay (admin or relay tenant)
|
||||
- `GET /invoices` — list invoices (`?tenant=<pubkey>` allowed for admin only)
|
||||
|
||||
## API Auth Model
|
||||
|
||||
Caravel intentionally uses a session-style variant of NIP-98 for client-to-backend API auth.
|
||||
|
||||
- Frontend signs one kind `27235` event with `u = VITE_API_URL` and caches that header for about 10 minutes.
|
||||
- Backend verifies event kind, signature, and that `u` contains configured `HOST`.
|
||||
- Backend intentionally does not bind auth to exact request URL/method/query, and does not enforce payload hash, timestamp freshness window, or replay cache.
|
||||
- Goal: reduce repeated wallet signing prompts and avoid cookie-based sessions.
|
||||
- Tradeoff: this is weaker request-intent binding than strict NIP-98 semantics.
|
||||
|
||||
+5
-3
@@ -184,9 +184,11 @@ Notes:
|
||||
## `extract_auth_pubkey(&self, headers: &HeaderMap) -> Result<String>`
|
||||
|
||||
- Parses `Authorization` header
|
||||
- Validates event kind and signature using `nostr_sdk`
|
||||
- Validates event `u` against `HOST` (not the request path. Non-standard, but correct)
|
||||
- Does not validate `method` tag
|
||||
- Validates event kind (`27235`) and signature using `nostr_sdk`
|
||||
- Validates event `u` contains configured `HOST`
|
||||
- Intentionally does **not** enforce exact request URL/method/query matching
|
||||
- Intentionally does **not** validate `payload` tag/hash, `created_at` freshness window, or replay nonce/cache
|
||||
- This is a deliberate session-style tradeoff to reduce repeated signer prompts in the client
|
||||
- Returns pubkey if header all checks pass
|
||||
|
||||
Refer to https://github.com/nostr-protocol/nips/blob/master/98.md for details. Use `nostr_sdk` functionality where possible.
|
||||
|
||||
@@ -209,6 +209,9 @@ impl Api {
|
||||
return Err(ApiError::Unauthorized(anyhow!("missing u tag")));
|
||||
};
|
||||
|
||||
// Intentional session-style variant of NIP-98 for Caravel API auth.
|
||||
// We validate signer identity plus host affinity, and do not bind to exact
|
||||
// request URL/method or maintain replay state here.
|
||||
if !self.host.is_empty() && !got_u.contains(&self.host) {
|
||||
return Err(ApiError::Unauthorized(anyhow!(
|
||||
"authorization host mismatch"
|
||||
|
||||
+5
-2
@@ -51,8 +51,11 @@ npm run preview
|
||||
|
||||
## Authentication
|
||||
|
||||
- Tenant requests use NIP-98 tokens derived from the logged-in user
|
||||
- Admin routes require a pubkey listed in `PLATFORM_ADMIN_PUBKEYS` on the backend
|
||||
- Tenant requests use an intentional session-style variant of NIP-98:
|
||||
- The client signs one kind `27235` event with `u = VITE_API_URL`.
|
||||
- The resulting `Authorization` header is cached for about 10 minutes to avoid repeated signer prompts.
|
||||
- The backend validates signer identity + host affinity rather than exact URL/method binding per request.
|
||||
- Admin routes require a pubkey listed in `ADMINS` on the backend.
|
||||
|
||||
## Routes
|
||||
|
||||
|
||||
@@ -145,6 +145,8 @@ export async function makeAuth(): Promise<string | undefined> {
|
||||
kind: 27235,
|
||||
content: "",
|
||||
created_at: Math.floor(now / 1000),
|
||||
// Intentional session-style auth: sign the API base URL once, then reuse
|
||||
// the header briefly to avoid prompting the signer on every request.
|
||||
tags: [["u", API_URL]],
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user