@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.
Files changed (127) hide show
  1. package/README.md +143 -87
  2. package/dist/Authorizer.d.ts +43 -47
  3. package/dist/Authorizer.d.ts.map +1 -1
  4. package/dist/Authorizer.js +5 -290
  5. package/dist/Authorizer.js.map +1 -1
  6. package/dist/Condition.d.ts +164 -332
  7. package/dist/Condition.d.ts.map +1 -1
  8. package/dist/Condition.js +75 -890
  9. package/dist/Condition.js.map +1 -1
  10. package/dist/Decision.d.ts +13 -13
  11. package/dist/Decision.d.ts.map +1 -1
  12. package/dist/Decision.js.map +1 -1
  13. package/dist/Domain.d.ts +10 -21
  14. package/dist/Domain.d.ts.map +1 -1
  15. package/dist/Domain.js +4 -62
  16. package/dist/Domain.js.map +1 -1
  17. package/dist/HttpApi.d.ts +31 -7
  18. package/dist/HttpApi.d.ts.map +1 -1
  19. package/dist/HttpApi.js +28 -11
  20. package/dist/HttpApi.js.map +1 -1
  21. package/dist/Limits.d.ts +114 -0
  22. package/dist/Limits.d.ts.map +1 -0
  23. package/dist/Limits.js +132 -0
  24. package/dist/Limits.js.map +1 -0
  25. package/dist/Policy.d.ts +274 -272
  26. package/dist/Policy.d.ts.map +1 -1
  27. package/dist/Policy.js +71 -1180
  28. package/dist/Policy.js.map +1 -1
  29. package/dist/Resource.d.ts +8 -181
  30. package/dist/Resource.d.ts.map +1 -1
  31. package/dist/Resource.js +8 -807
  32. package/dist/Resource.js.map +1 -1
  33. package/dist/index.d.ts +3 -4
  34. package/dist/index.js +4 -5
  35. package/dist/internal/algebra.js +267 -0
  36. package/dist/internal/algebra.js.map +1 -0
  37. package/dist/internal/analysis.js +245 -0
  38. package/dist/internal/analysis.js.map +1 -0
  39. package/dist/internal/authorizer.d.ts +30 -0
  40. package/dist/internal/authorizer.d.ts.map +1 -0
  41. package/dist/internal/authorizer.js +221 -0
  42. package/dist/internal/authorizer.js.map +1 -0
  43. package/dist/internal/bind.js +286 -0
  44. package/dist/internal/bind.js.map +1 -0
  45. package/dist/internal/catalog.d.ts +56 -0
  46. package/dist/internal/catalog.d.ts.map +1 -0
  47. package/dist/internal/catalog.js +292 -0
  48. package/dist/internal/catalog.js.map +1 -0
  49. package/dist/internal/decide.js +166 -0
  50. package/dist/internal/decide.js.map +1 -0
  51. package/dist/internal/domain.js +32 -0
  52. package/dist/internal/domain.js.map +1 -0
  53. package/dist/internal/json.js +134 -0
  54. package/dist/internal/json.js.map +1 -0
  55. package/dist/internal/limits.d.ts +14 -0
  56. package/dist/internal/limits.d.ts.map +1 -0
  57. package/dist/internal/limits.js +43 -0
  58. package/dist/internal/limits.js.map +1 -0
  59. package/dist/internal/nodes.d.ts +121 -0
  60. package/dist/internal/nodes.d.ts.map +1 -0
  61. package/dist/internal/nodes.js +277 -0
  62. package/dist/internal/nodes.js.map +1 -0
  63. package/dist/internal/patterns.js +110 -0
  64. package/dist/internal/patterns.js.map +1 -0
  65. package/dist/internal/policy-json.js +255 -0
  66. package/dist/internal/policy-json.js.map +1 -0
  67. package/dist/internal/policy-nodes.d.ts +61 -0
  68. package/dist/internal/policy-nodes.d.ts.map +1 -0
  69. package/dist/internal/policy-nodes.js +146 -0
  70. package/dist/internal/policy-nodes.js.map +1 -0
  71. package/dist/internal/policy.d.ts +42 -0
  72. package/dist/internal/policy.d.ts.map +1 -0
  73. package/dist/internal/policy.js +124 -0
  74. package/dist/internal/policy.js.map +1 -0
  75. package/dist/internal/read.js +39 -0
  76. package/dist/internal/read.js.map +1 -0
  77. package/dist/internal/resource.js +115 -0
  78. package/dist/internal/resource.js.map +1 -0
  79. package/dist/internal/rule.d.ts +26 -0
  80. package/dist/internal/rule.d.ts.map +1 -0
  81. package/dist/internal/rule.js +93 -0
  82. package/dist/internal/rule.js.map +1 -0
  83. package/dist/internal/search.d.ts +22 -0
  84. package/dist/internal/search.d.ts.map +1 -0
  85. package/dist/internal/search.js +274 -0
  86. package/dist/internal/search.js.map +1 -0
  87. package/dist/internal/suggest.js +46 -0
  88. package/dist/internal/suggest.js.map +1 -0
  89. package/package.json +4 -5
  90. package/src/Authorizer.ts +69 -585
  91. package/src/Condition.ts +339 -2095
  92. package/src/Decision.ts +6 -5
  93. package/src/Domain.ts +13 -70
  94. package/src/HttpApi.ts +75 -13
  95. package/src/Limits.ts +132 -0
  96. package/src/Policy.ts +386 -2772
  97. package/src/Resource.ts +14 -1555
  98. package/src/index.ts +3 -9
  99. package/src/internal/algebra.ts +482 -0
  100. package/src/internal/analysis.ts +478 -0
  101. package/src/internal/authorizer.ts +418 -0
  102. package/src/internal/bind.ts +596 -0
  103. package/src/internal/catalog.ts +534 -0
  104. package/src/internal/decide.ts +402 -0
  105. package/src/internal/domain.ts +66 -0
  106. package/src/internal/json.ts +260 -0
  107. package/src/internal/limits.ts +78 -0
  108. package/src/internal/nodes.ts +404 -0
  109. package/src/internal/patterns.ts +211 -0
  110. package/src/internal/policy-json.ts +484 -0
  111. package/src/internal/policy-nodes.ts +207 -0
  112. package/src/internal/policy.ts +264 -0
  113. package/src/internal/read.ts +55 -0
  114. package/src/internal/resource.ts +180 -0
  115. package/src/internal/rule.ts +181 -0
  116. package/src/internal/search.ts +541 -0
  117. package/src/internal/suggest.ts +104 -0
  118. package/dist/Principal.d.ts +0 -35
  119. package/dist/Principal.d.ts.map +0 -1
  120. package/dist/Principal.js +0 -37
  121. package/dist/Principal.js.map +0 -1
  122. package/dist/Rule.d.ts +0 -275
  123. package/dist/Rule.d.ts.map +0 -1
  124. package/dist/Rule.js +0 -257
  125. package/dist/Rule.js.map +0 -1
  126. package/src/Principal.ts +0 -41
  127. 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, a user token, or a Bot.
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
- | `Principal` | The Schema of the one who asks. |
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
- | `Rule` | An allow rule or a deny rule. |
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, Principal, Resource } from "@nmnmcc/ability"
65
+ import { Decision, Domain, Policy, Resource } from "@nmnmcc/ability"
67
66
 
