@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.
- package/README.md +169 -14
- package/dist/Authorizer.d.ts +64 -39
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js +291 -6
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +444 -65
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +1007 -25
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +27 -17
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +52 -10
- package/dist/Domain.js.map +1 -1
- package/dist/HttpApi.d.ts +84 -40
- package/dist/HttpApi.d.ts.map +1 -1
- package/dist/HttpApi.js +101 -11
- package/dist/HttpApi.js.map +1 -1
- package/dist/Policy.d.ts +311 -53
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +1212 -35
- package/dist/Policy.js.map +1 -1
- package/dist/Resource.d.ts +167 -16
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +784 -11
- package/dist/Resource.js.map +1 -1
- package/dist/Rule.d.ts +113 -27
- package/dist/Rule.d.ts.map +1 -1
- package/dist/Rule.js +217 -10
- package/dist/Rule.js.map +1 -1
- package/dist/index.js +4 -4
- package/package.json +1 -1
- package/src/Authorizer.ts +623 -59
- package/src/Condition.ts +2403 -112
- package/src/Domain.ts +79 -22
- package/src/HttpApi.ts +223 -23
- package/src/Policy.ts +2833 -93
- package/src/Resource.ts +1537 -22
- package/src/Rule.ts +461 -43
- package/dist/internal/authorizer.d.ts +0 -28
- package/dist/internal/authorizer.d.ts.map +0 -1
- package/dist/internal/authorizer.js +0 -153
- package/dist/internal/authorizer.js.map +0 -1
- package/dist/internal/condition.d.ts +0 -11
- package/dist/internal/condition.d.ts.map +0 -1
- package/dist/internal/condition.js +0 -393
- package/dist/internal/condition.js.map +0 -1
- package/dist/internal/domain.js +0 -34
- package/dist/internal/domain.js.map +0 -1
- package/dist/internal/httpApi.js +0 -41
- package/dist/internal/httpApi.js.map +0 -1
- package/dist/internal/policy.js +0 -524
- package/dist/internal/policy.js.map +0 -1
- package/dist/internal/resource.js +0 -66
- package/dist/internal/resource.js.map +0 -1
- package/dist/internal/rule.js +0 -131
- package/dist/internal/rule.js.map +0 -1
- package/src/internal/authorizer.ts +0 -271
- package/src/internal/condition.ts +0 -607
- package/src/internal/domain.ts +0 -56
- package/src/internal/httpApi.ts +0 -84
- package/src/internal/policy.ts +0 -815
- package/src/internal/resource.ts +0 -98
- 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.
|
|
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
|
|
296
|
-
yield
|
|
297
|
-
|
|
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
|
-
|
|
344
|
-
yield
|
|
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
|
|
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.
|
package/dist/Authorizer.d.ts
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { Condition, Opaque } from "./Condition.js";
|
|
2
|
-
import { Any,
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
118
|
-
|
|
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: <
|
|
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
|
-
*
|
|
132
|
-
*
|
|
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
|
|
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:
|
|
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: <
|
|
156
|
-
readonly filter: (target:
|
|
157
|
-
readonly
|
|
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;
|
package/dist/Authorizer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Authorizer.d.ts","names":["
|
|
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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"}
|