@nmnmcc/ability 1.0.0 → 2.0.0
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 +143 -87
- package/dist/Authorizer.d.ts +43 -47
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js +5 -290
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +164 -332
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +75 -890
- package/dist/Condition.js.map +1 -1
- package/dist/Decision.d.ts +13 -13
- package/dist/Decision.d.ts.map +1 -1
- package/dist/Decision.js.map +1 -1
- package/dist/Domain.d.ts +10 -21
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +4 -62
- package/dist/Domain.js.map +1 -1
- package/dist/HttpApi.d.ts +31 -7
- package/dist/HttpApi.d.ts.map +1 -1
- package/dist/HttpApi.js +28 -11
- package/dist/HttpApi.js.map +1 -1
- package/dist/Limits.d.ts +114 -0
- package/dist/Limits.d.ts.map +1 -0
- package/dist/Limits.js +132 -0
- package/dist/Limits.js.map +1 -0
- package/dist/Policy.d.ts +274 -272
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +71 -1180
- package/dist/Policy.js.map +1 -1
- package/dist/Resource.d.ts +8 -181
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +8 -807
- package/dist/Resource.js.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +4 -5
- package/dist/internal/algebra.js +267 -0
- package/dist/internal/algebra.js.map +1 -0
- package/dist/internal/analysis.js +245 -0
- package/dist/internal/analysis.js.map +1 -0
- package/dist/internal/authorizer.d.ts +30 -0
- package/dist/internal/authorizer.d.ts.map +1 -0
- package/dist/internal/authorizer.js +221 -0
- package/dist/internal/authorizer.js.map +1 -0
- package/dist/internal/bind.js +286 -0
- package/dist/internal/bind.js.map +1 -0
- package/dist/internal/catalog.d.ts +56 -0
- package/dist/internal/catalog.d.ts.map +1 -0
- package/dist/internal/catalog.js +292 -0
- package/dist/internal/catalog.js.map +1 -0
- package/dist/internal/decide.js +166 -0
- package/dist/internal/decide.js.map +1 -0
- package/dist/internal/domain.js +32 -0
- package/dist/internal/domain.js.map +1 -0
- package/dist/internal/json.js +134 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/internal/limits.d.ts +14 -0
- package/dist/internal/limits.d.ts.map +1 -0
- package/dist/internal/limits.js +43 -0
- package/dist/internal/limits.js.map +1 -0
- package/dist/internal/nodes.d.ts +121 -0
- package/dist/internal/nodes.d.ts.map +1 -0
- package/dist/internal/nodes.js +277 -0
- package/dist/internal/nodes.js.map +1 -0
- package/dist/internal/patterns.js +110 -0
- package/dist/internal/patterns.js.map +1 -0
- package/dist/internal/policy-json.js +255 -0
- package/dist/internal/policy-json.js.map +1 -0
- package/dist/internal/policy-nodes.d.ts +61 -0
- package/dist/internal/policy-nodes.d.ts.map +1 -0
- package/dist/internal/policy-nodes.js +146 -0
- package/dist/internal/policy-nodes.js.map +1 -0
- package/dist/internal/policy.d.ts +42 -0
- package/dist/internal/policy.d.ts.map +1 -0
- package/dist/internal/policy.js +124 -0
- package/dist/internal/policy.js.map +1 -0
- package/dist/internal/read.js +39 -0
- package/dist/internal/read.js.map +1 -0
- package/dist/internal/resource.js +115 -0
- package/dist/internal/resource.js.map +1 -0
- package/dist/internal/rule.d.ts +26 -0
- package/dist/internal/rule.d.ts.map +1 -0
- package/dist/internal/rule.js +93 -0
- package/dist/internal/rule.js.map +1 -0
- package/dist/internal/search.d.ts +22 -0
- package/dist/internal/search.d.ts.map +1 -0
- package/dist/internal/search.js +274 -0
- package/dist/internal/search.js.map +1 -0
- package/dist/internal/suggest.js +46 -0
- package/dist/internal/suggest.js.map +1 -0
- package/package.json +4 -5
- package/src/Authorizer.ts +69 -585
- package/src/Condition.ts +339 -2095
- package/src/Decision.ts +6 -5
- package/src/Domain.ts +13 -70
- package/src/HttpApi.ts +75 -13
- package/src/Limits.ts +132 -0
- package/src/Policy.ts +386 -2772
- package/src/Resource.ts +14 -1555
- package/src/index.ts +3 -9
- package/src/internal/algebra.ts +482 -0
- package/src/internal/analysis.ts +478 -0
- package/src/internal/authorizer.ts +418 -0
- package/src/internal/bind.ts +596 -0
- package/src/internal/catalog.ts +534 -0
- package/src/internal/decide.ts +402 -0
- package/src/internal/domain.ts +66 -0
- package/src/internal/json.ts +260 -0
- package/src/internal/limits.ts +78 -0
- package/src/internal/nodes.ts +404 -0
- package/src/internal/patterns.ts +211 -0
- package/src/internal/policy-json.ts +484 -0
- package/src/internal/policy-nodes.ts +207 -0
- package/src/internal/policy.ts +264 -0
- package/src/internal/read.ts +55 -0
- package/src/internal/resource.ts +180 -0
- package/src/internal/rule.ts +181 -0
- package/src/internal/search.ts +541 -0
- package/src/internal/suggest.ts +104 -0
- package/dist/Principal.d.ts +0 -35
- package/dist/Principal.d.ts.map +0 -1
- package/dist/Principal.js +0 -37
- package/dist/Principal.js.map +0 -1
- package/dist/Rule.d.ts +0 -275
- package/dist/Rule.d.ts.map +0 -1
- package/dist/Rule.js +0 -257
- package/dist/Rule.js.map +0 -1
- package/src/Principal.ts +0 -41
- package/src/Rule.ts +0 -683
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Authorization tools for Effect programs. Authorization means the work of
|
|
4
4
|
deciding if a principal may do an act. A principal is the one who asks: a
|
|
5
|
-
person,
|
|
5
|
+
person, an API token, or another service.
|
|
6
6
|
|
|
7
7
|
The library is made with [Effect](https://effect.website) only:
|
|
8
8
|
|
|
@@ -15,6 +15,8 @@ The library is made with [Effect](https://effect.website) only:
|
|
|
15
15
|
- An `Authorizer` is an Effect service. A "no" goes into the error channel. When
|
|
16
16
|
a policy can not be loaded, the check fails, so an error is never a "yes".
|
|
17
17
|
|
|
18
|
+
Version 2.0 changed the API. See [MIGRATION.md](MIGRATION.md) to move from 1.x.
|
|
19
|
+
|
|
18
20
|
## Requirements
|
|
19
21
|
|
|
20
22
|
- Use strict type checking. Without it, the types do not give all of their
|
|
@@ -36,9 +38,7 @@ import {
|
|
|
36
38
|
Decision,
|
|
37
39
|
Domain,
|
|
38
40
|
Policy,
|
|
39
|
-
Principal,
|
|
40
41
|
Resource,
|
|
41
|
-
Rule,
|
|
42
42
|
} from "@nmnmcc/ability"
|
|
43
43
|
import * as AbilityHttpApi from "@nmnmcc/ability/HttpApi"
|
|
44
44
|
```
|
|
@@ -46,13 +46,12 @@ import * as AbilityHttpApi from "@nmnmcc/ability/HttpApi"
|
|
|
46
46
|
| Module | What it is |
|
|
47
47
|
| ------------ | --------------------------------------------------------------------------- |
|
|
48
48
|
| `Resource` | A kind of thing to act on, with its actions, its Schema, and a path. |
|
|
49
|
-
| `
|
|
50
|
-
| `Domain` | The principal and all the resources. Every other type comes from it. |
|
|
49
|
+
| `Domain` | The principal Schema and all the resources. Every type comes from it. |
|
|
51
50
|
| `Condition` | A test on a resource value, as plain data. |
|
|
52
|
-
| `
|
|
53
|
-
| `Policy` | A tree of rules that gives a decision, a residual condition, and fields. |
|
|
51
|
+
| `Policy` | Rules, and a tree of rules that gives a decision, a residual and fields. |
|
|
54
52
|
| `Decision` | `Allow`, `Deny` or `NotApplicable`. |
|
|
55
53
|
| `Authorizer` | An Effect service that checks the principal of the current request. |
|
|
54
|
+
| `Limits` | The limits that keep every walk and every decoder small. |
|
|
56
55
|
| `HttpApi` | Annotations and a middleware for `effect/http-api`. A separate entry point. |
|
|
57
56
|
|
|
58
57
|
## First Steps
|
|
@@ -63,9 +62,9 @@ the same time:
|
|
|
63
62
|
|
|
64
63
|
```ts
|
|
65
64
|
import { Effect, Schema } from "effect"
|
|
66
|
-
import { Decision, Domain, Policy,
|
|
65
|
+
import { Decision, Domain, Policy, Resource } from "@nmnmcc/ability"
|
|
67
66
|
|
|
68
|
-
class User extends
|
|
67
|
+
class User extends Schema.Class<User>("User")({
|
|
69
68
|
id: Schema.String,
|
|
70
69
|
}) {}
|
|
71
70
|
|
|
@@ -140,35 +139,35 @@ There are two ways to make a resource:
|
|
|
140
139
|
|
|
141
140
|
```ts
|
|
142
141
|
// A Schema class. Its body may have its own getters and methods.
|
|
143
|
-
class
|
|
142
|
+
class Document extends Resource.Class<Document>()("Document", {
|
|
144
143
|
actions: ["read", "update"],
|
|
145
|
-
fields: { id: Schema.String,
|
|
144
|
+
fields: { id: Schema.String, ownerId: Schema.NullOr(Schema.String) },
|
|
146
145
|
}) {
|
|
147
|
-
get
|
|
148
|
-
return this.
|
|
146
|
+
get orphaned() {
|
|
147
|
+
return this.ownerId === null
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
150
|
|
|
152
151
|
// Plain objects with a `_tag` field.
|
|
153
|
-
const
|
|
152
|
+
const Comment = Resource.make("Comment", {
|
|
154
153
|
actions: ["read", "write"],
|
|
155
|
-
fields: { id: Schema.String,
|
|
154
|
+
fields: { id: Schema.String, hidden: Schema.Boolean },
|
|
156
155
|
})
|
|
157
|
-
const
|
|
156
|
+
const comment = Comment.make({ id: "c1", hidden: false })
|
|
158
157
|
```
|
|
159
158
|
|
|
160
159
|
A resource may have a path. The path puts the resource in a tree, so one rule
|
|
161
160
|
can cover a part of the tree:
|
|
162
161
|
|
|
163
162
|
```ts
|
|
164
|
-
class
|
|
163
|
+
class Document extends Resource.Class<Document>()("Document", {
|
|
165
164
|
actions: ["read", "update"],
|
|
166
165
|
fields: {
|
|
167
166
|
id: Schema.String,
|
|
168
167
|
organizationId: Schema.String,
|
|
169
|
-
|
|
168
|
+
teamId: Schema.NullOr(Schema.String),
|
|
170
169
|
},
|
|
171
|
-
path: (f) => ["org", f.organizationId, "
|
|
170
|
+
path: (f) => ["org", f.organizationId, "team", f.teamId, "doc", f.id],
|
|
172
171
|
}) {}
|
|
173
172
|
|
|
174
173
|
// Every resource of the organization of the principal.
|
|
@@ -183,9 +182,12 @@ _.allow("read", "*", {
|
|
|
183
182
|
and the resource may be lists.
|
|
184
183
|
- The action `"*"` means every action. The resource `"*"` means every resource.
|
|
185
184
|
- `when` is the condition. It gets a builder with the types of the resource.
|
|
186
|
-
- `fields` makes the rule about some fields only.
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
- `fields` makes the rule about some fields only. A field name covers the fields
|
|
186
|
+
below it too, so `settings` covers `settings.model`. In a pattern, `*` stands
|
|
187
|
+
for any text inside one part of a name, as in `secret*`. `**` stands for any
|
|
188
|
+
number of parts, also none, and it must be a whole part: `**.secret` covers
|
|
189
|
+
`secret` and `settings.secret`. A pattern that matches no field of the
|
|
190
|
+
resources of the rule is an error, so a mistake in a name shows at once.
|
|
189
191
|
- `reason`, `id` and `meta` are kept with the rule. `meta` is extra data, such
|
|
190
192
|
as the scope of a grant. Give its Schema to `Domain.make({ meta })`.
|
|
191
193
|
|
|
@@ -193,11 +195,23 @@ An allow rule with fields covers a request for the whole value: "may update some
|
|
|
193
195
|
fields" is a yes. This is true only for a kind that has one of these fields. A
|
|
194
196
|
deny rule with fields does not stop a request for the whole value.
|
|
195
197
|
|
|
196
|
-
To know if a kind has a field that a pattern fits, the library
|
|
197
|
-
of the kind: its fields
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
198
|
+
To know if a kind has a field that a pattern fits, the library reads the shape
|
|
199
|
+
of the Schema of the kind: its fields and the keys of its records. A name in a
|
|
200
|
+
pattern fits a key of a record when the Schema of the keys takes it. A part with
|
|
201
|
+
a star fits any key.
|
|
202
|
+
|
|
203
|
+
`Policy.define` checks every rule against the domain when the policy is made. A
|
|
204
|
+
rule with a mistake is a `PolicyError`, with each problem and its path. For a
|
|
205
|
+
name with a mistake, it gives the name that you may mean:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
A rule does not fit the domain: The field "titel" is not a field of every
|
|
209
|
+
resource of the rule. Did you mean "title"?
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
A condition reads only the data fields of a value: an own field that a loop over
|
|
213
|
+
the keys sees. A rule that names a getter of a class is a `PolicyError`. Use a
|
|
214
|
+
predicate for a value that comes from code.
|
|
201
215
|
|
|
202
216
|
## Conditions
|
|
203
217
|
|
|
@@ -216,8 +230,8 @@ The builder in `when` has these functions:
|
|
|
216
230
|
|
|
217
231
|
A missing field of a resource has the value `null`. A field of the principal
|
|
218
232
|
that is `null` or missing is different: a test with it is always false, also
|
|
219
|
-
with `ne`. So a guest with no `userId` never matches a
|
|
220
|
-
test for `null`, use a fixed value, as in `$.eq("
|
|
233
|
+
with `ne`. So a guest with no `userId` never matches a document with no owner.
|
|
234
|
+
To test for `null`, use a fixed value, as in `$.eq("ownerId", null)`.
|
|
221
235
|
|
|
222
236
|
`predicate` is the one way out of plain data. Its test may give a boolean or an
|
|
223
237
|
`Effect`. The error type and the service type of the `Effect` go into the types
|
|
@@ -225,8 +239,13 @@ of the policy and of every check. A policy with a predicate can not become JSON
|
|
|
225
239
|
or a query, and the type system stops you when you try.
|
|
226
240
|
|
|
227
241
|
A predicate also keeps the types of the value and the principal that it reads.
|
|
228
|
-
So a predicate for a
|
|
229
|
-
with the builder: a node made by hand
|
|
242
|
+
So a predicate for a Document can not go into a rule for a Comment. Make
|
|
243
|
+
conditions with the builder: a node made by hand, or a spread of a node, is not
|
|
244
|
+
a condition, in the types or when the code runs.
|
|
245
|
+
|
|
246
|
+
A condition may have at most 100000 nodes and 500 levels, with a shared part
|
|
247
|
+
counted for each place. The builder throws an error for a larger condition. So
|
|
248
|
+
no condition can make a check slow or fill the stack.
|
|
230
249
|
|
|
231
250
|
## Policies
|
|
232
251
|
|
|
@@ -257,11 +276,15 @@ Put policies together:
|
|
|
257
276
|
| `Policy.firstOf([a, b])` | The first policy that allows or denies. |
|
|
258
277
|
| `Policy.ceiling(policy, cap)` | A deny from `cap` always wins. An allow from `cap` does nothing. |
|
|
259
278
|
|
|
260
|
-
A policy tree may have at most 256 levels
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
field path may have at most
|
|
279
|
+
A policy tree may have at most 256 levels and 100000 nodes, with its rules and
|
|
280
|
+
the nodes of their conditions, and a shared part counted for each place. A
|
|
281
|
+
combinator throws an error for a larger tree, and the policy Schema does not
|
|
282
|
+
decode one. The decoder reads at most 1000000 objects and array items, so no
|
|
283
|
+
input can fill the stack or take a very long time. A field path may have at most
|
|
284
|
+
5 parts.
|
|
285
|
+
|
|
286
|
+
A shared part of a tree is decided again for each place, so its predicates run
|
|
287
|
+
each time, and `Policy.explain` lists its rules each time.
|
|
265
288
|
|
|
266
289
|
For example, a user token may never do more than its member, and the platform
|
|
267
290
|
has deny rules for everyone:
|
|
@@ -298,9 +321,7 @@ it gives the same answers and the same failures as `Policy.decide`.
|
|
|
298
321
|
|
|
299
322
|
A deep policy tree may give a very large residual. When it would have more than
|
|
300
323
|
10000 nodes, `Policy.residual` throws an error, and a check in an `Authorizer`
|
|
301
|
-
fails.
|
|
302
|
-
getter of a class: the read may fail when the policy runs, and a smaller
|
|
303
|
-
condition could leave it out.
|
|
324
|
+
fails.
|
|
304
325
|
|
|
305
326
|
### Scopes
|
|
306
327
|
|
|
@@ -341,32 +362,19 @@ list query for the scope:
|
|
|
341
362
|
so a scope with a mistake never gives a "yes".
|
|
342
363
|
- `Some`: the policy allows some values and not others. It gives an example of
|
|
343
364
|
each, `allowed` and `refused`.
|
|
344
|
-
- `Unknown`: the policy has a predicate,
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
value
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
a
|
|
357
|
-
other than finite and integer checks, on a path of required fields of plain
|
|
358
|
-
structs. For another field, such as a class, a record, a union of structs, a
|
|
359
|
-
date or a field of `Schema.declare`, a value may have data that the Schema does
|
|
360
|
-
not show, so the answer is `Unknown`. A check on a kind then says no.
|
|
361
|
-
`Resource.isPlainField` tells if a field is plain. This is true for each rule
|
|
362
|
-
that the request may use, also for a test that a smaller condition leaves out: a
|
|
363
|
-
read of a field that is not plain, such as a getter, may fail in a check.
|
|
364
|
-
|
|
365
|
-
A "yes" also needs a value of the whole kind, so each required field of the kind
|
|
366
|
-
must surely have values, also a field that the condition does not test. A
|
|
367
|
-
required field with a check other than finite and integer checks, such as a
|
|
368
|
-
minimum length, may have no value, so then the answer is `Unknown` in place of
|
|
369
|
-
`All` or `Some`. `Resource.hasValue` tells if a Schema surely has a value.
|
|
365
|
+
- `Unknown`: the policy has a predicate, or the search went past its budget.
|
|
366
|
+
|
|
367
|
+
`Condition.satisfiable` does the search. Each test in a condition compares one
|
|
368
|
+
field with fixed values, so these values cut the values of the field into a few
|
|
369
|
+
parts, and every value in one part gives the same answer. The search tries one
|
|
370
|
+
value from each part, one field at a time.
|
|
371
|
+
|
|
372
|
+
The search reads the shape of the Schema of the resource: the fields, their
|
|
373
|
+
literals, `null` for a nullable or optional field, and finite or integer
|
|
374
|
+
numbers. It does not read other checks, such as a minimum length. A check only
|
|
375
|
+
keeps values out, so the shape holds every real value and more. So `All` and
|
|
376
|
+
`None` are right for every value of the Schema. `Some` means that, by the shape,
|
|
377
|
+
a value may get either answer. A check may still stop one of the examples.
|
|
370
378
|
|
|
371
379
|
"Some" means that some value of the Schema is allowed. It does not mean that
|
|
372
380
|
such a value is in your database. To know that, run the condition as a query.
|
|
@@ -400,7 +408,7 @@ function too. Take care with `null`: in this library `ne` is true when the field
|
|
|
400
408
|
is `null`, but in SQL `x <> 'a'` is not true when `x` is `NULL`. Use
|
|
401
409
|
`IS DISTINCT FROM` or a similar form. Give the values to the database as bound
|
|
402
410
|
parameters. A value from a principal may have a quote in it, and in the SQL text
|
|
403
|
-
it could change the query. `examples/
|
|
411
|
+
it could change the query. `examples/multi-tenant.ts` shows how.
|
|
404
412
|
|
|
405
413
|
## JSON
|
|
406
414
|
|
|
@@ -425,21 +433,27 @@ request, loads its policy, and keeps the policy in a cache.
|
|
|
425
433
|
```ts
|
|
426
434
|
class Identity extends Context.Service<Identity, User>()("app/Identity") {}
|
|
427
435
|
|
|
428
|
-
class Auth extends Authorizer.Service<Auth
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
) {}
|
|
436
|
+
class Auth extends Authorizer.Service<Auth>()("app/Auth", {
|
|
437
|
+
domain: Blog,
|
|
438
|
+
principal: Identity,
|
|
439
|
+
}) {}
|
|
432
440
|
|
|
433
441
|
const AuthLive = Authorizer.layer(Auth, {
|
|
434
|
-
principal: Identity,
|
|
435
442
|
policy: (user) => loadPolicyOf(user),
|
|
436
443
|
cache: { capacity: 1000, timeToLive: "30 seconds" },
|
|
437
444
|
})
|
|
438
445
|
```
|
|
439
446
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
447
|
+
The error type and the service type of every check come from the `principal`
|
|
448
|
+
Effect.
|
|
449
|
+
|
|
450
|
+
The cache key of a principal is the text of its JSON form, from the Schema of
|
|
451
|
+
the principal of the domain. A principal is kept only when the value that the
|
|
452
|
+
Schema decodes from that form is equal to it, so two principals with one key are
|
|
453
|
+
equal. The loader always gets that decoded copy, also with no cache, so it must
|
|
454
|
+
decide by the data of the principal, not by the identity of its objects. Give
|
|
455
|
+
`cache: { key }` to make the key in another way, or `cache: false` to load the
|
|
456
|
+
policy for each check.
|
|
443
457
|
|
|
444
458
|
Use it in a handler:
|
|
445
459
|
|
|
@@ -493,12 +507,11 @@ The errors of a check:
|
|
|
493
507
|
### Deciders
|
|
494
508
|
|
|
495
509
|
When no rule gives an answer, the authorizer asks the decider, if there is one.
|
|
496
|
-
A decider may answer at once, or fail with `Pending` when a person or
|
|
497
|
-
answer later:
|
|
510
|
+
A decider may answer at once, or fail with `Pending` when a person or another
|
|
511
|
+
service must answer later:
|
|
498
512
|
|
|
499
513
|
```ts
|
|
500
514
|
Authorizer.layer(Auth, {
|
|
501
|
-
principal: Identity,
|
|
502
515
|
policy: loadPolicyOf,
|
|
503
516
|
decider: (request) =>
|
|
504
517
|
requests
|
|
@@ -547,9 +560,9 @@ const AuthorizationLive = AbilityHttpApi.layer(Authorization, Auth)
|
|
|
547
560
|
|
|
548
561
|
An act on a part of a kind takes a scope from the path parameters. A list takes
|
|
549
562
|
`lists`: the middleware does not check every value, because the handler gets its
|
|
550
|
-
query
|
|
551
|
-
unless you give `nonEmpty: true`. Then the middleware needs a coverage of
|
|
552
|
-
or `Some`, so `Unknown` fails too:
|
|
563
|
+
query from the same requirement, with the same scope. An empty list is not an
|
|
564
|
+
error, unless you give `nonEmpty: true`. Then the middleware needs a coverage of
|
|
565
|
+
`All` or `Some`, so `Unknown` fails too:
|
|
553
566
|
|
|
554
567
|
```ts
|
|
555
568
|
const create = HttpApiEndpoint.post("create", "/users/:user/posts", {
|
|
@@ -560,13 +573,23 @@ const create = HttpApiEndpoint.post("create", "/users/:user/posts", {
|
|
|
560
573
|
}),
|
|
561
574
|
])
|
|
562
575
|
|
|
576
|
+
const postsOfUser = AbilityHttpApi.lists(Auth, "read", "Post", {
|
|
577
|
+
within: (params) => ($) => $.eq("authorId", params["user"] ?? ""),
|
|
578
|
+
})
|
|
579
|
+
|
|
563
580
|
const list = HttpApiEndpoint.get("list", "/users/:user/posts", {
|
|
564
581
|
params: { user: Schema.String },
|
|
565
|
-
}).annotate(AbilityHttpApi.Required, [
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
582
|
+
}).annotate(AbilityHttpApi.Required, [postsOfUser])
|
|
583
|
+
|
|
584
|
+
const PostsLive = HttpApiBuilder.group(Api, "posts", (handlers) =>
|
|
585
|
+
Effect.map(Auth, (auth) =>
|
|
586
|
+
handlers.handle("list", () =>
|
|
587
|
+
postsOfUser
|
|
588
|
+
.filter(auth)
|
|
589
|
+
.pipe(Effect.flatMap(toSql), Effect.flatMap(Posts.query)),
|
|
590
|
+
),
|
|
591
|
+
),
|
|
592
|
+
)
|
|
570
593
|
```
|
|
571
594
|
|
|
572
595
|
A path parameter may be `undefined`. A scope with `undefined` in it is a defect,
|
|
@@ -580,10 +603,43 @@ group without the middleware is not checked.
|
|
|
580
603
|
one for every other method. `AbilityHttpApi.errors` gives the errors with HTTP
|
|
581
604
|
status codes: 403, 503, and 409.
|
|
582
605
|
|
|
606
|
+
## Limits
|
|
607
|
+
|
|
608
|
+
Each limit keeps a walk, a decoder or a search small, so no input can fill the
|
|
609
|
+
stack or take a very long time. An input past a limit is an error, as in "A
|
|
610
|
+
condition has 100001 nodes. The most is 100000.". In an `Authorizer`, such an
|
|
611
|
+
error is never a "yes". The `Limits` module gives each value.
|
|
612
|
+
|
|
613
|
+
| Limit | Value | What it limits |
|
|
614
|
+
| ---------------- | ------- | ------------------------------------------------------- |
|
|
615
|
+
| `fieldPathParts` | 5 | The parts of a field path, as in `settings.model`. |
|
|
616
|
+
| `patternParts` | 16 | The parts of a field pattern. |
|
|
617
|
+
| `conditionNodes` | 100000 | The nodes of a condition, a shared part for each place. |
|
|
618
|
+
| `conditionDepth` | 500 | The levels of a condition. |
|
|
619
|
+
| `residualNodes` | 10000 | The nodes of a residual condition. |
|
|
620
|
+
| `policyDepth` | 256 | The levels of a policy tree. |
|
|
621
|
+
| `policyNodes` | 100000 | The nodes of a policy tree, with its rules. |
|
|
622
|
+
| `jsonNodes` | 1000000 | The objects and items that a decoder reads. |
|
|
623
|
+
| `catalogFields` | 10000 | The fields of the Schema of a resource. |
|
|
624
|
+
| `searchSteps` | 1000000 | The steps of one search, as in `Policy.coverage`. |
|
|
625
|
+
| `searchFields` | 256 | The fields that one search may test. |
|
|
626
|
+
|
|
583
627
|
## Examples
|
|
584
628
|
|
|
629
|
+
Each example is a small program. Run it with Node.js, which runs `.ts` files
|
|
630
|
+
itself:
|
|
631
|
+
|
|
585
632
|
```sh
|
|
586
633
|
node examples/basic.ts
|
|
587
|
-
node examples/authorizer.ts
|
|
588
|
-
node examples/openbot-rbac.ts
|
|
589
634
|
```
|
|
635
|
+
|
|
636
|
+
| Example | What it shows |
|
|
637
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
638
|
+
| `examples/basic.ts` | A blog: a domain, a policy, field rules and a decision with a reason. |
|
|
639
|
+
| `examples/authorizer.ts` | Team documents: an authorizer service with a cache, `guard`, `filter`, coverage and a decider. |
|
|
640
|
+
| `examples/multi-tenant.ts` | A task tracker for many teams: roles, API tokens, a platform limit, scopes and SQL list queries. |
|
|
641
|
+
| `examples/field-access.ts` | A staff list: rules for some fields, field patterns and `permittedFields`. |
|
|
642
|
+
| `examples/stored-policies.ts` | A help desk: policies kept as JSON, `meta` on rules, checks when a policy loads, and `explain`. |
|
|
643
|
+
| `examples/predicates.ts` | Meeting rooms: a predicate that asks an Effect service, its error type, and `provideContext`. |
|
|
644
|
+
| `examples/algorithms.ts` | File sharing: the four combining algorithms, `any`, `firstOf` and `ceiling`. |
|
|
645
|
+
| `examples/http-api.ts` | A notes API: the `effect/http-api` middleware, one-value checks, list scopes and closed routes. |
|
package/dist/Authorizer.d.ts
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { Condition, Opaque } from "./Condition.js";
|
|
2
2
|
import { Any, CommonAction, Kind, Principal } from "./Domain.js";
|
|
3
|
-
import { Decision } from "./Decision.js";
|
|
4
3
|
import { Coverage, FieldlessTarget, KindRequest, KindTarget, Policy, Request, ResidualReads, Target as Target$1 } from "./Policy.js";
|
|
4
|
+
import { Decision } from "./Decision.js";
|
|
5
|
+
import { Forbidden as Forbidden$1, Pending as Pending$1, PolicyUnavailable as PolicyUnavailable$1 } from "./internal/authorizer.js";
|
|
5
6
|
import * as Context from "effect/Context";
|
|
6
7
|
import * as Effect from "effect/Effect";
|
|
7
8
|
import * as Layer from "effect/Layer";
|
|
8
|
-
import * as Schema from "effect/Schema";
|
|
9
9
|
import * as Duration from "effect/Duration";
|
|
10
10
|
declare namespace Authorizer_d_exports {
|
|
11
|
-
export { AnyServiceClass, Authorizer,
|
|
11
|
+
export { AnyServiceClass, Authorizer, DeciderRequest, Forbidden, GuardOptions, Options, Pending, PolicyUnavailable, Service, ServiceClass, Target, layer };
|
|
12
12
|
}
|
|
13
|
-
declare const Forbidden_base: Schema.Class<Forbidden, Schema.TaggedStruct<"Forbidden", {
|
|
14
|
-
readonly action: Schema.String;
|
|
15
|
-
readonly kind: Schema.String;
|
|
16
|
-
readonly field: Schema.optionalKey<Schema.String>;
|
|
17
|
-
readonly reason: Schema.optionalKey<Schema.String>;
|
|
18
|
-
}>, import("effect/Cause").YieldableError>;
|
|
19
13
|
/**
|
|
20
14
|
* The error when a policy says no.
|
|
21
15
|
*
|
|
@@ -23,10 +17,13 @@ declare const Forbidden_base: Schema.Class<Forbidden, Schema.TaggedStruct<"Forbi
|
|
|
23
17
|
* @category errors
|
|
24
18
|
* @since 0.3.0
|
|
25
19
|
*/
|
|
26
|
-
export declare
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
export declare const Forbidden: typeof Forbidden$1;
|
|
21
|
+
/**
|
|
22
|
+
* @stability stable
|
|
23
|
+
* @category errors
|
|
24
|
+
* @since 0.3.0
|
|
25
|
+
*/
|
|
26
|
+
export type Forbidden = Forbidden$1;
|
|
30
27
|
/**
|
|
31
28
|
* The error when the policy of the principal can not be loaded. The cause is
|
|
32
29
|
* kept, but it is not shown to the client.
|
|
@@ -35,11 +32,13 @@ declare const PolicyUnavailable_base: Schema.Class<PolicyUnavailable, Schema.Tag
|
|
|
35
32
|
* @category errors
|
|
36
33
|
* @since 0.3.0
|
|
37
34
|
*/
|
|
38
|
-
export declare
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
export declare const PolicyUnavailable: typeof PolicyUnavailable$1;
|
|
36
|
+
/**
|
|
37
|
+
* @stability stable
|
|
38
|
+
* @category errors
|
|
39
|
+
* @since 0.3.0
|
|
40
|
+
*/
|
|
41
|
+
export type PolicyUnavailable = PolicyUnavailable$1;
|
|
43
42
|
/**
|
|
44
43
|
* The error when a decider must wait for an answer. `ticket` names the
|
|
45
44
|
* request that waits, so the client can come back for the answer.
|
|
@@ -48,25 +47,13 @@ declare const Pending_base: Schema.Class<Pending, Schema.TaggedStruct<"Pending",
|
|
|
48
47
|
* @category errors
|
|
49
48
|
* @since 0.3.0
|
|
50
49
|
*/
|
|
51
|
-
export declare
|
|
50
|
+
export declare const Pending: typeof Pending$1;
|
|
52
51
|
/**
|
|
53
|
-
* The services and the errors of the principal and the decider of an
|
|
54
|
-
* authorizer.
|
|
55
|
-
*
|
|
56
52
|
* @stability stable
|
|
57
|
-
* @category
|
|
53
|
+
* @category errors
|
|
58
54
|
* @since 0.3.0
|
|
59
55
|
*/
|
|
60
|
-
export
|
|
61
|
-
readonly requires?: unknown;
|
|
62
|
-
readonly error?: unknown;
|
|
63
|
-
}
|
|
64
|
-
type RequiresOf<C> = C extends {
|
|
65
|
-
readonly requires: infer R;
|
|
66
|
-
} ? R : never;
|
|
67
|
-
type ErrorOf<C> = C extends {
|
|
68
|
-
readonly error: infer E;
|
|
69
|
-
} ? E : never;
|
|
56
|
+
export type Pending = Pending$1;
|
|
70
57
|
/**
|
|
71
58
|
* What a check is about: an act on a resource value, or an act on a kind of
|
|
72
59
|
* resource. A check on a kind passes only when the policy allows the act on
|
|
@@ -125,7 +112,7 @@ export interface Authorizer<D extends Any, E, R> {
|
|
|
125
112
|
* The policy of the principal of the current request. Its predicates have
|
|
126
113
|
* the services of the layer. It may have predicates, so it is opaque.
|
|
127
114
|
*/
|
|
128
|
-
readonly policy: Effect.Effect<Policy<D, E
|
|
115
|
+
readonly policy: Effect.Effect<Policy<D, Opaque<E>>, PolicyUnavailable | E, R>;
|
|
129
116
|
/** Gives the decision of the policy. It does not ask the decider. */
|
|
130
117
|
readonly decide: (target: Target$1<D>) => Effect.Effect<Decision, PolicyUnavailable | E, R>;
|
|
131
118
|
/** Succeeds when the principal may do the act, and fails when it may not. */
|
|
@@ -143,13 +130,13 @@ export interface Authorizer<D extends Any, E, R> {
|
|
|
143
130
|
* with `Condition.fold`. It is `False` when the principal may act on no
|
|
144
131
|
* value, so the list is empty: it is not an error.
|
|
145
132
|
*/
|
|
146
|
-
readonly filter: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Condition<
|
|
133
|
+
readonly filter: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Condition<ResidualReads<D, T["kind"]>, Opaque<E>>, PolicyUnavailable | E, R>;
|
|
147
134
|
/**
|
|
148
135
|
* Tells if the principal may act on all, none or some of the values in
|
|
149
136
|
* the scope of the target. See `Policy.coverage`. Use it to show a button
|
|
150
137
|
* only when the answer is not `None`.
|
|
151
138
|
*/
|
|
152
|
-
readonly coverage: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Coverage<
|
|
139
|
+
readonly coverage: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Coverage<ResidualReads<D, T["kind"]>, Opaque<E>>, PolicyUnavailable | E, R>;
|
|
153
140
|
/** Gives the fields of a value that the principal may use. */
|
|
154
141
|
readonly permittedFields: (target: FieldlessTarget<D>) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R>;
|
|
155
142
|
/**
|
|
@@ -168,11 +155,13 @@ export interface Authorizer<D extends Any, E, R> {
|
|
|
168
155
|
*/
|
|
169
156
|
export interface ServiceClass<Self, Id extends string, D extends Any, E, R> extends Context.ServiceClass<Self, Id, Authorizer<D, E, R>> {
|
|
170
157
|
readonly domain: D;
|
|
158
|
+
/** The Effect that gives the principal of the current request. */
|
|
159
|
+
readonly principal: Effect.Effect<Principal<D>, E, R>;
|
|
171
160
|
readonly decide: (target: Target$1<D>) => Effect.Effect<Decision, PolicyUnavailable | E, R | Self>;
|
|
172
161
|
readonly check: (target: Target<D>) => Effect.Effect<void, Forbidden | Pending | PolicyUnavailable | E, R | Self>;
|
|
173
162
|
readonly guard: <const T extends Target$1<D>, E2>(target: T, options: GuardOptions<D, E2, T["resource"]["_tag"]>) => Effect.Effect<void, E2 | Forbidden | Pending | PolicyUnavailable | E, R | Self>;
|
|
174
|
-
readonly filter: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Condition<
|
|
175
|
-
readonly coverage: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Coverage<
|
|
163
|
+
readonly filter: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Condition<ResidualReads<D, T["kind"]>, Opaque<E>>, PolicyUnavailable | E, R | Self>;
|
|
164
|
+
readonly coverage: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Coverage<ResidualReads<D, T["kind"]>, Opaque<E>>, PolicyUnavailable | E, R | Self>;
|
|
176
165
|
readonly permittedFields: (target: FieldlessTarget<D>) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R | Self>;
|
|
177
166
|
}
|
|
178
167
|
/**
|
|
@@ -184,6 +173,7 @@ export interface ServiceClass<Self, Id extends string, D extends Any, E, R> exte
|
|
|
184
173
|
*/
|
|
185
174
|
export type AnyServiceClass = Context.Key<any, any> & {
|
|
186
175
|
readonly domain: Any;
|
|
176
|
+
readonly principal: Effect.Effect<any, any, any>;
|
|
187
177
|
};
|
|
188
178
|
/**
|
|
189
179
|
* Makes an authorizer service class for a domain.
|
|
@@ -192,7 +182,11 @@ export type AnyServiceClass = Context.Key<any, any> & {
|
|
|
192
182
|
* @category constructors
|
|
193
183
|
* @since 0.3.0
|
|
194
184
|
*/
|
|
195
|
-
export declare const Service: <Self
|
|
185
|
+
export declare const Service: <Self>() => <const Id extends string, D extends Any, E = never, R = never>(id: Id, options: {
|
|
186
|
+
readonly domain: D;
|
|
187
|
+
/** Gives the principal of the current request. */
|
|
188
|
+
readonly principal: Effect.Effect<Principal<NoInfer<D>>, E, R>;
|
|
189
|
+
}) => ServiceClass<Self, Id, D, E, R>;
|
|
196
190
|
/**
|
|
197
191
|
* The options of {@link layer}.
|
|
198
192
|
*
|
|
@@ -201,14 +195,12 @@ export declare const Service: <Self, C extends Config = {}>() => <const Id exten
|
|
|
201
195
|
* @since 0.3.0
|
|
202
196
|
*/
|
|
203
197
|
export interface Options<D extends Any, E, R, EL, RL, RP> {
|
|
204
|
-
/** Gives the principal of the current request. */
|
|
205
|
-
readonly principal: Effect.Effect<Principal<D>, E, R>;
|
|
206
198
|
/**
|
|
207
199
|
* Loads the policy of a principal. A failure becomes
|
|
208
200
|
* {@link PolicyUnavailable}. The services of the predicates of the policy
|
|
209
201
|
* are taken when the layer is built.
|
|
210
202
|
*/
|
|
211
|
-
readonly policy: (principal: Principal<D>) => Effect.Effect<Policy<D, E, RP
|
|
203
|
+
readonly policy: (principal: Principal<D>) => Effect.Effect<Policy<D, Opaque<E, RP>>, EL, RL>;
|
|
212
204
|
/**
|
|
213
205
|
* Answers when no rule gives an answer. Without a decider, such a request
|
|
214
206
|
* fails with {@link Forbidden}.
|
|
@@ -218,15 +210,19 @@ export interface Options<D extends Any, E, R, EL, RL, RP> {
|
|
|
218
210
|
* How the policies are kept. The default keeps 1000 policies for 30 seconds.
|
|
219
211
|
* Give `false` to load the policy for each check.
|
|
220
212
|
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
213
|
+
* The key of a principal is the text of its JSON form, from the Schema of
|
|
214
|
+
* the principal of the domain. A principal is kept only when the value that
|
|
215
|
+
* the Schema decodes from that form is `Equal` to it. The loader always
|
|
216
|
+
* gets that decoded copy, also with no cache, so two principals with one
|
|
217
|
+
* key get one policy, and the cache can not change an answer. So the loader
|
|
218
|
+
* must decide by the data of the principal, not by the identity of its
|
|
219
|
+
* objects. A principal with no JSON form is not kept, and the loader gets
|
|
220
|
+
* it as it is. Give `key` to make the key in another way.
|
|
226
221
|
*/
|
|
227
222
|
readonly cache?: {
|
|
228
223
|
readonly capacity?: number;
|
|
229
224
|
readonly timeToLive?: Duration.Input;
|
|
225
|
+
readonly key?: (principal: Principal<D>) => string | undefined;
|
|
230
226
|
} | false;
|
|
231
227
|
}
|
|
232
228
|
/**
|
package/dist/Authorizer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Authorizer.d.ts","names":["Policy.Target","Policy.KindTarget","Policy.Request","Policy.KindRequest","Domain.Kind","Domain.CommonAction","Domain.Any","Domain.Principal","Condition.Opaque","Policy.Policy","Decision.Decision","Policy.ResidualReads","Condition.Condition","Policy.Coverage","Policy.FieldlessTarget"],"sources":["../src/Authorizer.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Authorizer.d.ts","names":["Internal.Forbidden","Internal.PolicyUnavailable","Internal.Pending","Policy.Target","Policy.KindTarget","Policy.Request","Policy.KindRequest","Domain.Kind","Domain.CommonAction","Domain.Any","Domain.Principal","Condition.Opaque","Policy.Policy","Decision.Decision","Policy.ResidualReads","Condition.Condition","Policy.Coverage","Policy.FieldlessTarget"],"sources":["../src/Authorizer.ts"],"mappings":"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"}
|