Second small package in a series I started last week (first one was rule-lite, a JSON rule evaluator). This one's about permissions.
Here's the thing about "can this user edit this invoice" - it sounds like a yes/no role question but it almost never is. It's really "can this user edit this invoice, AND do they own it, AND is it under some approval limit." Plain role-based permissions handle the first half fine. Then the second half turns into random if-checks scattered across the codebase, and six months later nobody remembers why that one check exists.
So I made entitlement-lite. Small idea: roles hold permissions like normal, but any permission can also carry an optional condition.
const engine = new EntitlementEngine([
{
name: 'contributor',
permissions: [{
action: 'edit',
resource: 'invoice',
condition: { field: 'resource.ownerId', operator: 'eq', value: 'u1' }
}]
}
]);
engine.can(subject, 'edit', 'invoice', { resource: { ownerId: 'u1' } }); // true
No condition on a permission? It's just a normal role check. Condition present? It gets evaluated at request time using rule-lite (the package I put out last week). I didn't want to write a second rule engine just for this, so it reuses the first one.
Also supports wildcards so you're not stuck writing { action: 'view' }, { action: 'edit' }, { action: 'delete' }... for an admin role - { action: '*', resource: '*' } covers it.
It's deliberately not trying to be OPA or Casbin. Those are the right call for big multi-service auth systems. This is for the much more common case: one app, a handful of roles, a few conditions that need actual logic instead of hardcoded ifs.
Repo: https://github.com/tejas821/entitlement-lite
npm: https://www.npmjs.com/package/entitlement-lite
If you've built something similar or have opinions on the API (especially the wildcard matching or how conditions merge into context), I'd like to hear it.
Top comments (1)
Hello ! Great pattern. However, for the condition I would suggest replacing the object with a boolean function
(context: EvaluationContext) => boolean. With your condition object you are re-creating a small expression DSL limiting the expressiveness and is far less convenient than just a function.