68
- class User extends Principal.Class<User>("User")({
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 Bot extends Resource.Class<Bot>()("Bot", {
142
+ class Document extends Resource.Class<Document>()("Document", {
144
143
  actions: ["read", "update"],
145
- fields: { id: Schema.String, ownerUserId: Schema.NullOr(Schema.String) },
144
+ fields: { id: Schema.String, ownerId: Schema.NullOr(Schema.String) },
146
145
  }) {
147
- get shared() {
148
- return this.ownerUserId === null
146
+ get orphaned() {
147
+ return this.ownerId === null
149
148
  }
150
149
  }
151
150
 
152
151
  // Plain objects with a `_tag` field.
153
- const Trigger = Resource.make("Trigger", {
152
+ const Comment = Resource.make("Comment", {
154
153
  actions: ["read", "write"],
155
- fields: { id: Schema.String, enabled: Schema.Boolean },
154
+ fields: { id: Schema.String, hidden: Schema.Boolean },
156
155
  })
157
- const trigger = Trigger.make({ id: "t1", enabled: true })
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 Bot extends Resource.Class<Bot>()("Bot", {
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
- ownerUserId: Schema.NullOr(Schema.String),
168
+ teamId: Schema.NullOr(Schema.String),
170
169
  },
171
- path: (f) => ["org", f.organizationId, "user", f.ownerUserId, "bot", f.id],
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. `*` stands for any part of one
187
- field name, and `**` for any number of parts, also none: `settings.*` covers
188
- `settings.model`, and `**.secret` covers `secret` and `settings.secret`.
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 walks the Schema
197
- of the kind: its fields, the keys of its records, and the getters of its
198
- classes. For some key Schemas it can only try sample keys, so it may not find a
199
- field that is there. Then the answer for the whole value is "no", never a wrong
200
- "yes".
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 Bot with no owner. To
220
- test for `null`, use a fixed value, as in `$.eq("ownerUserId", null)`.
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 Bot can not go into a rule for a Trigger. Make conditions
229
- with the builder: a node made by hand is not a condition in the types.
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. A combinator throws an error for a
261
- deeper tree, and the policy Schema does not decode one. A deeper tree could fill
262
- the stack of a check. The JSON form of a policy may have at most 1000 levels of
263
- objects and arrays, so a deep input fails to decode before it fills the stack. A
264
- field path may have at most 256 parts.
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. It throws an error too when a rule that the request may use reads a
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, a condition tests a field that is not a
345
- plain scalar, or the search went past its budget.
346
-
347
- The answer is exact. Each test in a condition compares one field with fixed
348
- values, so these values cut the values of the field into a few parts, and every
349
- value in one part gives the same answer. `Condition.satisfiable` tries one value
350
- from each part, one field at a time. It takes the values of each field from the
351
- Schema of the resource: its literals, `null` for a nullable or optional field,
352
- and finite or integer numbers. It does not know other Schema checks, such as a
353
- minimum length, so a field with such a check is not plain (see below).
354
-
355
- The search answers only for plain scalar fields: a string, a number, a boolean,
356
- a literal, `null`, or an optional field or a union of these, with no checks
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/openbot-rbac.ts` shows how.
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, { requires: Identity }>()(
429
- "app/Auth",
430
- Blog,
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
- Two principals with the same data get one policy from the cache. So the loader
441
- must decide by the data of the principal only. When it tests that an object of
442
- the principal is a given object, as `role === admin` does, give `cache: false`.
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 a Bot must
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 with `Auth.filter`, with the same scope. An empty list is not an error,
551
- unless you give `nonEmpty: true`. Then the middleware needs a coverage of `All`
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
- AbilityHttpApi.lists(Auth, "read", "Post", {
567
- within: (params) => ($) => $.eq("authorId", params["user"] ?? ""),
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. |
@@ -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, Config, DeciderRequest, Forbidden, GuardOptions, Options, Pending, PolicyUnavailable, Service, ServiceClass, Target, layer };
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 class Forbidden extends Forbidden_base {}
27
- declare const PolicyUnavailable_base: Schema.Class<PolicyUnavailable, Schema.TaggedStruct<"PolicyUnavailable", {
28
- readonly cause: Schema.Defect;
29
- }>, import("effect/Cause").YieldableError>;
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 class PolicyUnavailable extends PolicyUnavailable_base {}
39
- declare const Pending_base: Schema.Class<Pending, Schema.TaggedStruct<"Pending", {
40
- readonly ticket: Schema.String;
41
- readonly reason: Schema.optionalKey<Schema.String>;
42
- }>, import("effect/Cause").YieldableError>;
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 class Pending extends Pending_base {}
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 models
53
+ * @category errors
58
54
  * @since 0.3.0
59
55
  */
60
- export interface Config {
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, never, Opaque>, PolicyUnavailable | E, R>;
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<E, never, Opaque, ResidualReads<D, T["kind"]>>, PolicyUnavailable | E, R>;
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<E, never, Opaque, ResidualReads<D, T["kind"]>>, PolicyUnavailable | E, R>;
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<E, never, Opaque, ResidualReads<D, T["kind"]>>, PolicyUnavailable | E, R | Self>;
175
- readonly coverage: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Coverage<E, never, Opaque, ResidualReads<D, T["kind"]>>, PolicyUnavailable | E, R | Self>;
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, C extends Config = {}>() => <const Id extends string, D extends Any>(id: Id, domain: D) => ServiceClass<Self, Id, D, ErrorOf<C>, RequiresOf<C>>;
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, any>, EL, RL>;
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
- * Two principals with the same data get one policy from the cache. So the
222
- * loader must decide by the data of the principal only. When it tests that
223
- * an object of the principal is a given object, as `role === admin` does,
224
- * give `false`. A principal with objects in an array, a set or the keys of
225
- * a map is not kept, because such tests are usual there.
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
  /**
@@ -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":"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"}
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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"}