import { ForbiddenError } from '@/lib/errors'; import { type RolePermissions } from '@/lib/db/schema/users'; import type { AuthContext } from '@/lib/api/helpers'; export type { RolePermissions }; export type PermissionResource = keyof RolePermissions; export type PermissionAction = keyof RolePermissions[R]; /** * Checks whether a permissions map grants a specific resource/action pair. * * Returns `true` automatically when `permissions` is `null`, which signals a * super-admin context that bypasses all permission checks. */ export function hasPermission( permissions: RolePermissions | null, resource: R, action: PermissionAction, ): boolean { // null = super admin; all permissions implicitly granted. if (permissions === null) return true; const resourcePerms = permissions[resource] as Record | undefined; if (!resourcePerms) return false; return resourcePerms[action as string] === true; } /** * Throws a `ForbiddenError` if the auth context does not have the required * resource/action permission. * * For use inside API route handlers or service functions after `withAuth` has * resolved the context. * * @example * requirePermission(ctx, 'clients', 'delete'); */ export function requirePermission( ctx: Pick, resource: R, action: PermissionAction, ): void { if (ctx.isSuperAdmin) return; if (!hasPermission(ctx.permissions, resource, action)) { throw new ForbiddenError('Insufficient permissions'); } }