import type { Request, Response, NextFunction } from "express"; import * as jose from "jose"; import { logger } from "../lib/logger"; export type AuthInfo = { userId: string | null; email: string | null; isAdmin: boolean; }; function getSecret(): Uint8Array { const secret = process.env.SESSION_SECRET; if (!secret) throw new Error("SESSION_SECRET is not set"); return new TextEncoder().encode(secret); } export async function resolveAuth(req: Request): Promise { const token = req.cookies?.session; if (!token) return { userId: null, email: null, isAdmin: false }; try { const { payload } = await jose.jwtVerify(token, getSecret(), { algorithms: ["HS256"], }); const email = typeof payload.email === "string" ? payload.email : null; const userId = typeof payload.sub === "string" ? payload.sub : null; if (!email || !userId) return { userId: null, email: null, isAdmin: false }; return { userId, email, isAdmin: true }; } catch (err) { logger.debug({ err }, "JWT verification failed"); return { userId: null, email: null, isAdmin: false }; } } export async function signToken(userId: number, email: string): Promise { return new jose.SignJWT({ email }) .setProtectedHeader({ alg: "HS256" }) .setSubject(String(userId)) .setIssuedAt() .setExpirationTime("30d") .sign(getSecret()); } declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Express { interface Request { auth?: AuthInfo; } } } export async function requireAdmin( req: Request, res: Response, next: NextFunction, ): Promise { const info = await resolveAuth(req); if (!info.userId) { res.status(401).json({ error: "Nicht angemeldet." }); return; } if (!info.isAdmin) { res.status(403).json({ error: "Kein Administratorzugriff." }); return; } req.auth = info; next(); }