Repository Public
1import { z } from "zod";2import { ApiError, body, clientIp } from "../http";3import { rateLimit } from "../ratelimit";4import {5 assertSameOrigin, authenticationOptions, clearSessionCookie, currentAdmin, loginWithRecoveryCode, registrationOptions, revokeSession,6 verifyAuthentication, verifyRegistration,7} from "../auth";8import { audit } from "../audit";9import type { RouteDef } from "./router";1011const anyJson = z.record(z.string(), z.unknown());1213export const authRoutes: RouteDef<{ ip: string }>[] = [14 ["GET", "session", async () => {15 const a = await currentAdmin();16 return a ? { authenticated: true, csrfToken: a.csrfToken, user: { email: a.email, displayName: a.displayName } } : { authenticated: false };17 }],18 ["POST", "register/options", async ({ req, ctx }) => {19 assertSameOrigin(req); await rateLimit(`auth:${ctx.ip}`, 20, 600);20 const b = await body(req, z.object({ setupToken: z.string().max(200).optional() }));21 const admin = await currentAdmin();22 if (admin) { const sent = req.headers.get("x-csrf-token") ?? ""; if (sent !== admin.csrfToken) throw new ApiError(403, "csrf", "Invalid CSRF token"); }23 return registrationOptions({ setupToken: b.setupToken, admin: b.setupToken ? null : admin });24 }],25 ["POST", "register/verify", async ({ req, ctx }) => {26 assertSameOrigin(req); await rateLimit(`auth:${ctx.ip}`, 20, 600);27 const b = await body(req, z.object({ challengeId: z.string().uuid(), response: anyJson, name: z.string().max(60).optional(), setupToken: z.string().max(200).optional() }));28 const admin = b.setupToken ? null : await currentAdmin();29 if (admin) { const sent = req.headers.get("x-csrf-token") ?? ""; if (sent !== admin.csrfToken) throw new ApiError(403, "csrf", "Invalid CSRF token"); }30 return verifyRegistration({ challengeId: b.challengeId, response: b.response as never, name: b.name, setupToken: b.setupToken, admin }, req);31 }],32 ["POST", "login/options", async ({ req, ctx }) => { assertSameOrigin(req); await rateLimit(`auth:${ctx.ip}`, 30, 600); return authenticationOptions(); }],33 ["POST", "login/verify", async ({ req, ctx }) => {34 assertSameOrigin(req); await rateLimit(`auth:${ctx.ip}`, 30, 600);35 const b = await body(req, z.object({ challengeId: z.string().uuid(), response: anyJson }));36 return verifyAuthentication({ challengeId: b.challengeId, response: b.response as never }, req);37 }],38 ["POST", "recovery", async ({ req, ctx }) => {39 assertSameOrigin(req); await rateLimit(`recovery:${ctx.ip}`, 5, 900);40 const b = await body(req, z.object({ code: z.string().min(8).max(40) }));41 return loginWithRecoveryCode(b.code, req);42 }],43 ["POST", "logout", async ({ req }) => {44 assertSameOrigin(req);45 const a = await currentAdmin();46 if (a) { await revokeSession(a.sessionId, a.userId); await audit({ userId: a.userId, actor: "admin", action: "logout", ip: clientIp(req) }); }47 await clearSessionCookie();48 return { ok: true };49 }],50];51