@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/src/Decision.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * @since 0.3.0
13
13
  */
14
14
  import * as Data from "effect/Data"
15
- import type * as Rule from "./Rule.ts"
15
+ import type * as Policy from "./Policy.ts"
16
16
 
17
17
  /**
18
18
  * @stability stable
@@ -22,11 +22,11 @@ import type * as Rule from "./Rule.ts"
22
22
  export type Decision = Data.TaggedEnum<{
23
23
  Allow: {
24
24
  /** The rule that gave the answer, when a rule gave it. */
25
- readonly rule?: Rule.Any
25
+ readonly rule?: Policy.AnyRule
26
26
  }
27
27
  Deny: {
28
28
  /** The rule that gave the answer, when a rule gave it. */
29
- readonly rule?: Rule.Any
29
+ readonly rule?: Policy.AnyRule
30
30
  readonly reason?: string
31
31
  }
32
32
  NotApplicable: {}
@@ -45,7 +45,8 @@ const {
45
45
  * @category constructors
46
46
  * @since 0.3.0
47
47
  */
48
- export const Allow: (args: { readonly rule?: Rule.Any }) => Decision = allow
48
+ export const Allow: (args: { readonly rule?: Policy.AnyRule }) => Decision =
49
+ allow
49
50
 
50
51
  /**
51
52
  * @stability stable
@@ -53,7 +54,7 @@ export const Allow: (args: { readonly rule?: Rule.Any }) => Decision = allow
53
54
  * @since 0.3.0
54
55
  */
55
56
  export const Deny: (args: {
56
- readonly rule?: Rule.Any
57
+ readonly rule?: Policy.AnyRule
57
58
  readonly reason?: string
58
59
  }) => Decision = deny
59
60
 
package/src/Domain.ts CHANGED
@@ -14,13 +14,14 @@
14
14
  * @stability stable
15
15
  * @since 0.3.0
16
16
  */
17
- import * as Arr from "effect/Array"
18
- import * as Data from "effect/Data"
19
- import * as HashMap from "effect/HashMap"
20
- import * as Option from "effect/Option"
17
+ import type * as HashMap from "effect/HashMap"
21
18
  import type * as Schema from "effect/Schema"
22
- import type * as PrincipalSchema from "./Principal.ts"
23
- import * as Resource from "./Resource.ts"
19
+ import type * as Catalog from "./internal/catalog.ts"
20
+ import * as Internal from "./internal/domain.ts"
21
+ import type * as Resource from "./Resource.ts"
22
+
23
+ /** The Schema of a principal: a Schema of objects, such as a `Schema.Class`. */
24
+ type PrincipalSchema = Schema.Top & { readonly Type: object }
24
25
 
25
26
  /**
26
27
  * A domain. `P` is the type of the principal, `Resources` is the union of the
@@ -35,7 +36,9 @@ export interface Domain<
35
36
  Resources extends Resource.Any,
36
37
  Meta,
37
38
  > {
38
- readonly principal: PrincipalSchema.Any
39
+ readonly principal: PrincipalSchema
40
+ /** The catalog of the data fields of the principal. */
41
+ readonly principalCatalog: Catalog.Catalog
39
42
  /** The descriptor of each resource, by its kind name. */
40
43
  readonly resources: HashMap.HashMap<string, Resource.Descriptor>
41
44
  readonly meta: Schema.Top | undefined
@@ -165,15 +168,6 @@ export type ValueOf<D, K extends string> = Resource.Value<ResourceOf<D, K>>
165
168
  */
166
169
  export type AnyValue<D> = Resource.Value<Resources<D>>
167
170
 
168
- /**
169
- * The class of every domain value. {@link isDomain} uses it to know a domain.
170
- */
171
- class DomainData extends Data.Class<{
172
- readonly principal: PrincipalSchema.Any
173
- readonly resources: HashMap.HashMap<string, Resource.Descriptor>
174
- readonly meta: Schema.Top | undefined
175
- }> {}
176
-
177
171
  /**
178
172
  * Makes a domain. Each resource must have its own kind name. Give `meta` when
179
173
  * rules must keep extra data, such as the scope of a grant. The `meta` Schema
@@ -185,62 +179,11 @@ class DomainData extends Data.Class<{
185
179
  * @since 0.3.0
186
180
  */
187
181
  export const make: <
188
- P extends PrincipalSchema.Any,
182
+ P extends PrincipalSchema,
189
183
  const Resources extends ReadonlyArray<Resource.Any>,
190
184
  M extends Schema.Codec<any, any, never, never> = Schema.Never,
191
185
  >(options: {
192
186
  readonly principal: P
193
187
  readonly resources: Resources
194
188
  readonly meta?: M
195
- }) => Domain<P["Type"], Resources[number], M["Type"]> = (options) => {
196
- const descriptors = Arr.map(options.resources, Resource.descriptor)
197
- const kinds = Arr.map(descriptors, (descriptor) => descriptor.kind)
198
- // Two resources with one kind name is a mistake in the code, not in the
199
- // data, so it is a defect.
200
- const twice = Arr.findFirst(
201
- kinds,
202
- (kind, index) => kinds.indexOf(kind) !== index,
203
- )
204
- if (Option.isSome(twice)) {
205
- throw new Error(`Two resources have the kind name "${twice.value}"`)
206
- }
207
- if (kinds.includes("*")) {
208
- throw new Error(`A resource may not have the kind name "*"`)
209
- }
210
- const resources = HashMap.fromIterable(
211
- Arr.map(
212
- descriptors,
213
- (descriptor) => [descriptor.kind, descriptor] as const,
214
- ),
215
- )
216
- // This checks the field names of the principal. See `Resource.fieldPaths`.
217
- Resource.fieldPaths(options.principal)
218
- return new DomainData({
219
- principal: options.principal,
220
- resources,
221
- meta: options.meta,
222
- })
223
- }
224
-
225
- /**
226
- * Gives the descriptor of the resource with a kind name, or `undefined` when
227
- * the domain has no such resource.
228
- *
229
- * @stability stable
230
- * @category getters
231
- * @since 1.0.0
232
- */
233
- export const descriptorOf = (
234
- domain: Any,
235
- kind: string,
236
- ): Resource.Descriptor | undefined =>
237
- Option.getOrUndefined(HashMap.get(domain.resources, kind))
238
-
239
- /**
240
- * Tests if a value is a domain.
241
- *
242
- * @stability stable
243
- * @category guards
244
- * @since 0.3.0
245
- */
246
- export const isDomain = (u: unknown): u is Any => u instanceof DomainData
189
+ }) => Domain<P["Type"], Resources[number], M["Type"]> = Internal.make as never
package/src/HttpApi.ts CHANGED
@@ -44,9 +44,10 @@ import * as SchemaTransformation from "effect/SchemaTransformation"
44
44
  import { HttpRouter } from "effect/http"
