import { FreshContext } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { rolePermissions, users } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; import { and, eq } from "npm:drizzle-orm@0.45.2"; type RuleFn = ( req: Request, ctx: FreshContext, ) => Promise | boolean; async function hasPermission( uid: string, permission: string, ): Promise { const [user] = await db.select().from(users).where(eq(users.id, uid)); if (!user || user.idRole === null) return false; const [rp] = await db.select().from(rolePermissions).where( and( eq(rolePermissions.idRole, user.idRole!), eq(rolePermissions.idPermission, permission), ), ); return !!rp; } function parseNumEtud(uid: string): number { return parseInt(uid.slice(1)); } const rules = { student_read: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "student_read"), student_write: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "student_write"), note_read: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "note_read"), note_write: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "note_write"), module_read: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "module_read"), module_write: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "module_write"), user_read: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "user_read"), user_write: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "user_write"), role_write: (_req: Request, ctx: FreshContext) => hasPermission(ctx.state.session.uid, "role_write"), // Contextual rules — student accessing their own data own_student: (_req: Request, ctx: FreshContext) => parseNumEtud(ctx.state.session.uid) === Number(ctx.params.numEtud), own_note: (_req: Request, ctx: FreshContext) => parseNumEtud(ctx.state.session.uid) === Number(ctx.params.numEtud), }; export type RuleName = keyof typeof rules; type HandlerFn = ( req: Request, ctx: FreshContext, ) => Promise; /** * Wraps a route handler with permission checks. * Access is granted if ANY of the provided rules passes (OR logic). * Returns 403 if none pass. * * @example * export const handler: Handlers = { * GET: withRules(["note_read", "own_note"])(async (req, ctx) => { * // ... * }), * }; */ export function withRules(ruleNames: RuleName[]) { return (handler: HandlerFn): HandlerFn => { return async (req, ctx) => { const results = await Promise.all( ruleNames.map((name) => rules[name](req, ctx)), ); if (!results.some(Boolean)) { return new Response(null, { status: 403 }); } return handler(req, ctx); }; }; }