/** * JWT authentication middleware for the API. * Mirrors `apps/interfaces/api/src/middleware/auth.rs`. * * Validates the `Authorization: Bearer ` header and injects the * validated subject into `req.user`. Refresh tokens are rejected on * protected routes — only access tokens are accepted. */ import { verifyToken } from "@zesdex/infrastructure"; import type { ApiState } from "../state.ts"; /** Claims decoded from a valid JWT, attached to the request. */ export interface JwtClaims { sub: string; } /** Result of authentication: either the subject or an error status/detail. */ export type AuthResult = { ok: true; sub: string } | { ok: false; status: number; detail: string }; /** * Authenticate a request against the API state's JWT secret. * Reads the `Authorization: Bearer ` header, verifies the signature * and token type (must be `access`), and returns the subject on success. */ export function authenticateRequest(state: ApiState, headers: Headers): AuthResult { const authHeader = headers.get("Authorization"); if (!authHeader) { return { ok: false, status: 401, detail: "Missing or invalid Authorization header" }; } const prefix = "Bearer "; if (!authHeader.startsWith(prefix)) { return { ok: false, status: 401, detail: "Missing or invalid Authorization header" }; } const token = authHeader.slice(prefix.length).trim(); if (!token) { return { ok: false, status: 401, detail: "Missing or invalid Authorization header" }; } try { const claims = verifyToken(state.jwt_secret, token); if (claims.typ !== "access") { return { ok: false, status: 401, detail: "refresh tokens are not accepted on protected routes", }; } return { ok: true, sub: claims.sub }; } catch (e) { return { ok: false, status: 401, detail: `Invalid token: ${(e as Error).message}` }; } }