import { NextFunction, Request, Response } from "express"; import { config } from "./config"; import { csrfMatches, getSession } from "./authService"; declare global { namespace Express { interface Request { auth?: { user: { id: string; email: string; emailVerifiedAt: string | null; }; csrfTokenHash: string; sessionToken: string; }; } } } const MUTATING_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]); export function setSessionCookie(res: Response, sessionToken: string): void { res.cookie(config.session.cookieName, sessionToken, { httpOnly: true, secure: config.session.secure, sameSite: "lax", path: "/", maxAge: config.session.ttlDays * 24 * 60 * 60 * 1000, }); } export function clearSessionCookie(res: Response): void { res.clearCookie(config.session.cookieName, { httpOnly: true, secure: config.session.secure, sameSite: "lax", path: "/", }); } export async function loadAuth(req: Request, _res: Response, next: NextFunction): Promise { const token = req.cookies?.[config.session.cookieName]; if (typeof token !== "string" || token.trim() === "") { next(); return; } try { const session = await getSession(token); if (session) { req.auth = { ...session, sessionToken: token }; } } catch (error) { next(error); return; } next(); } export function requireAuth(req: Request, res: Response, next: NextFunction): void { if (!req.auth) { res.status(401).json({ error: "unauthorized", details: ["Authentication required"] }); return; } if (!req.auth.user.emailVerifiedAt) { res.status(403).json({ error: "email_not_verified", details: ["Email verification required"] }); return; } next(); } export function requireCsrf(req: Request, res: Response, next: NextFunction): void { if (!MUTATING_METHODS.has(req.method) || !req.auth) { next(); return; } const token = req.header("X-CSRF-Token"); if (!token || !csrfMatches(req.auth.csrfTokenHash, token)) { res.status(403).json({ error: "csrf_error", details: ["Invalid CSRF token"] }); return; } next(); }