Skip to content

PermissionEvaluator ​

For complete Authup grant enforcement, read the caller's document from GET /authorization and use createAuthorizationEvaluator. The generic evaluator below does not enforce grant realm reach by itself.

This package is shipped with a framework for evaluating and checking permissions.

The PermissionEvaluator can be initialized with various providers. In the following, the PermissionEvaluator is initialized with the help of the PermissionMemoryProvider.

Provider ​

For demonstration purposes, the PermissionMemoryProvider is only initialized with three permissions, whereby only the first permission is defined with a policy.

typescript
import { PermissionMemoryProvider } from '@authup/access';

const items = [
    {
        permission: { name: 'user_update' },
        policies: [
            {
                type: 'attributeNames',
                names: ['name'],
            },
        ],
    },
    {
        permission: { name: 'user_create' },
    },
    {
        permission: { name: 'user_delete' },
    },
]

const provider = new PermissionMemoryProvider(items);

Evaluate ​

To evaluate if a permission will be granted use the evaluate method. The evaluate method accepts an object of type PermissionEvaluationContext.

typescript
import { PermissionEvaluator, PermissionMemoryProvider, PolicyData } from '@authup/access';

const evaluator = new PermissionEvaluator({
    provider,
});

await evaluator.evaluate({
    name: 'user_create',
});
// success (always) - no restrictions/policies

const data = new PolicyData();
data.set('attributes', { name: 'admin' });

await evaluator.evaluate({
    name: 'user_update',
    data,
});
// success

const data2 = new PolicyData();
data2.set('attributes', { name: 'admin', foo: 'bar' });

await evaluator.evaluate({
    name: 'user_update',
    data: data2,
});
// fails - foo is not allowed as attribute name

Each method throws a PermissionError on denial and resolves on success.

Pre-Evaluate ​

preEvaluate is the pre-flight gate: it runs before the data a policy needs is fully known (e.g. before the target row is loaded or the request payload is validated). It is derived from data availability: a policy whose required data keys are absent from the bag stays pending and passes the gate — only a policy that settles false with the data available at that point denies. The later evaluate() call with the complete data remains the authority (there, pending counts as a denial).

typescript
await evaluator.preEvaluate({
    name: 'user_update',
});
// success — the attributeNames policy needs `attributes` (or `attributeNames`),
// neither of which is available yet: it stays pending and passes the gate

const data = new PolicyData();
data.set('attributes', { name: 'admin', foo: 'bar' });

await evaluator.evaluate({
    name: 'user_update',
    data,
});
// fails — with the data present, the policy settles false

This also holds under invert and inside composite trees: an unknown child is never masked to a settled value, so an inverted policy tree cannot produce a spurious denial at the gate.

evaluateOneOf / preEvaluateOneOf are variants that pass when any of the given permission names passes (affirmative decision strategy).

Compile ​

compile is the query-build counterpart of evaluate: instead of deciding a single access request, it expresses the permission's restrictions as a condition over row attributes (a rapiq ICondition), so list endpoints can enforce authorization in the database query itself — keeping pagination and totals exact.

typescript
const result = await evaluator.compile({ name: 'user_update' });

switch (result.verdict) {
    case 'allow':        // no restriction — every row passes
        break;
    case 'deny':         // no row can pass
        break;
    case 'conditional':  // push result.condition into the row query (WHERE)
        break;
    case 'post':         // not expressible — load rows and evaluate() per row
        break;
}

Multiple names compile as a disjunction (evaluateOneOf semantics): any unrestricted name yields allow, and a single non-expressible name degrades the whole result to post — pushing only part of a disjunction would wrongly exclude rows.

A conditional result is exact: a row satisfies the condition if and only if a full evaluate() with that row's attributes would pass. When it cannot be guaranteed, compile returns post instead — falling back to per-row evaluation is always sound.