Unreleased changes
These pages document main, which has changes 0.1.2 does not. npm install @evanion/acl gives you 0.1.2, documented here. Values on these pages are produced by running the current source on every build.
Rules about the thing being accessed
Many authorization decisions depend not just on who the user is, but on the specific resource they are trying to access. For example, while any customer can view a question in the Baize shop, only the customer who asked that specific question should be allowed to edit it.
To handle this, @evanion/acl allows you to write conditions that read properties directly from the object being accessed.
Authorizing based on the resource
When a condition begins with object., the evaluator looks at the row passed to the can() call. This allows you to create dynamic rules based on the data in your database.
Instead of writing manual if checks in every handler—which inevitably drift and lead to bugs—you can declare the relationship once in your policy:
import { } from '@evanion/acl';
type = { : string; : string[] };
type = { : string; : string };
const = <, { : }>()
.('question', () =>
.('update', .('object.askedBy', 'subject.id')),
)
.();
// The same roles, so nothing on the subject tells them apart.
const = { : 'sam', : ['customer'] };
const = { : 'jo', : ['customer'] };
const = { : 'urn:game:brass-birmingham', : 'sam' };
.(, 'question', 'update', ).; // -> true
.(, 'question', 'update', ).; // -> falseBy using p.eq('object.askedBy', 'subject.id'), you’ve created a rule that only matches if the user’s ID matches the askedBy field of the question.
The “Unevaluable” state
Unlike subject-only rules, object-based rules depend on the data you provide to the can() call. If your database query didn’t select the field that a rule needs to read, the evaluator cannot decide if the rule holds or fails.
In this case, the decision returns a fourth state: unevaluable.
If a decision is unevaluable, the missing field will list the exact paths that were missing from the object. Your application can then use this list to perform a targeted refetch of the missing data and ask the evaluator again.
`unevaluable` is not `denied`
An unevaluable result still results in allowed: false, so your app
remains secure. However, it is fundamentally different from a denied
result. A denied request is a permanent refusal; an unevaluable request is a
temporary lack of information. Treating an unevaluable result as “forbidden”
might hide features from a user who actually has permission.
Ensuring path safety
Because object.askedBy is just a string, it’s easy to make a typo (e.g., object.askedBv). In a large policy, these typos can be hard to find because the matrix still builds, and the rules simply never match.
To prevent this, @evanion/acl allows you to declare the shape of your objects in the Objects type parameter of policy<Subject, Objects>(). This enables the TypeScript compiler to check your paths against your data model at compile time.
For a deep dive into using types to secure your policy, see Typed authoring.
Next steps: Applying the rules
Now that you can write rules based on the resource, you need to apply them to your data.
- Can this user do this? explains how to handle the
unevaluablestate and how to check permissions for lists of objects (canMany). - Which fields may they write? covers the more advanced
canFieldsAPI, which decides not just if an action is allowed, but which specific fields of the object may be updated.