import { Request, Response, NextFunction } from "express"; import { supabase } from "../routes/auth.js"; import { db } from "@workspace/db"; import { usersTable } from "@workspace/db"; import { eq } from "drizzle-orm"; import { logAudit } from "../lib/audit-logger.js"; export interface AuthRequest extends Request { userId?: number; userUid?: string; user?: typeof usersTable.$inferSelect; } export async function requireAuth(req: AuthRequest, res: Response, next: NextFunction) { const authHeader = req.headers.authorization; if (!authHeader?.startsWith("Bearer ")) { logAudit({ userId: req.userUid ?? req.userId ?? 1, action: "unauthorized_access", entityType: "auth", details: { attemptedEndpoint: req.originalUrl || req.url, httpMethod: req.method, reason: "Missing or invalid authorization header", }, req, }); res.status(401).json({ error: "Unauthorized" }); return; } const token = authHeader.slice(7); const { data: { user: supabaseUser }, error } = await supabase.auth.getUser(token); if (error || !supabaseUser) { logAudit({ userId: req.userUid ?? req.userId ?? 1, action: "unauthorized_access", entityType: "auth", details: { attemptedEndpoint: req.originalUrl || req.url, httpMethod: req.method, reason: error?.message ?? "Invalid or expired token", }, req, }); res.status(401).json({ error: "Invalid or expired token" }); return; } const [user] = await db.select().from(usersTable).where(eq(usersTable.userId, supabaseUser.id)); if (!user) { logAudit({ userId: supabaseUser.id, action: "unauthorized_access", entityType: "auth", details: { attemptedEndpoint: req.originalUrl || req.url, httpMethod: req.method, reason: "User no longer exists in database", }, req, }); res.status(401).json({ error: "User no longer exists" }); return; } req.userId = user.id; req.userUid = user.userId ?? String(user.id); req.user = user; (req as any).userUid = req.userUid; // Ensure any generic Request cast can read it next(); } export function requireRoles(...roles: string[]) { return (req: AuthRequest, res: Response, next: NextFunction) => { if (!req.user) { logAudit({ userId: 1, action: "unauthorized_access", entityType: "auth", details: { attemptedEndpoint: req.originalUrl || req.url, httpMethod: req.method, reason: "Unauthorized: No user loaded", }, req, }); res.status(401).json({ error: "Unauthorized" }); return; } if (!roles.includes(req.user.role)) { logAudit({ userId: req.user.id, action: "unauthorized_access", entityType: "auth", details: { attemptedEndpoint: req.originalUrl || req.url, httpMethod: req.method, callerRole: req.user.role, requiredRoles: roles, reason: "Forbidden: insufficient role", }, req, }); res.status(403).json({ error: "Forbidden: insufficient role" }); return; } next(); }; } export const requireAdmin = requireRoles("admin"); export const requireHeadOfTeam = requireRoles("admin", "head_of_team"); export const requireTeamLeadOrAbove = requireRoles("admin", "head_of_team", "team_lead", "dept_manager"); export const requireManagerOrAdmin = requireTeamLeadOrAbove; // Compatibility alias