45
45
  import { HttpApiSchema } from "effect/http-api"
46
46
  import * as Authorizer from "./Authorizer.ts"
47
+ import type * as Condition from "./Condition.ts"
47
48
  import type * as Domain from "./Domain.ts"
48
49
  import type * as Policy from "./Policy.ts"
49
- import * as Resource from "./Resource.ts"
50
+ import * as ResourceData from "./internal/resource.ts"
50
51
 
51
52
  /**
52
53
  * The path parameters of a request.
@@ -146,11 +147,49 @@ export const requires: <
146
147
  ? { action: action as string, kind, load: how }
147
148
  : { action: action as string, kind, within: how.within }
148
149
 
150
+ /**
151
+ * A {@link Requirement} for an endpoint that gives a list, with the list query
152
+ * for the handler. `filter` takes the authorizer, which the handler gets when
153
+ * its group is built. It reads the path parameters of the request, and it
154
+ * gives the condition of `Authorizer.filter` in the scope of the requirement.
155
+ * So the scope is written one time, for the middleware and for the handler.
156
+ *
157
+ * @stability unstable
158
+ * @category models
159
+ * @since 2.0.0
160
+ */
161
+ export interface List<A, K extends string> extends Requirement {
162
+ readonly filter: A extends Authorizer.ServiceClass<
163
+ any,
164
+ any,
165
+ infer D,
166
+ infer E,
167
+ infer R
168
+ >
169
+ ? (
170
+ authorizer: Authorizer.Authorizer<D, E, R>,
171
+ ) => Effect.Effect<
172
+ Condition.Condition<Policy.ResidualReads<D, K>, Condition.Opaque<E>>,
173
+ Authorizer.PolicyUnavailable | E,
174
+ R | HttpRouter.RouteContext
175
+ >
176
+ : never
177
+ }
178
+
149
179
  /**
150
180
  * Makes a {@link Requirement} for an endpoint that gives a list. The middleware
151
181
  * does not check that the principal may act on every value, because the list
152
182
  * shows only the values that it may act on. The handler gets the list query
153
- * with `Authorizer.filter`, with the same scope.
183
+ * from `filter` of the result, with the same scope:
184
+ *
185
+ * ```ts
186
+ * const byAuthor = HttpApi.lists(Auth, "read", "Note", {
187
+ * within: (params) => ($) => $.eq("authorId", params.user ?? null),
188
+ * })
189
+ * // When the group is built: const auth = yield* Auth
190
+ * // In the handler:
191
+ * const where = yield* byAuthor.filter(auth)
192
+ * ```
154
193
  *
155
194
  * When the principal may act on no value, the list is empty: that is not an
156
195
  * error, so a client does not learn that hidden values are there. With
@@ -163,7 +202,7 @@ export const requires: <
163
202
  * @since 1.0.0
164
203
  */
