import Fastify from "fastify"; import cookie from "@fastify/cookie"; import rateLimit from "@fastify/rate-limit"; import authRoutes from "../routes/admin/auth"; import totpRoutes from "../routes/admin/totp"; export async function buildApp() { const app = Fastify({ logger: false }); await app.register(cookie); await app.register(rateLimit, { max: 1000, timeWindow: "1 minute" }); await app.register(authRoutes, { prefix: "/api/admin" }); await app.register(totpRoutes, { prefix: "/api/admin/totp" }); return app; } /** * Minimal structural shape of the only thing extractCookie needs from a * response — its `set-cookie` header. Matches Fastify's `LightMyRequestResponse` * (from `app.inject`) and a plain supertest response without coupling to either. */ interface ResponseWithCookies { headers: Record; } export function extractCookie( response: ResponseWithCookies, name: string, ): string | undefined { const cookies = response.headers["set-cookie"]; if (!cookies) return undefined; const arr = Array.isArray(cookies) ? cookies : [cookies]; for (const c of arr) { if (typeof c !== "string") continue; if (c.startsWith(`${name}=`)) { // The value is everything from the first "=" up to the first ";". // Split ONLY on the first "=" so a base64/JWT value containing "=" (e.g. // padding) is not truncated — `split("=")[1]` would drop everything after // the second "=". const pair = c.split(";")[0]; return pair.slice(pair.indexOf("=") + 1); } } return undefined; }