@nmnmcc/ability 0.3.0 → 1.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 (63) hide show
  1. package/README.md +169 -14
  2. package/dist/Authorizer.d.ts +64 -39
  3. package/dist/Authorizer.d.ts.map +1 -1
  4. package/dist/Authorizer.js +291 -6
  5. package/dist/Authorizer.js.map +1 -1
  6. package/dist/Condition.d.ts +444 -65
  7. package/dist/Condition.d.ts.map +1 -1
  8. package/dist/Condition.js +1007 -25
  9. package/dist/Condition.js.map +1 -1
  10. package/dist/Domain.d.ts +27 -17
  11. package/dist/Domain.d.ts.map +1 -1
  12. package/dist/Domain.js +52 -10
  13. package/dist/Domain.js.map +1 -1
  14. package/dist/HttpApi.d.ts +84 -40
  15. package/dist/HttpApi.d.ts.map +1 -1
  16. package/dist/HttpApi.js +101 -11
  17. package/dist/HttpApi.js.map +1 -1
  18. package/dist/Policy.d.ts +311 -53
  19. package/dist/Policy.d.ts.map +1 -1
  20. package/dist/Policy.js +1212 -35
  21. package/dist/Policy.js.map +1 -1
  22. package/dist/Resource.d.ts +167 -16
  23. package/dist/Resource.d.ts.map +1 -1
  24. package/dist/Resource.js +784 -11
  25. package/dist/Resource.js.map +1 -1
  26. package/dist/Rule.d.ts +113 -27
  27. package/dist/Rule.d.ts.map +1 -1
  28. package/dist/Rule.js +217 -10
  29. package/dist/Rule.js.map +1 -1
  30. package/dist/index.js +4 -4
  31. package/package.json +1 -1
  32. package/src/Authorizer.ts +623 -59
  33. package/src/Condition.ts +2403 -112
  34. package/src/Domain.ts +79 -22
  35. package/src/HttpApi.ts +223 -23
  36. package/src/Policy.ts +2833 -93
  37. package/src/Resource.ts +1537 -22
  38. package/src/Rule.ts +461 -43
  39. package/dist/internal/authorizer.d.ts +0 -28
  40. package/dist/internal/authorizer.d.ts.map +0 -1
  41. package/dist/internal/authorizer.js +0 -153
  42. package/dist/internal/authorizer.js.map +0 -1
  43. package/dist/internal/condition.d.ts +0 -11
  44. package/dist/internal/condition.d.ts.map +0 -1
  45. package/dist/internal/condition.js +0 -393
  46. package/dist/internal/condition.js.map +0 -1
  47. package/dist/internal/domain.js +0 -34
  48. package/dist/internal/domain.js.map +0 -1
  49. package/dist/internal/httpApi.js +0 -41
  50. package/dist/internal/httpApi.js.map +0 -1
  51. package/dist/internal/policy.js +0 -524
  52. package/dist/internal/policy.js.map +0 -1
  53. package/dist/internal/resource.js +0 -66
  54. package/dist/internal/resource.js.map +0 -1
  55. package/dist/internal/rule.js +0 -131
  56. package/dist/internal/rule.js.map +0 -1
  57. package/src/internal/authorizer.ts +0 -271
  58. package/src/internal/condition.ts +0 -607
  59. package/src/internal/domain.ts +0 -56
  60. package/src/internal/httpApi.ts +0 -84
  61. package/src/internal/policy.ts +0 -815
  62. package/src/internal/resource.ts +0 -98
  63. package/src/internal/rule.ts +0 -203
