docs: resolve two pre-flight conflicts in the authz plan
- Global Constraints said the change was additive while Task 8 changed authorizeDecision's 403 body. Ruled: the security fix governs; the constraint now names it as the one approved exception. - Task 6 defined permissionsFor and then re-implemented it inline in decide. Both now call a single loadEffective helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
- Zero runtime npm dependencies. Use only Bun/WebCrypto/node: builtins.
|
||||
- `@wrnexus/core` MUST NOT import `@wrnexus/authz`. `can()` stays off `Context`; the resolver lives in `ctx.locals._authz`.
|
||||
- `@wrnexus/authz` may import **types only** from `@wrnexus/core` (`import type { Context, Middleware }`).
|
||||
- Existing exports of `@wrnexus/authz` must keep working unchanged. This is additive.
|
||||
- Existing exports of `@wrnexus/authz` keep working unchanged, with ONE approved exception: Task 8 changes the default 403 body of `authorizeDecision` to stop disclosing policy internals. That break is intentional and ruled on; everything else is additive.
|
||||
- Framework-owned tables use the `_wrn_` prefix (matching `_wrn_tenant`, `_wrn_cursor`). The spec wrote `wrn_authz_assignment`; use `_wrn_authz_assignment` and `_wrn_authz_grant`.
|
||||
- `requirePermission` is already exported with signature `(rbac: Rbac, permission: string)`. Do not change it. The new resource-aware guard is named `guardPermission`.
|
||||
- Every failure path denies. Never fail open.
|
||||
@@ -1344,13 +1344,21 @@ export function createAuthzResolver(options: AuthzResolverOptions): AuthzResolve
|
||||
const { catalog, store, audit } = options;
|
||||
const strict = options.strict ?? !isProduction();
|
||||
|
||||
const permissionsFor = async (subjectId: string, scope?: AuthzScope): Promise<Set<string>> => {
|
||||
/**
|
||||
* Single source of truth for "what does this subject hold?". Returns the
|
||||
* raw assignments too, because `decide` needs `denies` and `permissionsFor`
|
||||
* does not — do NOT duplicate this logic in either caller.
|
||||
*/
|
||||
const loadEffective = async (subjectId: string, scope?: AuthzScope) => {
|
||||
const assignments = await store.assignmentsFor(subjectId, scope);
|
||||
const granted = expandRoles(catalog, assignments.roles);
|
||||
for (const grant of assignments.grants) granted.add(grant);
|
||||
return granted;
|
||||
return { assignments, granted };
|
||||
};
|
||||
|
||||
const permissionsFor = async (subjectId: string, scope?: AuthzScope): Promise<Set<string>> =>
|
||||
(await loadEffective(subjectId, scope)).granted;
|
||||
|
||||
const finish = (input: DecideInput, result: AuthorizationDecision): AuthorizationDecision => {
|
||||
if (!result.allowed || options.auditAllows) {
|
||||
safeRecord(audit, {
|
||||
@@ -1399,9 +1407,7 @@ export function createAuthzResolver(options: AuthzResolverOptions): AuthzResolve
|
||||
let assignments;
|
||||
let granted: Set<string>;
|
||||
try {
|
||||
assignments = await store.assignmentsFor(subjectId, scope);
|
||||
granted = expandRoles(catalog, assignments.roles);
|
||||
for (const grant of assignments.grants) granted.add(grant);
|
||||
({ assignments, granted } = await loadEffective(subjectId, scope));
|
||||
} catch (error) {
|
||||
console.error("[wrnexus:authz] permission store failed; denying", error);
|
||||
return finish(input, { allowed: false, reason: "Authorization store unavailable" });
|
||||
|
||||
Reference in New Issue
Block a user