import type { AuthzModule } from "./types.ts"; const PERMISSION_ID = /^[a-z0-9]+(?::[a-z0-9-]+)+$/; /** * Validate and freeze one authorization declaration. Called from * `app/authz/.ts` as the module's default export. */ export function defineAuthz(module: AuthzModule): AuthzModule { const permissions = module.permissions ?? {}; const roles = module.roles ?? {}; const policies = module.policies ?? {}; const attributes = module.attributes ?? {}; const bindings = module.bindings ?? {}; for (const id of Object.keys(permissions)) { if (id.includes("*")) { throw new Error( `WRN-AUTHZ-DECL: permission id '${id}' must not contain a wildcard; wildcards belong in roles.`, ); } if (!PERMISSION_ID.test(id)) { throw new Error( `WRN-AUTHZ-DECL: permission id '${id}' must be lowercase colon-namespaced, e.g. 'post:read'.`, ); } } for (const [role, grants] of Object.entries(roles)) { for (const grant of grants) { if (typeof grant !== "string" || !grant.trim()) { throw new Error( `WRN-AUTHZ-DECL: role '${role}' grants an empty entry; expected a permission, 'ns:*', or 'role:'.`, ); } } } for (const [permission, names] of Object.entries(bindings)) { for (const name of names) { if (!(name in policies)) { throw new Error( `WRN-AUTHZ-DECL: binding for '${permission}' names policy '${name}', which is not declared in the same module.`, ); } } } return Object.freeze({ permissions, roles, policies, attributes, bindings }); }