@aooth/arbac 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,6 +33,44 @@ Full docs, API reference, and recipes: **https://aooth.moost.org/arbac/**
|
|
|
33
33
|
- [Codegen CLI](https://aooth.moost.org/arbac/codegen)
|
|
34
34
|
- [API reference](https://aooth.moost.org/api/arbac)
|
|
35
35
|
|
|
36
|
+
## Deny is binary on (resource, action)
|
|
37
|
+
|
|
38
|
+
`.deny(resource, action)` short-circuits the entire `(resource, action)` tuple — there is no scope or filter parameter. Once a deny rule's action regex matches, `evaluate()` returns `{ allowed: false }` immediately, before any other role's `allow` scopes are even constructed (see `arbac-core/src/arbac.ts:126-131`).
|
|
39
|
+
|
|
40
|
+
If you need "hide rows where condition X", express it as an `allow` with a filter that excludes X, not as a partial deny:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// ✗ Not supported — no row-level deny.
|
|
44
|
+
// .deny("tasks", "query", { scope: { filter: { status: "secret" } } })
|
|
45
|
+
|
|
46
|
+
// ✓ Use a filtered allow instead.
|
|
47
|
+
allowTableRead<UserAttrs, Scope>("tasks", {
|
|
48
|
+
scope: (attrs) => ({
|
|
49
|
+
filter: { tenantId: attrs.tenantId, status: { $ne: "secret" } },
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pinned by `UNION-03` and `UNION-05` in `packages/e2e-demo/test/arbac-union.spec.ts` — unscoped deny short-circuits sibling allows, and wildcard `.deny("tasks", "*")` 403s both row-allow (`markDone`) and set-allow (`new`) tuples.
|
|
55
|
+
|
|
56
|
+
## Scopes from multiple roles union via `$or` — permissive wins
|
|
57
|
+
|
|
58
|
+
When a user has multiple roles that each `allow` the same `(resource, action)`, their scopes are merged disjunctively via `mergeScopeFilters` (see `packages/arbac/src/scope/filter.ts`). A row matches if ANY scope's filter matches. Consequence: if one scope has no filter (rule registered without `scope`, or with `filter: undefined` / `filter: {}`), the union widens to universe and the more restrictive filters in sibling scopes are silently dropped.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// Role "viewer" — tenant-scoped read on tasks.
|
|
62
|
+
.use(allowTableRead("tasks", {
|
|
63
|
+
scope: (attrs) => ({ filter: { tenantId: attrs.tenantId } }),
|
|
64
|
+
}))
|
|
65
|
+
|
|
66
|
+
// Role "tasks-everywhere" — unscoped read on tasks (e.g., for an audit role).
|
|
67
|
+
.allow("tasks", "query") // no scope ← contributes universe to the union
|
|
68
|
+
|
|
69
|
+
// A user with both roles sees ALL tasks (universe wins), not just their tenant.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This is the natural `$or` semantic: union of "tenant rows" and "all rows" is "all rows". The takeaway: don't grant an unscoped allow on a resource that other roles are trying to filter. Pinned by the `transformArbacFilter` empty-vs-undefined coercion tests in `packages/arbac-moost/src/db/shared-read-helpers.spec.ts`.
|
|
73
|
+
|
|
36
74
|
## License
|
|
37
75
|
|
|
38
76
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aooth/arbac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Batteries-included RBAC: builder API, privilege factories, scope merge utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"access-control",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@aooth/arbac-core": "0.1.
|
|
48
|
+
"@aooth/arbac-core": "0.1.4"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "vp pack",
|