import type { Context } from "@oak/oak"; import { verifyJWT } from "./jwt.ts"; import { APIErrorCode, APIException } from "../model/interfaces.ts"; /** * Extracts the userId from an optional Bearer token. * * Distinguishes *missing* credentials from *invalid* ones: * - No `Authorization: Bearer …` header (or a placeholder value such as the * `Bearer undefined` that the client emits when logged out) → anonymous (null). * - A real token that fails verification (expired / bad signature) → throws 401, * so a stale client session surfaces as an auth error it can react to, instead * of being silently downgraded to anonymous (which yields a misleading 404 on * private resources). */ export async function parseOptionalAuth(ctx: Context): Promise { const authHeader = ctx.request.headers.get("Authorization"); if (!authHeader?.startsWith("Bearer ")) return null; const token = authHeader.substring(7).trim(); if (!token || token === "undefined" || token === "null") return null; const payload = await verifyJWT(token); if (!payload) { throw new APIException(APIErrorCode.UNAUTHORIZED, 401, "Invalid token"); } return payload.userId; }