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)Scopes
Section titled “Scopes”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 checkac.category('billing') .require('$.ip cidr 10.0.0.0/8'); // any billing/* resourceac.resource('billing/invoice') .require('$.mfa == true'); // just billing/invoiceExample: Layered Gates
Section titled “Example: Layered Gates”const ac = new AccessControl(grants, { context: { env: process.env.NODE_ENV }});
ac.require('$.env == "prod"'); // 1) prod onlyac.category('billing').require('$.ip cidr 10.0.0.0/8'); // 2) + from the VPNac.resource('billing/invoice').require('$.mfa == true'); // 3) + MFA
// passes only if prod AND in-VPN AND mfa — on top of a matching grantac.can('accountant', { ip, mfa: true }) .readAny('billing/invoice').granted;A denial by a gate surfaces as reason: 'require_failed' on the
access event.
Missing Context — Gates Fail Closed
Section titled “Missing Context — Gates Fail Closed”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; // trueac.can('admin', { env: 'dev' }).readAny('post').granted; // falseac.can('admin', {}).readAny('post').granted; // false ← env missingac.can('admin').readAny('post').granted; // false ← no contextWith 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.
Inspecting Gates
Section titled “Inspecting Gates”ac.getRequirements();// { global: [...], categories: { billing: [...] }, resources: { 'billing/invoice': [...] } }Async Gates
Section titled “Async Gates”A gate may use a custom { fn, args } condition; like conditional grants, that
moves the check to the async path
(grantedAsync / checkAsync).