Files
WRNexusJS/packages/authz/src/registry.ts
T
Clintchiz 56cde5aaf8
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s
fix(authz): reserve role inheritance namespace
2026-08-23 19:35:03 +05:30

58 lines
1.9 KiB
TypeScript

import type { AuthzModule, DefinedAuthzModule } from "./types.ts";
const PERMISSION_ID = /^[a-z0-9]+(?::[a-z0-9-]+)+$/;
/**
* Validate and freeze one authorization declaration. Called from
* `app/authz/<name>.ts` as the module's default export.
*/
export function defineAuthz<Subject = any, Resource = any>(
module: AuthzModule<Subject, Resource>,
): DefinedAuthzModule<Subject, Resource> {
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.startsWith("role:")) {
throw new Error(
`WRN-AUTHZ-DECL: permission id '${id}' uses the reserved 'role:' prefix; role grants use 'role:<name>' for inheritance.`,
);
}
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:<name>'.`,
);
}
}
}
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 });
}