Spaces:
Sleeping
Sleeping
| 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 | |