165
204
  export const lists: <
166
- A extends { readonly domain: Domain.Any },
205
+ A extends Authorizer.AnyServiceClass,
167
206
  const K extends Domain.Kind<A["domain"]>,
168
207
  >(
169
208
  authorizer: A,
@@ -173,12 +212,35 @@ export const lists: <
173
212
  readonly within?: (params: Params) => Policy.Scope<A["domain"], K>
174
213
  readonly nonEmpty?: boolean
175
214
  },
176
- ) => Requirement = (_authorizer, action, kind, options) => ({
177
- action: action as string,
178
- kind,
179
- list: { nonEmpty: options?.nonEmpty ?? false },
180
- ...(options?.within === undefined ? {} : { within: options.within }),
181
- })
215
+ ) => List<A, K> = ((
216
+ _authorizer: Authorizer.AnyServiceClass,
217
+ action: string,
218
+ kind: string,
219
+ options?: {
220
+ readonly within?: (params: Params) => unknown
221
+ readonly nonEmpty?: boolean
222
+ },
223
+ ) => {
224
+ const within = options?.within
225
+ return {
226
+ action,
227
+ kind,
228
+ list: { nonEmpty: options?.nonEmpty ?? false },
229
+ ...(within === undefined ? {} : { within }),
230
+ filter: (authorizer: {
231
+ readonly filter: (
232
+ target: object,
233
+ ) => Effect.Effect<unknown, unknown, unknown>
234
+ }) =>
235
+ Effect.flatMap(HttpRouter.params, (params) =>
236
+ authorizer.filter({
237
+ action,
238
+ kind,
239
+ ...(within === undefined ? {} : { within: within(params) }),
240
+ }),
241
+ ),
242
+ }
243
+ }) as never
182
244
 
