import { rolesRepo, type RolePermission } from "@eden/database"; export const SUPER_ADMIN_ROLE = "super_admin"; export type ResolvedPermissions = | { superAdmin: true } | { superAdmin: false; grants: RolePermission[] }; /** * super_admin is never looked up in role_permissions — hardcoded bypass, * mirrored from the legacy system and enforced again at the DB level * (docs/adr/0004-permission-model.md, chk_role_permissions_no_super_admin). */ export async function resolvePermissions(roleKey: string): Promise { if (roleKey === SUPER_ADMIN_ROLE) return { superAdmin: true }; const grants = await rolesRepo.getRolePermissions(roleKey); return { superAdmin: false, grants }; } /** * Returns the maximum scope the actor holds for (resource, action), or * null if they hold none. Scope precedence — broader wins when a role * somehow has more than one row for the same resource+action (shouldn't * happen given the UNIQUE constraint per scope, but a role could have * both e.g. 'own' and 'all' rows intentionally): */ const SCOPE_RANK: Record = { own: 1, team: 2, reseller: 3, legal_entity: 4, all: 5, }; export function maxGrantedScope( permissions: ResolvedPermissions, resource: string, action: RolePermission["action"], ): RolePermission["scope"] | "all" | null { if (permissions.superAdmin) return "all"; const matches = permissions.grants.filter((g) => g.resource === resource && g.action === action); if (matches.length === 0) return null; return matches.reduce( (best, g) => (SCOPE_RANK[g.scope] > SCOPE_RANK[best] ? g.scope : best), matches[0]!.scope, ); } export function hasPermission( permissions: ResolvedPermissions, resource: string, action: RolePermission["action"], ): boolean { return maxGrantedScope(permissions, resource, action) !== null; }