fix(#26): tighten token handling and verify API contract

- resolveNostrPubkey() now returns { pubkey, rejected } instead of string|null
  so invalid/expired tokens return 401 instead of silently falling to anonymous
- POST /sessions and POST /jobs: return 401 if nostr_token header/body is
  present but invalid or expired
- POST /identity/verify: now accepts optional top-level 'pubkey' field alongside
  'event'; asserts pubkey matches event.pubkey if both are provided — aligns
  API contract with { pubkey, event } spec shape and hardens against mismatch
This commit is contained in:
Replit Agent
2026-03-19 16:15:55 +00:00
parent 96d5915ada
commit 99ede5792e
3 changed files with 48 additions and 9 deletions

View File

@@ -52,13 +52,22 @@ router.post("/identity/challenge", (_req: Request, res: Response) => {
// event.kind — any (27235 recommended per NIP-98, but not enforced)
router.post("/identity/verify", async (req: Request, res: Response) => {
const { event } = req.body as { event?: unknown };
// Accept both { event } and { pubkey, event } shapes (pubkey is optional but asserted if present)
const { event, pubkey: explicitPubkey } = req.body as { event?: unknown; pubkey?: unknown };
if (!event || typeof event !== "object") {
res.status(400).json({ error: "Body must include 'event' (Nostr signed event)" });
return;
}
// If caller provided a top-level pubkey, validate it matches event.pubkey
if (explicitPubkey !== undefined) {
if (typeof explicitPubkey !== "string" || !/^[0-9a-f]{64}$/.test(explicitPubkey)) {
res.status(400).json({ error: "top-level 'pubkey' must be a 64-char hex string" });
return;
}
}
// ── Validate event structure ──────────────────────────────────────────────
const ev = event as Record<string, unknown>;
const pubkey = ev["pubkey"];
@@ -69,6 +78,12 @@ router.post("/identity/verify", async (req: Request, res: Response) => {
return;
}
// Assert top-level pubkey matches event.pubkey if both are provided
if (typeof explicitPubkey === "string" && explicitPubkey !== pubkey) {
res.status(400).json({ error: "top-level 'pubkey' does not match event.pubkey" });
return;
}
if (typeof content !== "string" || content.trim().length === 0) {
res.status(400).json({ error: "event.content must be the nonce string" });
return;