package/README.md CHANGED
@@ -184,14 +184,20 @@ _.allow("read", "*", {
184
184
  - The action `"*"` means every action. The resource `"*"` means every resource.
185
185
  - `when` is the condition. It gets a builder with the types of the resource.
186
186
  - `fields` makes the rule about some fields only. `*` stands for any part of one
187
- field name, and `**` for any number of parts: `settings.*` covers
188
- `settings.model`.
187
+ field name, and `**` for any number of parts, also none: `settings.*` covers
188
+ `settings.model`, and `**.secret` covers `secret` and `settings.secret`.
189
189
  - `reason`, `id` and `meta` are kept with the rule. `meta` is extra data, such
190
190
  as the scope of a grant. Give its Schema to `Domain.make({ meta })`.
191
191
 
192
192
  An allow rule with fields covers a request for the whole value: "may update some
193
- fields" is a yes. A deny rule with fields does not stop a request for the whole
194
- value.
193
+ fields" is a yes. This is true only for a kind that has one of these fields. A
194
+ deny rule with fields does not stop a request for the whole value.
195
+
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".
195
201
 
196
202
  ## Conditions
197
203
 
@@ -218,6 +224,10 @@ test for `null`, use a fixed value, as in `$.eq("ownerUserId", null)`.
218
224
  of the policy and of every check. A policy with a predicate can not become JSON
219
225
  or a query, and the type system stops you when you try.
220
226
 
227
+ 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.
230
+
221
231
  ## Policies
222
232
 
223
233
  A list of rules has a combining algorithm. It decides which rule gives the
@@ -247,6 +257,12 @@ Put policies together:
247
257
  | `Policy.firstOf([a, b])` | The first policy that allows or denies. |
248
258
  | `Policy.ceiling(policy, cap)` | A deny from `cap` always wins. An allow from `cap` does nothing. |
249
259
 
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.
265
+
250
266
  For example, a user token may never do more than its member, and the platform
251
267
  has deny rules for everyone:
252
268
 
@@ -268,6 +284,7 @@ authorizer with a decider asks the decider first.
268
284
  | `Policy.decideSync` | The same, at once. Only for policies with no predicates. |
269
285
  | `Policy.explain` | The decision and the rules that were tested, for an audit log. |
270
286
  | `Policy.residual` | The condition on resource values that the policy allows. |
287
+ | `Policy.coverage` | If the policy allows all, none or some of a part of a kind. |
271
288
  | `Policy.permittedFields` | The fields of a value that the policy lets a request use. |
272
289
 
273
290
  `Policy.residual` puts the principal into every rule and gives a condition on
@@ -275,6 +292,85 @@ the resource fields only. It is `True` when the policy allows every value of the
275
292
  kind, and `False` when it allows none. Use it to ask "may this principal do this
276
293
  act on some value of this kind?", or to make a database query.
277
294
 
295
+ When a rule that the request may use has a predicate, the residual is one
296
+ predicate with the name `"policy"`. It runs the whole policy for each value, so
297
+ it gives the same answers and the same failures as `Policy.decide`.
298
+
299
+ A deep policy tree may give a very large residual. When it would have more than
300
+ 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.
304
+
305
+ ### Scopes
306
+
307
+ A scope is a part of a kind, such as the posts of one writer. Give it as
308
+ `within`, with the same builder as the condition of a rule. A scope is plain
309
+ data: it may not have a predicate.
310
+
311
+ There are three questions about a scope. Each one is a question about two sets:
312
+ the values in the scope, and the values that the policy allows.
313
+
314
+ | Question | Sets | Function |
315
+ | -------------------------------------- | ------------------- | ------------------------------- |
316
+ | May the principal act on every value? | scope ⊆ allowed | `Policy.coverage` is `All` |
317
+ | Which values may the principal act on? | scope ∩ allowed | `Policy.residual` |
318
+ | May the principal act on some value? | scope ∩ allowed ≠ ∅ | `Policy.coverage` is not `None` |
319
+
320
+ ```ts
321
+ const ownPosts = {
322
+ principal: writer,
323
+ action: "update",
324
+ kind: "Post",
325
+ within: ($) => $.eq("authorId", $.principal("id")),
326
+ } satisfies Policy.KindRequest<typeof Blog>
327
+
328
+ Policy.coverage(policy, ownPosts)._tag // "All"
329
+ Policy.residual(policy, ownPosts) // authorId = "u1"
330
+ ```
331
+
332
+ Use "every value" for an act on a value that does not exist yet. A new post has
333
+ no id, but its author is known, so a check on the scope of its author tells if
334
+ the writer may make it.
335
+
336
+ `Policy.coverage` gives one of four answers. Each one has `condition`, the exact
337
+ list query for the scope:
338
+
339
+ - `All`: the policy allows every value in the scope.
340
+ - `None`: the policy allows no value in the scope. An empty scope is `None` too,
341
+ so a scope with a mistake never gives a "yes".
342
+ - `Some`: the policy allows some values and not others. It gives an example of
343
+ 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.
370
+
371
+ "Some" means that some value of the Schema is allowed. It does not mean that
372
+ such a value is in your database. To know that, run the condition as a query.
373
+
278
374
  ## Queries
279
375
 
280
376
  Change a residual condition into a query with `Condition.fold`. You give one
@@ -292,15 +388,19 @@ const toSql = Condition.fold<string>({
292
388
  not: (part) => `NOT ${part}`,
293
389
  })
294
390
 
295
- const where =
296
- yield *
297
- toSql(Policy.residual(policy, { principal, action: "read", kind: "Post" }))
391
+ const program = Effect.gen(function* () {
392
+ const where = yield* toSql(
393
+ Policy.residual(policy, { principal, action: "read", kind: "Post" }),
394
+ )
395
+ })
298
396
  ```
299
397
 
300
398
  `fold` fails with `Untranslatable` on a predicate, unless you give a `predicate`
301
399
  function too. Take care with `null`: in this library `ne` is true when the field
302
400
  is `null`, but in SQL `x <> 'a'` is not true when `x` is `NULL`. Use
303
- `IS DISTINCT FROM` or a similar form.
401
+ `IS DISTINCT FROM` or a similar form. Give the values to the database as bound
402
+ 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.
304
404
 
305
405
  ## JSON
306
406
 
@@ -337,19 +437,32 @@ const AuthLive = Authorizer.layer(Auth, {
337
437
  })
338
438
  ```
339
439
 
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`.
443
+
340
444
  Use it in a handler:
341
445
 
342
446
  ```ts
343
- yield * Auth.check({ action: "update", resource: post })
344
- yield * Auth.check({ action: "read", kind: "Post" })
345
- yield *
346
- Auth.guard(
447
+ const handler = Effect.gen(function* () {
448
+ yield* Auth.check({ action: "update", resource: post })
449
+ yield* Auth.check({ action: "read", kind: "Post" })
450
+ yield* Auth.guard(
347
451
  { action: "update", resource: post },
348
452
  {
349
453
  hide: () => PostNotFound.make({ id: post.id }),
350
454
  },
351
455
  )
352
- const where = yield * Auth.filter({ action: "read", kind: "Post" })
456
+ const where = yield* Auth.filter({ action: "read", kind: "Post" })
457
+ // A new post of the writer: every post of this author may be made.
458
+ yield* Auth.check({
459
+ action: "update",
460
+ kind: "Post",
461
+ within: ($) => $.eq("authorId", $.principal("id")),
462
+ })
463
+ const covered = yield* Auth.coverage({ action: "delete", kind: "Post" })
464
+ const showDeleteButton = covered._tag !== "None"
465
+ })
353
466
  ```
354
467
 
355
468
  | Function | What it does |
@@ -357,12 +470,18 @@ const where = yield * Auth.filter({ action: "read", kind: "Post" })
357
470
  | `check` | Succeeds with `void`, or fails with `Forbidden`. |
358
471
  | `guard` | Fails with your error when the principal may not read the value, then checks. |
359
472
  | `filter` | Gives the residual condition, for a list query. |
473
+ | `coverage` | Tells if the principal may act on all, none or some values in a scope. |
360
474
  | `decide` | Gives the decision. It does not fail on a "no". |
361
475
  | `permittedFields` | Gives the fields that the principal may use. |
362
476
  | `invalidate` | Removes a cached policy, after a change to the rules of a principal. |
363
477
 
364
478
  A check on a kind (`{ action, kind }`) passes only when the policy allows the
365
- act on every value of the kind.
479
+ act on every value of the kind. With `within`, it passes only when the policy
480
+ allows the act on every value in the scope, and the scope is not empty. The
481
+ decider is asked only when no value in the scope is denied.
482
+
483
+ `filter` gives `False` when the principal may act on no value. Then the list is
484
+ empty, and that is not an error.
366
485
 
367
486
  The errors of a check:
368
487
 
@@ -418,9 +537,45 @@ const health = HttpApiEndpoint.get("health", "/health").annotate(
418
537
  [],
419
538
  )
420
539
 
540
+ // The checks run only in a group that uses the middleware.
541
+ const posts = HttpApiGroup.make("posts")
542
+ .add(update, health)
543
+ .middleware(Authorization)
544
+
421
545
  const AuthorizationLive = AbilityHttpApi.layer(Authorization, Auth)
422
546
  ```
423
547
 
548
+ An act on a part of a kind takes a scope from the path parameters. A list takes
549
+ `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:
553
+
554
+ ```ts
555
+ const create = HttpApiEndpoint.post("create", "/users/:user/posts", {
556
+ params: { user: Schema.String },
557
+ }).annotate(AbilityHttpApi.Required, [
558
+ AbilityHttpApi.requires(Auth, "update", "Post", {
559
+ within: (params) => ($) => $.eq("authorId", params["user"] ?? ""),
560
+ }),
561
+ ])
562
+
563
+ const list = HttpApiEndpoint.get("list", "/users/:user/posts", {
564
+ params: { user: Schema.String },
565
+ }).annotate(AbilityHttpApi.Required, [
566
+ AbilityHttpApi.lists(Auth, "read", "Post", {
567
+ within: (params) => ($) => $.eq("authorId", params["user"] ?? ""),
568
+ }),
569
+ ])
570
+ ```
571
+
572
+ A path parameter may be `undefined`. A scope with `undefined` in it is a defect,
573
+ so give another value for it.
574
+
575
+ The annotations and the layer do nothing alone. Give the middleware to every
576
+ group with `.middleware(Authorization)`, or to the whole API. An endpoint in a
577
+ group without the middleware is not checked.
578
+
424
579
  `GroupRequired` gives a default for a whole group: one list for GET requests and
425
580
  one for every other method. `AbilityHttpApi.errors` gives the errors with HTTP
426
581
  status codes: 403, 503, and 409.
@@ -1,15 +1,21 @@
1
1
  import { Condition, Opaque } from "./Condition.js";
2
- import { Any, AnyAction, Principal } from "./Domain.js";
2
+ import { Any, CommonAction, Kind, Principal } from "./Domain.js";
3
3
  import { Decision } from "./Decision.js";
4
- import { KindRequest, KindTarget, Policy, Request, Target as Target$1 } from "./Policy.js";
5
- import { Forbidden as Forbidden$1, Pending as Pending$1, PolicyUnavailable as PolicyUnavailable$1 } from "./internal/authorizer.js";
4
+ import { Coverage, FieldlessTarget, KindRequest, KindTarget, Policy, Request, ResidualReads, Target as Target$1 } from "./Policy.js";
6
5
  import * as Context from "effect/Context";
7
6
  import * as Effect from "effect/Effect";
8
7
  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
11
  export { AnyServiceClass, Authorizer, Config, 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>;
13
19
  /**
14
20
  * The error when a policy says no.
15
21
  *
@@ -17,13 +23,10 @@ declare namespace Authorizer_d_exports {
17
23
  * @category errors
18
24
  * @since 0.3.0
19
25
  */
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;
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>;
27
30
  /**
28
31
  * The error when the policy of the principal can not be loaded. The cause is
29
32
  * kept, but it is not shown to the client.
@@ -32,13 +35,11 @@ export type Forbidden = Forbidden$1;
32
35
  * @category errors
33
36
  * @since 0.3.0
34
37
  */
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;
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>;
42
43
  /**
43
44
  * The error when a decider must wait for an answer. `ticket` names the
44
45
  * request that waits, so the client can come back for the answer.
@@ -47,13 +48,7 @@ export type PolicyUnavailable = PolicyUnavailable$1;
47
48
  * @category errors
48
49
  * @since 0.3.0
49
50
  */
50
- export declare const Pending: typeof Pending$1;
51
- /**
52
- * @stability stable
53
- * @category errors
54
- * @since 0.3.0
55
- */
56
- export type Pending = Pending$1;
51
+ export declare class Pending extends Pending_base {}
57
52
  /**
58
53
  * The services and the errors of the principal and the decider of an
59
54
  * authorizer.
@@ -75,7 +70,8 @@ type ErrorOf<C> = C extends {
75
70
  /**
76
71
  * What a check is about: an act on a resource value, or an act on a kind of
77
72
  * resource. A check on a kind passes only when the policy allows the act on
78
- * every value of the kind.
73
+ * every value of the kind. With a scope (`within`), it passes when the policy
74
+ * allows the act on every value in the scope, and the scope is not empty.
79
75
  *
80
76
  * @stability stable
81
77
  * @category models
@@ -97,12 +93,23 @@ export type DeciderRequest<D> = Request<D> | KindRequest<D>;
97
93
  * @category models
98
94
  * @since 0.3.0
99
95
  */
100
- export interface GuardOptions<D, E2> {
101
- /** The action that lets the principal see the resource. The default is `"read"`. */
102
- readonly read?: AnyAction<D>;
96
+ export type GuardOptions<D, E2, K extends string = Kind<D>> = {
103
97
  /** The error when the principal may not see the resource. */
104
98
  readonly hide: () => E2;
105
- }
99
+ } & ("read" extends CommonAction<D, K> ? {
100
+ /**
101
+ * The action that lets the principal see the resource. It must be an
102
+ * action of every kind in `K`. The default is `"read"`.
103
+ */
104
+ readonly read?: CommonAction<D, K>;
105
+ } : {
106
+ /**
107
+ * The action that lets the principal see the resource. It must be an
108
+ * action of every kind in `K`. Some kind in `K` has no `"read"`
109
+ * action, so you must give it.
110
+ */
111
+ readonly read: CommonAction<D, K>;
112
+ });
106
113
  /**
107
114
  * The service.
108
115
  *
@@ -114,8 +121,11 @@ export interface Authorizer<D extends Any, E, R> {
114
121
  readonly domain: D;
115
122
  /** The principal of the current request. */
116
123
  readonly principal: Effect.Effect<Principal<D>, E, R>;
117
- /** The policy of the principal of the current request. */
118
- readonly policy: Effect.Effect<Policy<D, E>, PolicyUnavailable | E, R>;
124
+ /**
125
+ * The policy of the principal of the current request. Its predicates have
126
+ * the services of the layer. It may have predicates, so it is opaque.
127
+ */
128
+ readonly policy: Effect.Effect<Policy<D, E, never, Opaque>, PolicyUnavailable | E, R>;
119
129
  /** Gives the decision of the policy. It does not ask the decider. */
120
130
  readonly decide: (target: Target$1<D>) => Effect.Effect<Decision, PolicyUnavailable | E, R>;
121
131
  /** Succeeds when the principal may do the act, and fails when it may not. */
@@ -126,14 +136,22 @@ export interface Authorizer<D extends Any, E, R> {
126
136
  * answer "not found" to a principal that may not know that a resource
127
137
  * exists.
128
138
  */
129
- readonly guard: <E2>(target: Target$1<D>, options: GuardOptions<D, E2>) => Effect.Effect<void, E2 | Forbidden | Pending | PolicyUnavailable | E, R>;
139
+ 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>;
140
+ /**
141
+ * Gives the condition on resource values that the principal may act on,
142
+ * inside the scope of the target when it has one. Change it into a query
143
+ * with `Condition.fold`. It is `False` when the principal may act on no
144
+ * value, so the list is empty: it is not an error.
145
+ */
146
+ readonly filter: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Condition<E, never, Opaque, ResidualReads<D, T["kind"]>>, PolicyUnavailable | E, R>;
130
147
  /**
131
- * Gives the condition on resource values that the principal may act on.
132
- * Change it into a query with `Condition.fold`.
148
+ * Tells if the principal may act on all, none or some of the values in
149
+ * the scope of the target. See `Policy.coverage`. Use it to show a button
150
+ * only when the answer is not `None`.
133
151
  */
134
- readonly filter: (target: KindTarget<D>) => Effect.Effect<Condition<E, never, Opaque>, PolicyUnavailable | E, R>;
152
+ readonly coverage: <const T extends KindTarget<D>>(target: T) => Effect.Effect<Coverage<E, never, Opaque, ResidualReads<D, T["kind"]>>, PolicyUnavailable | E, R>;
135
153
  /** Gives the fields of a value that the principal may use. */
136
- readonly permittedFields: (target: Omit<Target$1<D>, "field">) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R>;
154
+ readonly permittedFields: (target: FieldlessTarget<D>) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R>;
137
155
  /**
138
156
  * Removes the cached policy of a principal, or of every principal. Call it
139
157
  * after a change to the rules or roles of a principal.
@@ -152,9 +170,10 @@ export interface ServiceClass<Self, Id extends string, D extends Any, E, R> exte
152
170
  readonly domain: D;
153
171
  readonly decide: (target: Target$1<D>) => Effect.Effect<Decision, PolicyUnavailable | E, R | Self>;
154
172
  readonly check: (target: Target<D>) => Effect.Effect<void, Forbidden | Pending | PolicyUnavailable | E, R | Self>;
155
- readonly guard: <E2>(target: Target$1<D>, options: GuardOptions<D, E2>) => Effect.Effect<void, E2 | Forbidden | Pending | PolicyUnavailable | E, R | Self>;
156
- readonly filter: (target: KindTarget<D>) => Effect.Effect<Condition<E, never, Opaque>, PolicyUnavailable | E, R | Self>;
157
- readonly permittedFields: (target: Omit<Target$1<D>, "field">) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R | Self>;
173
+ 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>;
176
+ readonly permittedFields: (target: FieldlessTarget<D>) => Effect.Effect<ReadonlyArray<string>, PolicyUnavailable | E, R | Self>;
158
177
  }
159
178
  /**
160
179
  * Any service class made by {@link Service}.
@@ -198,6 +217,12 @@ export interface Options<D extends Any, E, R, EL, RL, RP> {
198
217
  /**
199
218
  * How the policies are kept. The default keeps 1000 policies for 30 seconds.
200
219
  * Give `false` to load the policy for each check.
220
+ *
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.
201
226
  */
202
227
  readonly cache?: {
203
228
  readonly capacity?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"Authorizer.d.ts","names":["internal.Forbidden","internal.PolicyUnavailable","internal.Pending","Policy.Target","Policy.KindTarget","Policy.Request","Policy.KindRequest","Domain.AnyAction","Domain.Any","Domain.Principal","Policy.Policy","Decision.Decision","Condition.Opaque","Condition.Condition"],"sources":["../src/Authorizer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;qBAqDa,kBAAkBA;;;;;;YAOnB,YAAYA;;;;;;;;;qBAUX,0BAA0BC;;;;;;YAQ3B,oBAAoBA;;;;;;;;;qBAUnB,gBAAgBC;;;;;;YAOjB,UAAUA;;;;;;;;;iBAUL;WACP;WACA;;KAGL,WAAW,KAAK;WAAqB,gBAAgB;IAAM;KAC3D,QAAQ,KAAK;WAAqB,aAAa;IAAM;;;;;;;;;;YAW9C,OAAO,KAAKC,SAAc,KAAKC,WAAkB;;;;;;;;YASjD,eAAe,KAAKC,QAAe,KAAKC,YAAmB;;;;;;;;iBAStD,aAAa,GAAG;;WAEvB,OAAOC,UAAiB;;WAExB,YAAY;;;;;;;;;iBAUL,WAAW,UAAUC,KAAY,GAAG;WAC3C,QAAQ;;WAER,WAAW,OAAO,OAAOC,UAAiB,IAAI,GAAG;;WAEjD,QAAQ,OAAO,OAAOC,OAAc,GAAG,IAAI,oBAAoB,GAAG;;WAElE,SACR,QAAQP,SAAc,OAClB,OAAO,OAAOQ,UAAmB,oBAAoB,GAAG;;WAEpD,QACR,QAAQ,OAAO,OACX,OAAO,aAAa,YAAY,UAAU,oBAAoB,GAAG;;;;;;;WAO7D,QAAQ,IAChB,QAAQR,SAAc,IACtB,SAAS,aAAa,GAAG,QACrB,OAAO,aAAa,KAAK,YAAY,UAAU,oBAAoB,GAAG;;;;;WAKlE,SACR,QAAQC,WAAkB,OACtB,OAAO,OACXS,UAAoB,UAAUD,SAC9B,oBAAoB,GACpB;;WAGQ,kBACR,QAAQ,KAAKT,SAAc,iBACvB,OAAO,OAAO,uBAAuB,oBAAoB,GAAG;;;;;WAKxD,aAAa,YAAYM,UAAiB,OAAO,OAAO;;;;;;;;;;iBAWjD,aAChB,MACA,mBACA,UAAUD,KACV,GACA,WACS,QAAQ,aAAa,MAAM,IAAI,WAAW,GAAG,GAAG;WAChD,QAAQ;WACR,SACR,QAAQL,SAAc,OAClB,OAAO,OAAOQ,UAAmB,oBAAoB,GAAG,IAAI;WACxD,QACR,QAAQ,OAAO,OACX,OAAO,aAEX,YAAY,UAAU,oBAAoB,GAC1C,IAAI;WAEI,QAAQ,IAChB,QAAQR,SAAc,IACtB,SAAS,aAAa,GAAG,QACrB,OAAO,aAEX,KAAK,YAAY,UAAU,oBAAoB,GAC/C,IAAI;WAEI,SACR,QAAQC,WAAkB,OACtB,OAAO,OACXS,UAAoB,UAAUD,SAC9B,oBAAoB,GACpB,IAAI;WAEI,kBACR,QAAQ,KAAKT,SAAc,iBACvB,OAAO,OAAO,uBAAuB,oBAAoB,GAAG,IAAI;;;;;;;;;YAU1D,kBAAkB,QAAQ;WAC5B,QAAQK;;;;;;;;;qBAUL,UAAU,MAAM,UAAU,yBAChC,mBACN,UAAUA,KAEV,IAAI,IACJ,QAAQ,MACJ,aAAa,MAAM,IAAI,GAAG,QAAQ,IAAI,WAAW;;;;;;;;iBAUrC,QAAQ,UAAUA,KAAY,GAAG,GAAG,IAAI,IAAI;;WAEnD,WAAW,OAAO,OAAOC,UAAiB,IAAI,GAAG;;;;;;WAMjD,SACR,WAAWA,UAAiB,OACxB,OAAO,OAAOC,OAAc,GAAG,GAAG,UAAU,IAAI;;;;;WAK5C,WACR,SAAS,eAAe,OACpB,OAAO,OAAOC,UAAmB,UAAU,GAAG;;;;;WAK1C;aAEG;aACA,aAAa,SAAS;;;;;;;;;;qBAYtB,QACZ,MACA,mBACA,UAAUH,KACV,GACA,GACA,YACA,YACA,YAEA,SAAS,aAAa,MAAM,IAAI,GAAG,GAAG,IACtC,SAAS,QAAQ,GAAG,QAAQ,IAAI,QAAQ,IAAI,IAAI,IAAI,QAChD,MAAM,MAAM,aAAa,KAAK"}
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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"}