Skip to content

Require Gates

.require() adds a mandatory gate: a condition that must pass for a check to be granted, independent of any role’s grants.

granted = (a grant matches) AND (every applicable gate passes)

A gate applies at one of three scopes. On a check, the applicable gates are the global ones, plus the resource’s category gates, plus the resource gates — all must pass.

ac.require('$.env == "prod"'); // global: every check
ac.category('billing')
.require('$.ip cidr 10.0.0.0/8'); // any billing/* resource
ac.resource('billing/invoice')
.require('$.mfa == true'); // just billing/invoice
const ac = new AccessControl(grants, {
context: { env: process.env.NODE_ENV }
});
ac.require('$.env == "prod"'); // 1) prod only
ac.category('billing').require('$.ip cidr 10.0.0.0/8'); // 2) + from the VPN
ac.resource('billing/invoice').require('$.mfa == true'); // 3) + MFA
// passes only if prod AND in-VPN AND mfa — on top of a matching grant
ac.can('accountant', { ip, mfa: true })
.readAny('billing/invoice').granted;

A denial by a gate surfaces as reason: 'require_failed' on the access event.

A gate’s condition is evaluated against the check-time context. There is no separate “is this property present?” step — a $.‑path that the context doesn’t supply simply resolves to undefined, and the operator runs against that.

For the common positive assertion this is exactly the behavior you want:

ac.grant('admin').readAny('post', ['*']);
ac.require('$.env == "prod"');
ac.can('admin', { env: 'prod' }).readAny('post').granted; // true
ac.can('admin', { env: 'dev' }).readAny('post').granted; // false
ac.can('admin', {}).readAny('post').granted; // false ← env missing
ac.can('admin').readAny('post').granted; // false ← no context

With env absent, $.env is undefined, undefined === 'prod' is false, the gate fails, and the check is denied (reason: 'require_failed'). A gate that references data you forgot to pass denies rather than silently letting the request through — fail‑closed by construction.

The same resolution rule applies to .where() grant conditions — a missing path evaluates to a clean false (the grant simply doesn’t apply) and never throws. The security difference is directional: an absent property makes a grant not apply and makes a positive gate deny — both restrictive — while a negative gate is the one case where absence widens access.

ac.getRequirements();
// { global: [...], categories: { billing: [...] }, resources: { 'billing/invoice': [...] } }

A gate may use a custom { fn, args } condition; like conditional grants, that moves the check to the async path (grantedAsync / checkAsync).