@nmnmcc/ability 1.0.1 → 2.0.1

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 +112 -69
  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 +25 -8
  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 +276 -269
  26. package/dist/Policy.d.ts.map +1 -1
  27. package/dist/Policy.js +73 -1177
  28. package/dist/Policy.js.map +1 -1
  29. package/dist/Resource.d.ts +4 -177
  30. package/dist/Resource.d.ts.map +1 -1
  31. package/dist/Resource.js +4 -803
  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 +311 -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 +265 -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 +103 -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 +3 -4
  90. package/src/Authorizer.ts +67 -583
  91. package/src/Condition.ts +339 -2095
  92. package/src/Decision.ts +6 -5
  93. package/src/Domain.ts +12 -69
  94. package/src/HttpApi.ts +72 -10
  95. package/src/Limits.ts +132 -0
  96. package/src/Policy.ts +391 -2772
  97. package/src/Resource.ts +7 -1548
  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 +648 -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 +487 -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 +196 -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
@@ -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
 
@@ -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
 
@@ -226,8 +240,12 @@ 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
242
  So a predicate for a Document can not go into a rule for a Comment. Make
229
- conditions with the builder: a node made by hand is not a condition in the
230
- types.
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.
231
249
 
232
250
  ## Policies
233
251
 
@@ -258,11 +276,15 @@ Put policies together:
258
276
  | `Policy.firstOf([a, b])` | The first policy that allows or denies. |
259
277
  | `Policy.ceiling(policy, cap)` | A deny from `cap` always wins. An allow from `cap` does nothing. |
260
278
 
261
- A policy tree may have at most 256 levels. A combinator throws an error for a
262
- deeper tree, and the policy Schema does not decode one. A deeper tree could fill
263
- the stack of a check. The JSON form of a policy may have at most 1000 levels of
264
- objects and arrays, so a deep input fails to decode before it fills the stack. A
265
- 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.
266
288
 
267
289
  For example, a user token may never do more than its member, and the platform
268
290
  has deny rules for everyone:
@@ -299,9 +321,7 @@ it gives the same answers and the same failures as `Policy.decide`.
299
321
 
300
322
  A deep policy tree may give a very large residual. When it would have more than
301
323
  10000 nodes, `Policy.residual` throws an error, and a check in an `Authorizer`
302
- fails. It throws an error too when a rule that the request may use reads a
303
- getter of a class: the read may fail when the policy runs, and a smaller
304
- condition could leave it out.
324
+ fails.
305
325
 
306
326
  ### Scopes
307
327
 
@@ -342,32 +362,19 @@ list query for the scope:
342
362
  so a scope with a mistake never gives a "yes".
343
363
  - `Some`: the policy allows some values and not others. It gives an example of
344
364
  each, `allowed` and `refused`.
345
- - `Unknown`: the policy has a predicate, a condition tests a field that is not a
346
- plain scalar, or the search went past its budget.
347
-
348
- The answer is exact. Each test in a condition compares one field with fixed
349
- values, so these values cut the values of the field into a few parts, and every
350
- value in one part gives the same answer. `Condition.satisfiable` tries one value
351
- from each part, one field at a time. It takes the values of each field from the
352
- Schema of the resource: its literals, `null` for a nullable or optional field,
353
- and finite or integer numbers. It does not know other Schema checks, such as a
354
- minimum length, so a field with such a check is not plain (see below).
355
-
356
- The search answers only for plain scalar fields: a string, a number, a boolean,
357
- a literal, `null`, or an optional field or a union of these, with no checks
358
- other than finite and integer checks, on a path of required fields of plain
359
- structs. For another field, such as a class, a record, a union of structs, a
360
- date or a field of `Schema.declare`, a value may have data that the Schema does
361
- not show, so the answer is `Unknown`. A check on a kind then says no.
362
- `Resource.isPlainField` tells if a field is plain. This is true for each rule
363
- that the request may use, also for a test that a smaller condition leaves out: a
364
- read of a field that is not plain, such as a getter, may fail in a check.
365
-
366
- A "yes" also needs a value of the whole kind, so each required field of the kind
367
- must surely have values, also a field that the condition does not test. A
368
- required field with a check other than finite and integer checks, such as a
369
- minimum length, may have no value, so then the answer is `Unknown` in place of
370
- `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.
371
378
 
372
379
  "Some" means that some value of the Schema is allowed. It does not mean that
373
380
  such a value is in your database. To know that, run the condition as a query.
@@ -426,21 +433,27 @@ request, loads its policy, and keeps the policy in a cache.
426
433
  ```ts
427
434
  class Identity extends Context.Service<Identity, User>()("app/Identity") {}
428
435
 
429
- class Auth extends Authorizer.Service<Auth, { requires: Identity }>()(
430
- "app/Auth",
431
- Blog,
432
- ) {}
436
+ class Auth extends Authorizer.Service<Auth>()("app/Auth", {
437
+ domain: Blog,
438
+ principal: Identity,
439
+ }) {}
433
440
 
434
441
  const AuthLive = Authorizer.layer(Auth, {
435
- principal: Identity,
436
442
  policy: (user) => loadPolicyOf(user),
437
443
  cache: { capacity: 1000, timeToLive: "30 seconds" },
438
444
  })
439
445
  ```
440
446
 
441
- Two principals with the same data get one policy from the cache. So the loader
442
- must decide by the data of the principal only. When it tests that an object of
443
- 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.
444
457
 
445
458
  Use it in a handler:
446
459
 
@@ -499,7 +512,6 @@ service must answer later:
499
512
 
500
513
  ```ts
501
514
  Authorizer.layer(Auth, {
502
- principal: Identity,
503
515
  policy: loadPolicyOf,
504
516
  decider: (request) =>
505
517
  requests
@@ -548,9 +560,9 @@ const AuthorizationLive = AbilityHttpApi.layer(Authorization, Auth)
548
560
 
549
561
  An act on a part of a kind takes a scope from the path parameters. A list takes
550
562
  `lists`: the middleware does not check every value, because the handler gets its
551
- query with `Auth.filter`, with the same scope. An empty list is not an error,
552
- unless you give `nonEmpty: true`. Then the middleware needs a coverage of `All`
553
- 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:
554
566
 
555
567
  ```ts
556
568
  const create = HttpApiEndpoint.post("create", "/users/:user/posts", {
@@ -561,13 +573,23 @@ const create = HttpApiEndpoint.post("create", "/users/:user/posts", {
561
573
  }),
562
574
  ])
563
575
 
576
+ const postsOfUser = AbilityHttpApi.lists(Auth, "read", "Post", {
577
+ within: (params) => ($) => $.eq("authorId", params["user"] ?? ""),
578
+ })
579
+
564
580
  const list = HttpApiEndpoint.get("list", "/users/:user/posts", {
565
581
  params: { user: Schema.String },
566
- }).annotate(AbilityHttpApi.Required, [
567
- AbilityHttpApi.lists(Auth, "read", "Post", {
568
- within: (params) => ($) => $.eq("authorId", params["user"] ?? ""),
569
- }),
570
- ])
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
+ )
571
593
  ```
572
594
 
573
595
  A path parameter may be `undefined`. A scope with `undefined` in it is a defect,
@@ -581,6 +603,27 @@ group without the middleware is not checked.
581
603
  one for every other method. `AbilityHttpApi.errors` gives the errors with HTTP
582
604
  status codes: 403, 503, and 409.
583
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
+
584
627
  ## Examples
585
628
 
586
629
  Each example is a small program. Run it with Node.js, which runs `.ts` files
@@ -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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"}