import type { NextFunction, Request, Response } from 'express'; import { resolveSession, verifyCsrf, type SessionPrincipalRow } from '../db/sessions'; import type { AuthenticatedPrincipal } from '../lib/authProvider'; declare global { namespace Express { interface Request { principal?:AuthenticatedPrincipal; sessionToken?:string; sessionRow?:SessionPrincipalRow } } } function cookie(req:Request,name:string):string|undefined { const raw=req.headers.cookie??''; for(const part of raw.split(';')){const [key,...rest]=part.trim().split('=');if(key===name)return decodeURIComponent(rest.join('='));} return undefined; } export function optionalAuth(req:Request,_res:Response,next:NextFunction):void { const token=cookie(req,'conductor_session'); if(!token){next();return;} const row=resolveSession(token); if(!row||row.revoked_at||row.enabled!==1||Date.parse(row.expires_at)<=Date.now()){next();return;} req.sessionToken=token;req.sessionRow=row;req.principal={userId:row.user_id,username:row.username,displayName:row.display_name,email:row.email,role:row.role,provider:'local'};next(); } export function requireAuth(req:Request,res:Response,next:NextFunction):void { optionalAuth(req,res,()=>{if(!req.principal){res.status(401).json({code:'AUTH_REQUIRED',error:'Authentication is required.'});return;}next();}); } export function requireAdmin(req:Request,res:Response,next:NextFunction):void { requireAuth(req,res,()=>{if(req.principal?.role!=='admin'){res.status(403).json({code:'ADMIN_REQUIRED',error:'Administrator access is required.'});return;}next();}); } export function requireCsrf(req:Request,res:Response,next:NextFunction):void { if(!req.sessionRow||!verifyCsrf(req.sessionRow,req.header('x-csrf-token'))){res.status(403).json({code:'CSRF_INVALID',error:'The security token is missing or invalid.'});return;}next(); } export function requireMutationCsrf(req:Request,res:Response,next:NextFunction):void { if(req.method==='GET'||req.method==='HEAD'||req.method==='OPTIONS'){next();return;}requireCsrf(req,res,next); } export const sessionCookie = (token:string,expiresAt:string) => `conductor_session=${encodeURIComponent(token)}; Path=/; HttpOnly; SameSite=Lax; Expires=${new Date(expiresAt).toUTCString()}${process.env.NODE_ENV==='production'?'; Secure':''}`; export const csrfCookie = (token:string,expiresAt:string) => `conductor_csrf=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Expires=${new Date(expiresAt).toUTCString()}${process.env.NODE_ENV==='production'?'; Secure':''}`; export const clearSessionCookie = () => `conductor_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0${process.env.NODE_ENV==='production'?'; Secure':''}`; export const clearCsrfCookie = () => `conductor_csrf=; Path=/; SameSite=Lax; Max-Age=0${process.env.NODE_ENV==='production'?'; Secure':''}`;