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

@@ -138,13 +138,20 @@ async function advanceTopup(session: Session): Promise<Session> {
// ── Resolve Nostr pubkey from token header or body ────────────────────────────
function resolveNostrPubkey(req: Request): string | null {
/** Resolves a Nostr token from the request.
* Returns `{ pubkey, rejected }` where:
* rejected=false, pubkey=null → no token supplied (anonymous)
* rejected=true, pubkey=null → token supplied but invalid/expired → caller should 401
* rejected=false, pubkey=str → valid token
*/
function resolveNostrPubkey(req: Request): { pubkey: string | null; rejected: boolean } {
const header = req.headers["x-nostr-token"];
const bodyToken = req.body?.nostr_token;
const raw = typeof header === "string" ? header : (typeof bodyToken === "string" ? bodyToken : null);
if (!raw) return null;
if (!raw) return { pubkey: null, rejected: false };
const parsed = trustService.verifyToken(raw.trim());
return parsed?.pubkey ?? null;
if (!parsed) return { pubkey: null, rejected: true };
return { pubkey: parsed.pubkey, rejected: false };
}
// ── POST /sessions ─────────────────────────────────────────────────────────────
@@ -161,7 +168,12 @@ router.post("/sessions", sessionsLimiter, async (req: Request, res: Response) =>
}
// Optionally bind a Nostr identity — ensure row exists before FK insert
const nostrPubkey = resolveNostrPubkey(req);
const tokenResult = resolveNostrPubkey(req);
if (tokenResult.rejected) {
res.status(401).json({ error: "Invalid or expired nostr_token" });
return;
}
const nostrPubkey = tokenResult.pubkey;
if (nostrPubkey) await trustService.getOrCreate(nostrPubkey);
try {