183
245
  /**
184
246
  * The Schema of a `PolicyUnavailable` response. It encodes only the `_tag`, so
@@ -272,7 +334,7 @@ const makeLayer = (
272
334
  // another kind is a mistake in `load`, and the check must
273
335
  // not pass with the rules of that other kind.
274
336
  Effect.filterOrElse(
275
- (value) => Resource.kindOf(value) === kind,
337
+ (value) => ResourceData.kindOf(value) === kind,
276
338
  () =>
277
339
  Effect.die(
278
340
  new Error(
package/src/Limits.ts ADDED
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The limits of the library. Each one keeps a walk, a decoder or a search
3
+ * small, so no input can fill the stack or take a very long time. An input
4
+ * past a limit is an error, with one form of message, as in "A condition has
5
+ * 100001 nodes. The most is 100000.". In an `Authorizer`, such an error is
6
+ * never a "yes".
7
+ *
8
+ * @stability stable
9
+ * @since 2.0.0
10
+ */
11
+ import * as Limits from "./internal/limits.ts"
12
+
13
+ /**
14
+ * The most parts of a field path, as in `settings.model`. The types of the
15
+ * field paths go to the same depth.
16
+ *
17
+ * @stability stable
18
+ * @category constants
19
+ * @since 2.0.0
20
+ */
21
+ export const fieldPathParts: number = Limits.fieldPathParts
22
+
23
+ /**
24
+ * The most parts of a field pattern, as in `**.secret`.
25
+ *
26
+ * @stability stable
27
+ * @category constants
28
+ * @since 2.0.0
29
+ */
30
+ export const patternParts: number = Limits.patternParts
31
+
32
+ /**
33
+ * The most nodes of one condition. A part that a condition uses in many
34
+ * places counts for each place.
35
+ *
36
+ * @stability stable
37
+ * @category constants
38
+ * @since 2.0.0
39
+ */
40
+ export const conditionNodes: number = Limits.conditionNodes
41
+
42
+ /**
43
+ * The most levels of one condition.
44
+ *
45
+ * @stability stable
46
+ * @category constants
47
+ * @since 2.0.0
48
+ */
49
+ export const conditionDepth: number = Limits.conditionDepth
50
+
51
+ /**
52
+ * The most nodes of one residual condition.
53
+ *
54
+ * @stability stable
55
+ * @category constants
56
+ * @since 2.0.0
57
+ */
58
+ export const residualNodes: number = Limits.residualNodes
59
+
60
+ /**
61
+ * The most levels of a policy tree.
62
+ *
63
+ * @stability stable
64
+ * @category constants
65
+ * @since 2.0.0
66
+ */
67
+ export const policyDepth: number = Limits.policyDepth
68
+
69
+ /**
70
+ * The most nodes of a policy tree, with its rules and the nodes of their
71
+ * conditions. A part that the tree uses in many places counts for each place.
72
+ *
73
+ * @stability stable
74
+ * @category constants
75
+ * @since 2.0.0
76
+ */
77
+ export const policyNodes: number = Limits.policyNodes
78
+
79
+ /**
80
+ * The most objects and arrays in one input to the Schema of a policy or of a
81
+ * condition.
82
+ *
83
+ * @stability stable
84
+ * @category constants
85
+ * @since 2.0.0
86
+ */
87
+ export const jsonNodes: number = Limits.jsonNodes
88
+
89
+ /**
90
+ * The most fields in the catalog of one Schema.
91
+ *
92
+ * @stability stable
93
+ * @category constants
94
+ * @since 2.0.0
95
+ */
96
+ export const catalogFields: number = Limits.catalogFields
97
+
98
+ /**
99
+ * The most steps of one search for a value, as in `Policy.coverage`. Past
100
+ * it, the answer is `Unknown`.
101
+ *
102
+ * @stability stable
103
+ * @category constants
104
+ * @since 2.0.0
105
+ */
106
+ export const searchSteps: number = Limits.searchSteps
107
+
108
+ /**
109
+ * The most fields that one search for a value may test. Past it, the answer
110
+ * is `Unknown`.
111
+ *
112
+ * @stability stable
113
+ * @category constants
114
+ * @since 2.0.0
115
+ */
116
+ export const searchFields: number = Limits.searchFields
117
+
118
+ /**
119
+ * The error when an input goes past a limit.
120
+ *
121
+ * @stability stable
122
+ * @category errors
123
+ * @since 2.0.0
124
+ */
125
+ export const LimitExceeded: typeof Limits.LimitExceeded = Limits.LimitExceeded
126
+
127
+ /**
128
+ * @stability stable
129
+ * @category errors
130
+ * @since 2.0.0
131
+ */
132
+ export type LimitExceeded = Limits.LimitExceeded