@nmnmcc/ability 2.0.7 → 2.1.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.
- package/README.md +50 -27
- package/dist/Authorizer.d.ts +21 -12
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +116 -16
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +27 -18
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +9 -0
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +5 -0
- package/dist/Domain.js.map +1 -1
- package/dist/Limits.d.ts +44 -13
- package/dist/Limits.d.ts.map +1 -1
- package/dist/Limits.js +52 -22
- package/dist/Limits.js.map +1 -1
- package/dist/Policy.d.ts +95 -12
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/internal/algebra.js +2 -2
- package/dist/internal/analysis.js +81 -75
- package/dist/internal/analysis.js.map +1 -1
- package/dist/internal/authorizer.d.ts.map +1 -1
- package/dist/internal/authorizer.js +11 -7
- package/dist/internal/authorizer.js.map +1 -1
- package/dist/internal/bind.js +86 -64
- package/dist/internal/bind.js.map +1 -1
- package/dist/internal/catalog.d.ts +33 -10
- package/dist/internal/catalog.d.ts.map +1 -1
- package/dist/internal/catalog.js +147 -72
- package/dist/internal/catalog.js.map +1 -1
- package/dist/internal/decide.js +25 -29
- package/dist/internal/decide.js.map +1 -1
- package/dist/internal/domain.js +19 -5
- package/dist/internal/domain.js.map +1 -1
- package/dist/internal/json.js +120 -76
- package/dist/internal/json.js.map +1 -1
- package/dist/internal/limits.d.ts +28 -0
- package/dist/internal/limits.d.ts.map +1 -1
- package/dist/internal/limits.js +75 -16
- package/dist/internal/limits.js.map +1 -1
- package/dist/internal/nodes.d.ts.map +1 -1
- package/dist/internal/nodes.js +53 -44
- package/dist/internal/nodes.js.map +1 -1
- package/dist/internal/patterns.js +50 -15
- package/dist/internal/patterns.js.map +1 -1
- package/dist/internal/policy-json.js +169 -127
- package/dist/internal/policy-json.js.map +1 -1
- package/dist/internal/policy-nodes.d.ts.map +1 -1
- package/dist/internal/policy-nodes.js +16 -13
- package/dist/internal/policy-nodes.js.map +1 -1
- package/dist/internal/policy.d.ts.map +1 -1
- package/dist/internal/policy.js +38 -22
- package/dist/internal/policy.js.map +1 -1
- package/dist/internal/resource.js +1 -1
- package/dist/internal/rule.d.ts.map +1 -1
- package/dist/internal/rule.js +6 -5
- package/dist/internal/rule.js.map +1 -1
- package/dist/internal/search.d.ts.map +1 -1
- package/dist/internal/search.js +106 -92
- package/dist/internal/search.js.map +1 -1
- package/dist/internal/trampoline.js +19 -5
- package/dist/internal/trampoline.js.map +1 -1
- package/package.json +1 -1
- package/src/Authorizer.ts +30 -20
- package/src/Condition.ts +308 -83
- package/src/Domain.ts +9 -0
- package/src/Limits.ts +69 -29
- package/src/Policy.ts +131 -15
- package/src/internal/analysis.ts +154 -124
- package/src/internal/authorizer.ts +29 -14
- package/src/internal/bind.ts +193 -136
- package/src/internal/catalog.ts +295 -112
- package/src/internal/decide.ts +58 -49
- package/src/internal/domain.ts +39 -3
- package/src/internal/json.ts +212 -117
- package/src/internal/limits.ts +101 -31
- package/src/internal/nodes.ts +65 -47
- package/src/internal/patterns.ts +72 -26
- package/src/internal/policy-json.ts +266 -197
- package/src/internal/policy-nodes.ts +31 -23
- package/src/internal/policy.ts +63 -23
- package/src/internal/rule.ts +16 -3
- package/src/internal/search.ts +186 -136
- package/src/internal/trampoline.ts +51 -4
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Allow, Deny, NotApplicable } from "../Decision.js";
|
|
2
|
-
import { exceeded
|
|
2
|
+
import { exceeded } from "./limits.js";
|
|
3
|
+
import { run } from "./trampoline.js";
|
|
3
4
|
import { toNode } from "./nodes.js";
|
|
4
5
|
import { and, not, or } from "./algebra.js";
|
|
6
|
+
import { at } from "./place.js";
|
|
5
7
|
import { kindsIn, satisfiable } from "./search.js";
|
|
6
8
|
import { kindOf } from "./resource.js";
|
|
7
9
|
import { always as always$1, builder, never as never$1, partial } from "../Condition.js";
|
|
@@ -11,9 +13,9 @@ import { fitsOf, matchesTarget } from "./rule.js";
|
|
|
11
13
|
import { rulesOf } from "./policy-nodes.js";
|
|
12
14
|
import { nodeOf } from "./policy.js";
|
|
13
15
|
import { decideNode, fitsKind } from "./decide.js";
|
|
16
|
+
import * as Arr from "effect/Array";
|
|
14
17
|
import * as Effect from "effect/Effect";
|
|
15
18
|
import * as Data from "effect/Data";
|
|
16
|
-
import * as Arr from "effect/Array";
|
|
17
19
|
// src/internal/analysis.ts
|
|
18
20
|
/**
|
|
19
21
|
* The residual conditions and the coverage of a policy. The types are in
|
|
@@ -21,14 +23,18 @@ import * as Arr from "effect/Array";
|
|
|
21
23
|
*/
|
|
22
24
|
const always = always$1;
|
|
23
25
|
const never = never$1;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
/** The steps of a residual for a domain with these limits. */
|
|
27
|
+
const buildFor = (limits) => {
|
|
28
|
+
const most = limits.residualNodes;
|
|
29
|
+
const limit = (node) => {
|
|
30
|
+
if (node.size > most) throw exceeded("A residual condition", "residualNodes", "nodes", node.size, most);
|
|
31
|
+
return node;
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
and: (values) => limit(and(values)),
|
|
35
|
+
or: (values) => limit(or(values)),
|
|
36
|
+
not: (value) => value.pipe(not, limit)
|
|
37
|
+
};
|
|
32
38
|
};
|
|
33
39
|
const makeCondition = (value, context) => !matchesTarget(value, context) ? never : value.when === void 0 ? always : partial(value.when, context.input);
|
|
34
40
|
const ruleCondition = (value, context) => context.conditions.get(value) ?? makeCondition(value, context);
|
|
@@ -37,7 +43,7 @@ const ruleCondition = (value, context) => context.conditions.get(value) ?? makeC
|
|
|
37
43
|
* "the first rule that applies is a deny rule".
|
|
38
44
|
*/
|
|
39
45
|
const firstApplicableResidual = (values, context) => {
|
|
40
|
-
const { and, not, or } = build;
|
|
46
|
+
const { and, not, or } = context.build;
|
|
41
47
|
const runs = Arr.isReadonlyArrayNonEmpty(values) ? Arr.groupWith(values, (a, b) => a._tag === b._tag) : [];
|
|
42
48
|
return Arr.reduceRight(runs, {
|
|
43
49
|
allow: never,
|
|
@@ -57,80 +63,72 @@ const firstApplicableResidual = (values, context) => {
|
|
|
57
63
|
* Gives the residual pair of a node. A shared child is worked again for each
|
|
58
64
|
* place. The limits of the nodes and of a residual keep this work small.
|
|
59
65
|
*/
|
|
60
|
-
const residualNode = (
|
|
61
|
-
const { and, not, or } = build;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
const residualNode = (root, context) => run(root.depth, (R) => {
|
|
67
|
+
const { and, not, or } = context.build;
|
|
68
|
+
const children = (nodes) => R.forEach(nodes, (child) => R.suspend(() => go(child)));
|
|
69
|
+
const go = (node) => {
|
|
70
|
+
switch (node._tag) {
|
|
71
|
+
case "Rules": return R.succeed(rulesResidual(node, context));
|
|
72
|
+
case "All":
|
|
73
|
+
if (node.policies.length === 0) return R.succeed({
|
|
74
|
+
allow: never,
|
|
75
|
+
deny: never
|
|
76
|
+
});
|
|
77
|
+
return R.map(children(node.policies), (pairs) => {
|
|
78
|
+
const deny = or(pairs.map((pair) => pair.deny));
|
|
79
|
+
return {
|
|
80
|
+
allow: and([...pairs.map((pair) => pair.allow), not(deny)]),
|
|
81
|
+
deny
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
case "Any": return R.map(children(node.policies), (pairs) => {
|
|
85
|
+
const allow = or(pairs.map((pair) => pair.allow));
|
|
73
86
|
return {
|
|
74
87
|
allow,
|
|
75
|
-
deny: and([not(allow), or(
|
|
88
|
+
deny: and([not(allow), or(pairs.map((pair) => pair.deny))])
|
|
76
89
|
};
|
|
77
|
-
}
|
|
78
|
-
case "
|
|
79
|
-
case "lastApplicable": return firstApplicableResidual(node.rules.slice().reverse(), context);
|
|
80
|
-
}
|
|
81
|
-
case "All": {
|
|
82
|
-
if (node.policies.length === 0) return {
|
|
90
|
+
});
|
|
91
|
+
case "FirstOf": return R.map(children(node.policies), (pairs) => Arr.reduceRight(pairs, {
|
|
83
92
|
allow: never,
|
|
84
93
|
deny: never
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
94
|
+
}, (rest, pair) => ({
|
|
95
|
+
allow: or([pair.allow, and([not(pair.deny), rest.allow])]),
|
|
96
|
+
deny: or([pair.deny, and([not(pair.allow), rest.deny])])
|
|
97
|
+
})));
|
|
98
|
+
case "Ceiling": return R.flatMap(R.suspend(() => go(node.cap)), (cap) => R.map(R.suspend(() => go(node.policy)), (inner) => ({
|
|
99
|
+
allow: and([not(cap.deny), inner.allow]),
|
|
100
|
+
deny: or([cap.deny, inner.deny])
|
|
101
|
+
})));
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return go(root);
|
|
105
|
+
});
|
|
106
|
+
/** The residual of a list of rules, by its combining algorithm. */
|
|
107
|
+
const rulesResidual = (node, context) => {
|
|
108
|
+
const { and, not, or } = context.build;
|
|
109
|
+
switch (node.algorithm) {
|
|
110
|
+
case "denyOverrides": {
|
|
111
|
+
const deny = or(node.rules.filter((value) => value._tag === "Deny").map((value) => ruleCondition(value, context)));
|
|
88
112
|
return {
|
|
89
|
-
allow: and([
|
|
113
|
+
allow: and([not(deny), or(node.rules.filter((value) => value._tag === "Allow").map((value) => ruleCondition(value, context)))]),
|
|
90
114
|
deny
|
|
91
115
|
};
|
|
92
116
|
}
|
|
93
|
-
case "
|
|
94
|
-
const
|
|
95
|
-
const allow = or(pairs.map((pair) => pair.allow));
|
|
117
|
+
case "permitOverrides": {
|
|
118
|
+
const allow = or(node.rules.filter((value) => value._tag === "Allow").map((value) => ruleCondition(value, context)));
|
|
96
119
|
return {
|
|
97
120
|
allow,
|
|
98
|
-
deny: and([not(allow), or(
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
case "FirstOf": return Arr.reduceRight(node.policies, {
|
|
102
|
-
allow: never,
|
|
103
|
-
deny: never
|
|
104
|
-
}, (rest, child) => {
|
|
105
|
-
const pair = residualNode(child, context);
|
|
106
|
-
return {
|
|
107
|
-
allow: or([pair.allow, and([not(pair.deny), rest.allow])]),
|
|
108
|
-
deny: or([pair.deny, and([not(pair.allow), rest.deny])])
|
|
109
|
-
};
|
|
110
|
-
});
|
|
111
|
-
case "Ceiling": {
|
|
112
|
-
const cap = residualNode(node.cap, context);
|
|
113
|
-
const inner = residualNode(node.policy, context);
|
|
114
|
-
return {
|
|
115
|
-
allow: and([not(cap.deny), inner.allow]),
|
|
116
|
-
deny: or([cap.deny, inner.deny])
|
|
121
|
+
deny: and([not(allow), or(node.rules.filter((value) => value._tag === "Deny").map((value) => ruleCondition(value, context)))])
|
|
117
122
|
};
|
|
118
123
|
}
|
|
124
|
+
case "firstApplicable": return firstApplicableResidual(node.rules, context);
|
|
125
|
+
case "lastApplicable": return firstApplicableResidual(node.rules.slice().reverse(), context);
|
|
119
126
|
}
|
|
120
127
|
};
|
|
121
128
|
/** The name of the predicate of a residual condition that runs `decide`. */
|
|
122
129
|
const policyPredicate = "policy";
|
|
123
130
|
/** Tests if a rule that the request may use has a predicate. */
|
|
124
|
-
const usesPredicate = (node, context) =>
|
|
125
|
-
if (!node.opaque) return false;
|
|
126
|
-
switch (node._tag) {
|
|
127
|
-
case "Rules": return node.rules.some((value) => value.when !== void 0 && toNode(value.when).opaque && matchesTarget(value, context));
|
|
128
|
-
case "All":
|
|
129
|
-
case "Any":
|
|
130
|
-
case "FirstOf": return node.policies.some((child) => usesPredicate(child, context));
|
|
131
|
-
case "Ceiling": return usesPredicate(node.cap, context) || usesPredicate(node.policy, context);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
131
|
+
const usesPredicate = (node, context) => node.opaque && Arr.some(rulesOf(node), (value) => value.when !== void 0 && toNode(value.when).opaque && matchesTarget(value, context));
|
|
134
132
|
/**
|
|
135
133
|
* Makes the scope of a request, with the principal in it. A scope comes from
|
|
136
134
|
* code, so a scope that does not fit the domain is a defect. It is bound as a
|
|
@@ -144,30 +142,35 @@ const scopeOf = (request, domain, input) => {
|
|
|
144
142
|
actions: [request.action],
|
|
145
143
|
resources: [request.kind],
|
|
146
144
|
when: ast
|
|
147
|
-
}, (
|
|
145
|
+
}, at(void 0, "within"));
|
|
148
146
|
if (issues.length > 0) throw new globalThis.Error(`A scope does not fit the domain: ${issues.map((issue) => issue.issue).join("; ")}`);
|
|
149
147
|
return partial(ast, input);
|
|
150
148
|
};
|
|
151
149
|
const residualsAny = (policy, request) => scopedResiduals(policy, request);
|
|
152
150
|
const scopedResiduals = (policy, request) => {
|
|
151
|
+
const limits = policy.domain.limits;
|
|
152
|
+
const parts = limits.fieldPathParts;
|
|
153
153
|
const descriptor = descriptorOf(policy.domain, request.kind);
|
|
154
|
-
if (descriptor === void 0 || !fitsKind(descriptor, request)) return {
|
|
154
|
+
if (descriptor === void 0 || !fitsKind(descriptor, request, parts)) return {
|
|
155
155
|
scope: never,
|
|
156
156
|
allow: never,
|
|
157
157
|
deny: never,
|
|
158
|
-
catalog: void 0
|
|
158
|
+
catalog: void 0,
|
|
159
|
+
limits
|
|
159
160
|
};
|
|
160
161
|
const input = {
|
|
161
162
|
principal: request.principal,
|
|
162
163
|
path: descriptor.path
|
|
163
164
|
};
|
|
164
165
|
const scope = scopeOf(request, policy.domain, input);
|
|
166
|
+
const build = buildFor(limits);
|
|
165
167
|
const and = build.and;
|
|
166
168
|
const inScope = (pair) => ({
|
|
167
169
|
scope,
|
|
168
170
|
allow: and([scope, pair.allow]),
|
|
169
171
|
deny: and([scope, pair.deny]),
|
|
170
|
-
catalog: descriptor.catalog
|
|
172
|
+
catalog: descriptor.catalog,
|
|
173
|
+
limits
|
|
171
174
|
});
|
|
172
175
|
if (scope._tag === "False") return inScope({
|
|
173
176
|
allow: never,
|
|
@@ -179,7 +182,9 @@ const scopedResiduals = (policy, request) => {
|
|
|
179
182
|
field: request.field,
|
|
180
183
|
input,
|
|
181
184
|
descriptor,
|
|
182
|
-
fits: fitsOf(descriptor.catalog)
|
|
185
|
+
fits: fitsOf(descriptor.catalog, parts),
|
|
186
|
+
parts,
|
|
187
|
+
build
|
|
183
188
|
};
|
|
184
189
|
const context = {
|
|
185
190
|
...target,
|
|
@@ -196,9 +201,10 @@ const scopedResiduals = (policy, request) => {
|
|
|
196
201
|
path: descriptor.path
|
|
197
202
|
},
|
|
198
203
|
descriptor,
|
|
199
|
-
fits: fitsOf(descriptor.catalog),
|
|
204
|
+
fits: fitsOf(descriptor.catalog, parts),
|
|
205
|
+
parts,
|
|
200
206
|
trace: void 0
|
|
201
|
-
},
|
|
207
|
+
}, void 0);
|
|
202
208
|
const test = (tag) => builder().predicate(policyPredicate, (value) => kindOf(value) === request.kind ? Effect.map(decision(value), (result) => result._tag === tag) : Effect.succeed(false));
|
|
203
209
|
return inScope({
|
|
204
210
|
allow: test("Allow"),
|
|
@@ -209,7 +215,7 @@ const scopedResiduals = (policy, request) => {
|
|
|
209
215
|
const residual = (policy, request) => residualsAny(policy, request).allow;
|
|
210
216
|
const coverages = Data.taggedEnum();
|
|
211
217
|
/** Searches for a value of the kind of a scope that makes a condition true. */
|
|
212
|
-
const search = (scoped, ast) => satisfiable(ast, kindsIn(scoped.catalog));
|
|
218
|
+
const search = (scoped, ast) => satisfiable(ast, kindsIn(scoped.catalog, scoped.limits.fieldPathParts), scoped.limits.searchSteps, scoped.limits.searchFields);
|
|
213
219
|
/** The values in the scope that the policy does not allow. */
|
|
214
220
|
const refusedOf = (scoped) => and([scoped.scope, not(scoped.allow)]);
|
|
215
221
|
const coverageAny = (policy, request) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analysis.js","names":["Condition.always","Condition.never","Limits.exceeded","Limits.residualNodes","Algebra.and","Algebra.or","Algebra.not","RuleData.matchesTarget","Condition.partial","Nodes.toNode","Condition.builder","Bind.ruleIssues","DomainData.descriptorOf","RuleData.fitsOf","PolicyNodes.rulesOf","ResourceData.kindOf","Search.satisfiable","Search.kindsIn","Decision.Deny","Decision.Allow","Decision.NotApplicable"],"sources":["../../src/internal/analysis.ts"],"sourcesContent":["/**\n * The residual conditions and the coverage of a policy. The types are in\n * `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Data from \"effect/Data\"\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Algebra from \"./algebra.ts\"\nimport * as Bind from \"./bind.ts\"\nimport type * as Catalog from \"./catalog.ts\"\nimport { decideNode, fitsKind } from \"./decide.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\nimport * as Search from \"./search.ts\"\n\ntype Any = Policy.Any\n\ntype Ast = Condition.Ast\n\nconst always: Ast = Condition.always\nconst never: Ast = Condition.never\n\n/**\n * Makes conditions for one residual, with no types. Each step makes the\n * condition simple, and it stops the work when the condition has more nodes\n * than `residualNodes`. A policy tree may use a condition of a child two\n * times, in its allow condition and in its deny condition, so a small policy\n * may give a very large condition.\n */\ninterface Build {\n\t// A list goes in as an array, not as arguments: a call may take only so\n\t// many arguments, and a policy may have more rules.\n\treadonly and: (values: ReadonlyArray<Ast>) => Ast\n\treadonly or: (values: ReadonlyArray<Ast>) => Ast\n\treadonly not: (value: Ast) => Ast\n}\n\nconst limit = (node: Nodes.Node): Ast => {\n\t// A residual grows by its nodes, as the conditions of the rules come\n\t// together. Its values come from the policy, which has a limit of them.\n\tif (node.size > Limits.residualNodes) {\n\t\tthrow Limits.exceeded(\n\t\t\t\"A residual condition\",\n\t\t\t\"residualNodes\",\n\t\t\t\"nodes\",\n\t\t\tnode.size,\n\t\t\tLimits.residualNodes,\n\t\t)\n\t}\n\treturn node\n}\n\nconst build: Build = {\n\tand: (values) => limit(Algebra.and(values as ReadonlyArray<Nodes.Node>)),\n\tor: (values) => limit(Algebra.or(values as ReadonlyArray<Nodes.Node>)),\n\tnot: (value) => (value as Nodes.Node).pipe(Algebra.not, limit),\n}\n\n/** The two residual conditions of a node, with no types. */\ninterface Pair {\n\treadonly allow: Ast\n\treadonly deny: Ast\n}\n\ninterface ResidualContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: {\n\t\treadonly principal: object\n\t\treadonly path: Condition.Input[\"path\"]\n\t}\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this request. */\n\treadonly fits: (pattern: string) => boolean\n\t/**\n\t * The condition of each rule, with the principal in it. They are made\n\t * first, from a flat list of the rules, so the walk does not go down the\n\t * levels of the tree and of a condition at once. It is a native map,\n\t * because it must find a rule by the rule itself: two rules with the same\n\t * data are `Equal`.\n\t */\n\treadonly conditions: Map<RuleData.Rule, Ast>\n}\n\nconst makeCondition = (\n\tvalue: RuleData.Rule,\n\tcontext: Omit<ResidualContext, \"conditions\">,\n): Ast =>\n\t!RuleData.matchesTarget(value, context)\n\t\t? never\n\t\t: value.when === undefined\n\t\t\t? always\n\t\t\t: Condition.partial(value.when as Condition.Any, context.input)\n\nconst ruleCondition = (value: RuleData.Rule, context: ResidualContext): Ast =>\n\tcontext.conditions.get(value) ?? makeCondition(value, context)\n\n/**\n * Gives the conditions for \"the first rule that applies is an allow rule\" and\n * \"the first rule that applies is a deny rule\".\n */\nconst firstApplicableResidual = (\n\tvalues: ReadonlyArray<RuleData.Rule>,\n\tcontext: ResidualContext,\n): Pair => {\n\tconst { and, not, or } = build\n\t// The rules go in runs of one tag, from the last run to the first. The\n\t// conditions of a run go into one node at once. With one node for each\n\t// rule, each step would flatten the whole condition again.\n\tconst runs = Arr.isReadonlyArrayNonEmpty(values)\n\t\t? Arr.groupWith(values, (a, b) => a._tag === b._tag)\n\t\t: []\n\treturn Arr.reduceRight(\n\t\truns,\n\t\t{ allow: never, deny: never },\n\t\t(rest: Pair, run): Pair => {\n\t\t\tconst applies = or(Arr.map(run, (value) => ruleCondition(value, context)))\n\t\t\treturn run[0]._tag === \"Allow\"\n\t\t\t\t? {\n\t\t\t\t\t\tallow: or([applies, rest.allow]),\n\t\t\t\t\t\tdeny: and([not(applies), rest.deny]),\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tallow: and([not(applies), rest.allow]),\n\t\t\t\t\t\tdeny: or([applies, rest.deny]),\n\t\t\t\t\t}\n\t\t},\n\t)\n}\n\n/**\n * Gives the residual pair of a node. A shared child is worked again for each\n * place. The limits of the nodes and of a residual keep this work small.\n */\nconst residualNode = (\n\tnode: PolicyNodes.Node,\n\tcontext: ResidualContext,\n): Pair => {\n\tconst { and, not, or } = build\n\tswitch (node._tag) {\n\t\tcase \"Rules\": {\n\t\t\tswitch (node.algorithm) {\n\t\t\t\tcase \"denyOverrides\": {\n\t\t\t\t\tconst deny = or(\n\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t.filter((value) => value._tag === \"Deny\")\n\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t)\n\t\t\t\t\tconst allow = and([\n\t\t\t\t\t\tnot(deny),\n\t\t\t\t\t\tor(\n\t\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t\t.filter((value) => value._tag === \"Allow\")\n\t\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t\t),\n\t\t\t\t\t])\n\t\t\t\t\treturn { allow, deny }\n\t\t\t\t}\n\t\t\t\tcase \"permitOverrides\": {\n\t\t\t\t\tconst allow = or(\n\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t.filter((value) => value._tag === \"Allow\")\n\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t)\n\t\t\t\t\tconst deny = and([\n\t\t\t\t\t\tnot(allow),\n\t\t\t\t\t\tor(\n\t\t\t\t\t\t\tnode.rules\n\t\t\t\t\t\t\t\t.filter((value) => value._tag === \"Deny\")\n\t\t\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t\t\t),\n\t\t\t\t\t])\n\t\t\t\t\treturn { allow, deny }\n\t\t\t\t}\n\t\t\t\tcase \"firstApplicable\":\n\t\t\t\t\treturn firstApplicableResidual(node.rules, context)\n\t\t\t\tcase \"lastApplicable\":\n\t\t\t\t\treturn firstApplicableResidual(node.rules.slice().reverse(), context)\n\t\t\t}\n\t\t}\n\t\t// falls through: every algorithm returns above\n\t\tcase \"All\": {\n\t\t\tif (node.policies.length === 0) {\n\t\t\t\treturn { allow: never, deny: never }\n\t\t\t}\n\t\t\tconst pairs = node.policies.map((child) => residualNode(child, context))\n\t\t\tconst deny = or(pairs.map((pair) => pair.deny))\n\t\t\tconst allow = and([...pairs.map((pair) => pair.allow), not(deny)])\n\t\t\treturn { allow, deny }\n\t\t}\n\t\tcase \"Any\": {\n\t\t\tconst pairs = node.policies.map((child) => residualNode(child, context))\n\t\t\tconst allow = or(pairs.map((pair) => pair.allow))\n\t\t\tconst deny = and([not(allow), or(pairs.map((pair) => pair.deny))])\n\t\t\treturn { allow, deny }\n\t\t}\n\t\tcase \"FirstOf\": {\n\t\t\t// The first child that gives an answer wins.\n\t\t\treturn Arr.reduceRight(\n\t\t\t\tnode.policies,\n\t\t\t\t{ allow: never, deny: never },\n\t\t\t\t(rest: Pair, child): Pair => {\n\t\t\t\t\tconst pair = residualNode(child, context)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tallow: or([pair.allow, and([not(pair.deny), rest.allow])]),\n\t\t\t\t\t\tdeny: or([pair.deny, and([not(pair.allow), rest.deny])]),\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\t\tcase \"Ceiling\": {\n\t\t\tconst cap = residualNode(node.cap, context)\n\t\t\tconst inner = residualNode(node.policy, context)\n\t\t\treturn {\n\t\t\t\tallow: and([not(cap.deny), inner.allow]),\n\t\t\t\tdeny: or([cap.deny, inner.deny]),\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** The name of the predicate of a residual condition that runs `decide`. */\nconst policyPredicate = \"policy\"\n\n/** What {@link usesPredicate} needs: the request. */\ninterface PredicateContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly descriptor: Resource.Descriptor\n\treadonly fits: (pattern: string) => boolean\n}\n\n/** Tests if a rule that the request may use has a predicate. */\nconst usesPredicate = (\n\tnode: PolicyNodes.Node,\n\tcontext: PredicateContext,\n): boolean => {\n\tif (!node.opaque) {\n\t\treturn false\n\t}\n\tswitch (node._tag) {\n\t\tcase \"Rules\":\n\t\t\treturn node.rules.some(\n\t\t\t\t(value) =>\n\t\t\t\t\tvalue.when !== undefined &&\n\t\t\t\t\tNodes.toNode(value.when).opaque &&\n\t\t\t\t\tRuleData.matchesTarget(value, context),\n\t\t\t)\n\t\tcase \"All\":\n\t\tcase \"Any\":\n\t\tcase \"FirstOf\":\n\t\t\treturn node.policies.some((child) => usesPredicate(child, context))\n\t\tcase \"Ceiling\":\n\t\t\treturn (\n\t\t\t\tusesPredicate(node.cap, context) || usesPredicate(node.policy, context)\n\t\t\t)\n\t}\n}\n\n/**\n * Makes the scope of a request, with the principal in it. A scope comes from\n * code, so a scope that does not fit the domain is a defect. It is bound as a\n * rule for the kind and the action of the request is.\n */\nconst scopeOf = (\n\trequest: ScopedRequest,\n\tdomain: Domain.Any,\n\tinput: ResidualContext[\"input\"],\n): Ast => {\n\tif (request.within === undefined) {\n\t\treturn always\n\t}\n\t// The builder stops a value that a condition can not hold, such as\n\t// `undefined` from a route parameter. A predicate could make a check pass\n\t// on the wrong values, so a scope with one is a defect too.\n\tconst ast = (request.within as (builder: unknown) => Ast)(Condition.builder())\n\tif (Nodes.toNode(ast).opaque) {\n\t\tthrow new globalThis.Error(\"A scope can not have a predicate\")\n\t}\n\tconst issues = Bind.ruleIssues(\n\t\tdomain,\n\t\t{ actions: [request.action], resources: [request.kind], when: ast },\n\t\t() => [\"within\"],\n\t)\n\tif (issues.length > 0) {\n\t\tthrow new globalThis.Error(\n\t\t\t`A scope does not fit the domain: ${issues.map((issue) => issue.issue).join(\"; \")}`,\n\t\t)\n\t}\n\treturn Condition.partial(ast as Condition.Condition, input)\n}\n\n/** A request about a kind with no types, with the scope of a target. */\nexport interface ScopedRequest {\n\treadonly principal: object\n\treadonly action: string\n\treadonly kind: string\n\treadonly field?: string | undefined\n\treadonly within?: unknown\n}\n\n/** The scope of a request, and the residual conditions inside it. */\ninterface Scoped extends Pair {\n\treadonly scope: Ast\n\t/** The catalog of the kind, when the request fits the kind. */\n\treadonly catalog: Catalog.Catalog | undefined\n}\n\nexport const residualsAny = (policy: Any, request: ScopedRequest): Pair =>\n\tscopedResiduals(policy, request)\n\nconst scopedResiduals = (policy: Any, request: ScopedRequest): Scoped => {\n\tconst descriptor = DomainData.descriptorOf(policy.domain, request.kind)\n\tif (descriptor === undefined || !fitsKind(descriptor, request)) {\n\t\treturn { scope: never, allow: never, deny: never, catalog: undefined }\n\t}\n\tconst input = { principal: request.principal, path: descriptor.path }\n\tconst scope = scopeOf(request, policy.domain, input)\n\tconst and = build.and\n\tconst inScope = (pair: Pair): Scoped => ({\n\t\tscope,\n\t\tallow: and([scope, pair.allow]),\n\t\tdeny: and([scope, pair.deny]),\n\t\tcatalog: descriptor.catalog,\n\t})\n\tif (scope._tag === \"False\") {\n\t\treturn inScope({ allow: never, deny: never })\n\t}\n\tconst target = {\n\t\taction: request.action,\n\t\tkind: request.kind,\n\t\tfield: request.field,\n\t\tinput,\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog),\n\t}\n\tconst context: ResidualContext = {\n\t\t...target,\n\t\tconditions: new Map(\n\t\t\tArr.map(policy.pipe(nodeOf, PolicyNodes.rulesOf), (value) => [\n\t\t\t\tvalue,\n\t\t\t\tmakeCondition(value, target),\n\t\t\t]),\n\t\t),\n\t}\n\t// The type of the residual of a plain policy says that it has no\n\t// predicate, so it can not be the predicate below. A residual past the\n\t// limit is a defect.\n\tif (!usesPredicate(nodeOf(policy), context)) {\n\t\treturn inScope(residualNode(nodeOf(policy), context))\n\t}\n\t// A predicate may fail, and it may give another answer each time it runs.\n\t// A tree of `And` and `Or` nodes can not run the predicates in the same\n\t// order and the same number of times as `decide`. So the residual is one\n\t// predicate that runs `decide` for each value.\n\tconst decision = (value: object) =>\n\t\tdecideNode(\n\t\t\tnodeOf(policy),\n\t\t\t{\n\t\t\t\taction: request.action,\n\t\t\t\tkind: request.kind,\n\t\t\t\tfield: request.field,\n\t\t\t\tinput: {\n\t\t\t\t\tvalue,\n\t\t\t\t\tprincipal: request.principal,\n\t\t\t\t\tpath: descriptor.path,\n\t\t\t\t},\n\t\t\t\tdescriptor,\n\t\t\t\tfits: RuleData.fitsOf(descriptor.catalog),\n\t\t\t\ttrace: undefined,\n\t\t\t},\n\t\t\t[],\n\t\t)\n\t// A value of another kind is not a value that the residual is about, so\n\t// the rules of this kind must not run on it.\n\tconst test = (tag: Decision.Decision[\"_tag\"]): Ast =>\n\t\tCondition.builder<any, any, any>().predicate(policyPredicate, (value) =>\n\t\t\tResourceData.kindOf(value) === request.kind\n\t\t\t\t? Effect.map(decision(value), (result) => result._tag === tag)\n\t\t\t\t: Effect.succeed(false),\n\t\t)\n\t// The scope comes first in each `And`, so the policy runs only on values\n\t// in the scope.\n\treturn inScope({ allow: test(\"Allow\"), deny: test(\"Deny\") })\n}\n\n/** Gives the condition on resource values for which the policy allows. */\nexport const residual = (policy: Any, request: ScopedRequest): Ast =>\n\tresidualsAny(policy, request).allow\n\n/** A {@link Policy.Coverage} with no types. */\nexport type AnyCoverage = Data.TaggedEnum<{\n\tAll: { readonly condition: Ast }\n\tNone: { readonly condition: Ast }\n\tSome: {\n\t\treadonly condition: Ast\n\t\treadonly allowed: Condition.Example\n\t\treadonly refused: Condition.Example\n\t}\n\tUnknown: {\n\t\treadonly condition: Ast\n\t\treadonly reason: \"predicate\" | \"budget\"\n\t}\n}>\n\nconst coverages = Data.taggedEnum<AnyCoverage>()\n\n/** Searches for a value of the kind of a scope that makes a condition true. */\nconst search = (scoped: Scoped, ast: Ast): Condition.Satisfiability =>\n\tSearch.satisfiable(ast as Nodes.Node, Search.kindsIn(scoped.catalog))\n\n/** The values in the scope that the policy does not allow. */\nconst refusedOf = (scoped: Scoped): Ast =>\n\t// Only two nodes more than the residual, so it needs no size limit.\n\tAlgebra.and([\n\t\tscoped.scope as Nodes.Node,\n\t\tAlgebra.not(scoped.allow as Nodes.Node),\n\t])\n\nexport const coverageAny = (\n\tpolicy: Any,\n\trequest: ScopedRequest,\n): AnyCoverage => {\n\tconst scoped = scopedResiduals(policy, request)\n\tconst allowed = scoped.allow\n\tconst some = search(scoped, allowed)\n\tif (some._tag === \"Unsatisfiable\") {\n\t\treturn coverages.None({ condition: never })\n\t}\n\tif (some._tag === \"Unknown\") {\n\t\treturn coverages.Unknown({ condition: allowed, reason: some.reason })\n\t}\n\tconst missing = search(scoped, refusedOf(scoped))\n\tif (missing._tag === \"Unsatisfiable\") {\n\t\treturn coverages.All({ condition: scoped.scope })\n\t}\n\tif (missing._tag === \"Unknown\") {\n\t\treturn coverages.Unknown({ condition: allowed, reason: missing.reason })\n\t}\n\treturn coverages.Some({\n\t\tcondition: allowed,\n\t\tallowed: some.example,\n\t\trefused: missing.example,\n\t})\n}\n\n/** Decides a request about every value in a scope. */\nexport const decideKind = (\n\tpolicy: Any,\n\trequest: ScopedRequest,\n): Decision.Decision => {\n\tconst scoped = scopedResiduals(policy, request)\n\tconst missing = search(scoped, refusedOf(scoped))\n\tswitch (missing._tag) {\n\t\tcase \"Unknown\":\n\t\t\treturn Decision.Deny({})\n\t\tcase \"Unsatisfiable\":\n\t\t\treturn search(scoped, scoped.scope)._tag === \"Satisfiable\"\n\t\t\t\t? Decision.Allow({})\n\t\t\t\t: Decision.Deny({})\n\t\tcase \"Satisfiable\":\n\t\t\t// A refused value is in the scope, so the scope has a value.\n\t\t\treturn search(scoped, scoped.deny)._tag === \"Unsatisfiable\"\n\t\t\t\t? Decision.NotApplicable\n\t\t\t\t: Decision.Deny({})\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"analysis.js","names":["Condition.always","Condition.never","Limits.exceeded","Algebra.and","Algebra.or","Algebra.not","RuleData.matchesTarget","Condition.partial","Trampoline.run","PolicyNodes.rulesOf","Nodes.toNode","Condition.builder","Bind.ruleIssues","Place.at","DomainData.descriptorOf","RuleData.fitsOf","ResourceData.kindOf","Search.satisfiable","Search.kindsIn","Decision.Deny","Decision.Allow","Decision.NotApplicable"],"sources":["../../src/internal/analysis.ts"],"sourcesContent":["/**\n * The residual conditions and the coverage of a policy. The types are in\n * `Policy.ts`.\n */\n// A policy keeps the error type and the service type of its predicates in the\n// public types. The code below works on the nodes with no types, where they\n// are `unknown`, so the language service check for `unknown` in an Effect is\n// off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Data from \"effect/Data\"\nimport * as Arr from \"effect/Array\"\nimport * as Effect from \"effect/Effect\"\nimport * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport * as Algebra from \"./algebra.ts\"\nimport * as Bind from \"./bind.ts\"\nimport type * as Catalog from \"./catalog.ts\"\nimport { decideNode, fitsKind } from \"./decide.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Limits from \"./limits.ts\"\nimport * as Nodes from \"./nodes.ts\"\nimport * as Place from \"./place.ts\"\nimport * as PolicyNodes from \"./policy-nodes.ts\"\nimport { nodeOf } from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as RuleData from \"./rule.ts\"\nimport * as Search from \"./search.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype Any = Policy.Any\n\ntype Ast = Condition.Ast\n\nconst always: Ast = Condition.always\nconst never: Ast = Condition.never\n\n/**\n * Makes conditions for one residual, with no types. Each step makes the\n * condition simple, and it stops the work when the condition has more nodes\n * than `residualNodes`. A policy tree may use a condition of a child two\n * times, in its allow condition and in its deny condition, so a small policy\n * may give a very large condition.\n */\ninterface Build {\n\t// A list goes in as an array, not as arguments: a call may take only so\n\t// many arguments, and a policy may have more rules.\n\treadonly and: (values: ReadonlyArray<Ast>) => Ast\n\treadonly or: (values: ReadonlyArray<Ast>) => Ast\n\treadonly not: (value: Ast) => Ast\n}\n\n/** The steps of a residual for a domain with these limits. */\nconst buildFor = (limits: Limits.Limits): Build => {\n\tconst most = limits.residualNodes\n\tconst limit = (node: Nodes.Node): Ast => {\n\t\t// A residual grows by its nodes, as the conditions of the rules come\n\t\t// together. Its values come from the policy, which has a limit of them.\n\t\tif (node.size > most) {\n\t\t\tthrow Limits.exceeded(\n\t\t\t\t\"A residual condition\",\n\t\t\t\t\"residualNodes\",\n\t\t\t\t\"nodes\",\n\t\t\t\tnode.size,\n\t\t\t\tmost,\n\t\t\t)\n\t\t}\n\t\treturn node\n\t}\n\treturn {\n\t\tand: (values) => limit(Algebra.and(values as ReadonlyArray<Nodes.Node>)),\n\t\tor: (values) => limit(Algebra.or(values as ReadonlyArray<Nodes.Node>)),\n\t\tnot: (value) => (value as Nodes.Node).pipe(Algebra.not, limit),\n\t}\n}\n\n/** The two residual conditions of a node, with no types. */\ninterface Pair {\n\treadonly allow: Ast\n\treadonly deny: Ast\n}\n\ninterface ResidualContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly input: {\n\t\treadonly principal: object\n\t\treadonly path: Condition.Input[\"path\"]\n\t}\n\t/** The resource of the kind, for the fields that a rule may grant. */\n\treadonly descriptor: Resource.Descriptor\n\t/** The test of `RuleData.fitsOf` for the catalog, for this request. */\n\treadonly fits: (pattern: string) => boolean\n\t/** The most parts of a field path in the domain. */\n\treadonly parts: number\n\t/** The steps of a residual, with the limits of the domain. */\n\treadonly build: Build\n\t/**\n\t * The condition of each rule, with the principal in it. They are made\n\t * first, from a flat list of the rules, so the walk does not go down the\n\t * levels of the tree and of a condition at once. It is a native map,\n\t * because it must find a rule by the rule itself: two rules with the same\n\t * data are `Equal`.\n\t */\n\treadonly conditions: Map<RuleData.Rule, Ast>\n}\n\nconst makeCondition = (\n\tvalue: RuleData.Rule,\n\tcontext: Omit<ResidualContext, \"conditions\">,\n): Ast =>\n\t!RuleData.matchesTarget(value, context)\n\t\t? never\n\t\t: value.when === undefined\n\t\t\t? always\n\t\t\t: Condition.partial(value.when as Condition.Any, context.input)\n\nconst ruleCondition = (value: RuleData.Rule, context: ResidualContext): Ast =>\n\tcontext.conditions.get(value) ?? makeCondition(value, context)\n\n/**\n * Gives the conditions for \"the first rule that applies is an allow rule\" and\n * \"the first rule that applies is a deny rule\".\n */\nconst firstApplicableResidual = (\n\tvalues: ReadonlyArray<RuleData.Rule>,\n\tcontext: ResidualContext,\n): Pair => {\n\tconst { and, not, or } = context.build\n\t// The rules go in runs of one tag, from the last run to the first. The\n\t// conditions of a run go into one node at once. With one node for each\n\t// rule, each step would flatten the whole condition again.\n\tconst runs = Arr.isReadonlyArrayNonEmpty(values)\n\t\t? Arr.groupWith(values, (a, b) => a._tag === b._tag)\n\t\t: []\n\treturn Arr.reduceRight(\n\t\truns,\n\t\t{ allow: never, deny: never },\n\t\t(rest: Pair, run): Pair => {\n\t\t\tconst applies = or(Arr.map(run, (value) => ruleCondition(value, context)))\n\t\t\treturn run[0]._tag === \"Allow\"\n\t\t\t\t? {\n\t\t\t\t\t\tallow: or([applies, rest.allow]),\n\t\t\t\t\t\tdeny: and([not(applies), rest.deny]),\n\t\t\t\t\t}\n\t\t\t\t: {\n\t\t\t\t\t\tallow: and([not(applies), rest.allow]),\n\t\t\t\t\t\tdeny: or([applies, rest.deny]),\n\t\t\t\t\t}\n\t\t},\n\t)\n}\n\n/**\n * Gives the residual pair of a node. A shared child is worked again for each\n * place. The limits of the nodes and of a residual keep this work small.\n */\nconst residualNode = (root: PolicyNodes.Node, context: ResidualContext): Pair =>\n\t// The walk of the tree takes the steps of a trampoline, so a deep tree\n\t// does not fill the stack.\n\tTrampoline.run(root.depth, (R) => {\n\t\tconst { and, not, or } = context.build\n\t\tconst children = (nodes: ReadonlyArray<PolicyNodes.Node>) =>\n\t\t\tR.forEach(nodes, (child) => R.suspend(() => go(child)))\n\t\tconst go = (node: PolicyNodes.Node): Trampoline.Step<Pair> => {\n\t\t\tswitch (node._tag) {\n\t\t\t\tcase \"Rules\":\n\t\t\t\t\treturn R.succeed(rulesResidual(node, context))\n\t\t\t\tcase \"All\":\n\t\t\t\t\tif (node.policies.length === 0) {\n\t\t\t\t\t\treturn R.succeed({ allow: never, deny: never })\n\t\t\t\t\t}\n\t\t\t\t\treturn R.map(children(node.policies), (pairs) => {\n\t\t\t\t\t\tconst deny = or(pairs.map((pair) => pair.deny))\n\t\t\t\t\t\tconst allow = and([...pairs.map((pair) => pair.allow), not(deny)])\n\t\t\t\t\t\treturn { allow, deny }\n\t\t\t\t\t})\n\t\t\t\tcase \"Any\":\n\t\t\t\t\treturn R.map(children(node.policies), (pairs) => {\n\t\t\t\t\t\tconst allow = or(pairs.map((pair) => pair.allow))\n\t\t\t\t\t\tconst deny = and([not(allow), or(pairs.map((pair) => pair.deny))])\n\t\t\t\t\t\treturn { allow, deny }\n\t\t\t\t\t})\n\t\t\t\tcase \"FirstOf\":\n\t\t\t\t\t// The first child that gives an answer wins.\n\t\t\t\t\treturn R.map(children(node.policies), (pairs) =>\n\t\t\t\t\t\tArr.reduceRight(\n\t\t\t\t\t\t\tpairs,\n\t\t\t\t\t\t\t{ allow: never, deny: never },\n\t\t\t\t\t\t\t(rest: Pair, pair): Pair => ({\n\t\t\t\t\t\t\t\tallow: or([pair.allow, and([not(pair.deny), rest.allow])]),\n\t\t\t\t\t\t\t\tdeny: or([pair.deny, and([not(pair.allow), rest.deny])]),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\tcase \"Ceiling\":\n\t\t\t\t\treturn R.flatMap(\n\t\t\t\t\t\tR.suspend(() => go(node.cap)),\n\t\t\t\t\t\t(cap) =>\n\t\t\t\t\t\t\tR.map(\n\t\t\t\t\t\t\t\tR.suspend(() => go(node.policy)),\n\t\t\t\t\t\t\t\t(inner) => ({\n\t\t\t\t\t\t\t\t\tallow: and([not(cap.deny), inner.allow]),\n\t\t\t\t\t\t\t\t\tdeny: or([cap.deny, inner.deny]),\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\treturn go(root)\n\t})\n\n/** The residual of a list of rules, by its combining algorithm. */\nconst rulesResidual = (\n\tnode: PolicyNodes.Rules,\n\tcontext: ResidualContext,\n): Pair => {\n\tconst { and, not, or } = context.build\n\tswitch (node.algorithm) {\n\t\tcase \"denyOverrides\": {\n\t\t\tconst deny = or(\n\t\t\t\tnode.rules\n\t\t\t\t\t.filter((value) => value._tag === \"Deny\")\n\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t)\n\t\t\tconst allow = and([\n\t\t\t\tnot(deny),\n\t\t\t\tor(\n\t\t\t\t\tnode.rules\n\t\t\t\t\t\t.filter((value) => value._tag === \"Allow\")\n\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t),\n\t\t\t])\n\t\t\treturn { allow, deny }\n\t\t}\n\t\tcase \"permitOverrides\": {\n\t\t\tconst allow = or(\n\t\t\t\tnode.rules\n\t\t\t\t\t.filter((value) => value._tag === \"Allow\")\n\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t)\n\t\t\tconst deny = and([\n\t\t\t\tnot(allow),\n\t\t\t\tor(\n\t\t\t\t\tnode.rules\n\t\t\t\t\t\t.filter((value) => value._tag === \"Deny\")\n\t\t\t\t\t\t.map((value) => ruleCondition(value, context)),\n\t\t\t\t),\n\t\t\t])\n\t\t\treturn { allow, deny }\n\t\t}\n\t\tcase \"firstApplicable\":\n\t\t\treturn firstApplicableResidual(node.rules, context)\n\t\tcase \"lastApplicable\":\n\t\t\treturn firstApplicableResidual(node.rules.slice().reverse(), context)\n\t}\n}\n\n/** The name of the predicate of a residual condition that runs `decide`. */\nconst policyPredicate = \"policy\"\n\n/** What {@link usesPredicate} needs: the request. */\ninterface PredicateContext {\n\treadonly action: string\n\treadonly kind: string\n\treadonly field: string | undefined\n\treadonly descriptor: Resource.Descriptor\n\treadonly fits: (pattern: string) => boolean\n}\n\n/** Tests if a rule that the request may use has a predicate. */\nconst usesPredicate = (\n\tnode: PolicyNodes.Node,\n\tcontext: PredicateContext,\n): boolean =>\n\tnode.opaque &&\n\tArr.some(\n\t\tPolicyNodes.rulesOf(node),\n\t\t(value) =>\n\t\t\tvalue.when !== undefined &&\n\t\t\tNodes.toNode(value.when).opaque &&\n\t\t\tRuleData.matchesTarget(value, context),\n\t)\n\n/**\n * Makes the scope of a request, with the principal in it. A scope comes from\n * code, so a scope that does not fit the domain is a defect. It is bound as a\n * rule for the kind and the action of the request is.\n */\nconst scopeOf = (\n\trequest: ScopedRequest,\n\tdomain: Domain.Any,\n\tinput: ResidualContext[\"input\"],\n): Ast => {\n\tif (request.within === undefined) {\n\t\treturn always\n\t}\n\t// The builder stops a value that a condition can not hold, such as\n\t// `undefined` from a route parameter. A predicate could make a check pass\n\t// on the wrong values, so a scope with one is a defect too.\n\tconst ast = (request.within as (builder: unknown) => Ast)(Condition.builder())\n\tif (Nodes.toNode(ast).opaque) {\n\t\tthrow new globalThis.Error(\"A scope can not have a predicate\")\n\t}\n\tconst issues = Bind.ruleIssues(\n\t\tdomain,\n\t\t{ actions: [request.action], resources: [request.kind], when: ast },\n\t\tPlace.at(undefined, \"within\"),\n\t)\n\tif (issues.length > 0) {\n\t\tthrow new globalThis.Error(\n\t\t\t`A scope does not fit the domain: ${issues.map((issue) => issue.issue).join(\"; \")}`,\n\t\t)\n\t}\n\treturn Condition.partial(ast as Condition.Condition, input)\n}\n\n/** A request about a kind with no types, with the scope of a target. */\nexport interface ScopedRequest {\n\treadonly principal: object\n\treadonly action: string\n\treadonly kind: string\n\treadonly field?: string | undefined\n\treadonly within?: unknown\n}\n\n/** The scope of a request, and the residual conditions inside it. */\ninterface Scoped extends Pair {\n\treadonly scope: Ast\n\t/** The catalog of the kind, when the request fits the kind. */\n\treadonly catalog: Catalog.Catalog | undefined\n\t/** The limits of the domain. */\n\treadonly limits: Limits.Limits\n}\n\nexport const residualsAny = (policy: Any, request: ScopedRequest): Pair =>\n\tscopedResiduals(policy, request)\n\nconst scopedResiduals = (policy: Any, request: ScopedRequest): Scoped => {\n\tconst limits = policy.domain.limits\n\tconst parts = limits.fieldPathParts\n\tconst descriptor = DomainData.descriptorOf(policy.domain, request.kind)\n\tif (descriptor === undefined || !fitsKind(descriptor, request, parts)) {\n\t\treturn {\n\t\t\tscope: never,\n\t\t\tallow: never,\n\t\t\tdeny: never,\n\t\t\tcatalog: undefined,\n\t\t\tlimits,\n\t\t}\n\t}\n\tconst input = { principal: request.principal, path: descriptor.path }\n\tconst scope = scopeOf(request, policy.domain, input)\n\tconst build = buildFor(limits)\n\tconst and = build.and\n\tconst inScope = (pair: Pair): Scoped => ({\n\t\tscope,\n\t\tallow: and([scope, pair.allow]),\n\t\tdeny: and([scope, pair.deny]),\n\t\tcatalog: descriptor.catalog,\n\t\tlimits,\n\t})\n\tif (scope._tag === \"False\") {\n\t\treturn inScope({ allow: never, deny: never })\n\t}\n\tconst target = {\n\t\taction: request.action,\n\t\tkind: request.kind,\n\t\tfield: request.field,\n\t\tinput,\n\t\tdescriptor,\n\t\tfits: RuleData.fitsOf(descriptor.catalog, parts),\n\t\tparts,\n\t\tbuild,\n\t}\n\tconst context: ResidualContext = {\n\t\t...target,\n\t\tconditions: new Map(\n\t\t\tArr.map(policy.pipe(nodeOf, PolicyNodes.rulesOf), (value) => [\n\t\t\t\tvalue,\n\t\t\t\tmakeCondition(value, target),\n\t\t\t]),\n\t\t),\n\t}\n\t// The type of the residual of a plain policy says that it has no\n\t// predicate, so it can not be the predicate below. A residual past the\n\t// limit is a defect.\n\tif (!usesPredicate(nodeOf(policy), context)) {\n\t\treturn inScope(residualNode(nodeOf(policy), context))\n\t}\n\t// A predicate may fail, and it may give another answer each time it runs.\n\t// A tree of `And` and `Or` nodes can not run the predicates in the same\n\t// order and the same number of times as `decide`. So the residual is one\n\t// predicate that runs `decide` for each value.\n\tconst decision = (value: object) =>\n\t\tdecideNode(\n\t\t\tnodeOf(policy),\n\t\t\t{\n\t\t\t\taction: request.action,\n\t\t\t\tkind: request.kind,\n\t\t\t\tfield: request.field,\n\t\t\t\tinput: {\n\t\t\t\t\tvalue,\n\t\t\t\t\tprincipal: request.principal,\n\t\t\t\t\tpath: descriptor.path,\n\t\t\t\t},\n\t\t\t\tdescriptor,\n\t\t\t\tfits: RuleData.fitsOf(descriptor.catalog, parts),\n\t\t\t\tparts,\n\t\t\t\ttrace: undefined,\n\t\t\t},\n\t\t\tundefined,\n\t\t)\n\t// A value of another kind is not a value that the residual is about, so\n\t// the rules of this kind must not run on it.\n\tconst test = (tag: Decision.Decision[\"_tag\"]): Ast =>\n\t\tCondition.builder<any, any, any>().predicate(policyPredicate, (value) =>\n\t\t\tResourceData.kindOf(value) === request.kind\n\t\t\t\t? Effect.map(decision(value), (result) => result._tag === tag)\n\t\t\t\t: Effect.succeed(false),\n\t\t)\n\t// The scope comes first in each `And`, so the policy runs only on values\n\t// in the scope.\n\treturn inScope({ allow: test(\"Allow\"), deny: test(\"Deny\") })\n}\n\n/** Gives the condition on resource values for which the policy allows. */\nexport const residual = (policy: Any, request: ScopedRequest): Ast =>\n\tresidualsAny(policy, request).allow\n\n/** A {@link Policy.Coverage} with no types. */\nexport type AnyCoverage = Data.TaggedEnum<{\n\tAll: { readonly condition: Ast }\n\tNone: { readonly condition: Ast }\n\tSome: {\n\t\treadonly condition: Ast\n\t\treadonly allowed: Condition.Example\n\t\treadonly refused: Condition.Example\n\t}\n\tUnknown: {\n\t\treadonly condition: Ast\n\t\treadonly reason: \"predicate\" | \"budget\"\n\t}\n}>\n\nconst coverages = Data.taggedEnum<AnyCoverage>()\n\n/** Searches for a value of the kind of a scope that makes a condition true. */\nconst search = (scoped: Scoped, ast: Ast): Condition.Satisfiability =>\n\tSearch.satisfiable(\n\t\tast as Nodes.Node,\n\t\tSearch.kindsIn(scoped.catalog, scoped.limits.fieldPathParts),\n\t\tscoped.limits.searchSteps,\n\t\tscoped.limits.searchFields,\n\t)\n\n/** The values in the scope that the policy does not allow. */\nconst refusedOf = (scoped: Scoped): Ast =>\n\t// Only two nodes more than the residual, so it needs no size limit.\n\tAlgebra.and([\n\t\tscoped.scope as Nodes.Node,\n\t\tAlgebra.not(scoped.allow as Nodes.Node),\n\t])\n\nexport const coverageAny = (\n\tpolicy: Any,\n\trequest: ScopedRequest,\n): AnyCoverage => {\n\tconst scoped = scopedResiduals(policy, request)\n\tconst allowed = scoped.allow\n\tconst some = search(scoped, allowed)\n\tif (some._tag === \"Unsatisfiable\") {\n\t\treturn coverages.None({ condition: never })\n\t}\n\tif (some._tag === \"Unknown\") {\n\t\treturn coverages.Unknown({ condition: allowed, reason: some.reason })\n\t}\n\tconst missing = search(scoped, refusedOf(scoped))\n\tif (missing._tag === \"Unsatisfiable\") {\n\t\treturn coverages.All({ condition: scoped.scope })\n\t}\n\tif (missing._tag === \"Unknown\") {\n\t\treturn coverages.Unknown({ condition: allowed, reason: missing.reason })\n\t}\n\treturn coverages.Some({\n\t\tcondition: allowed,\n\t\tallowed: some.example,\n\t\trefused: missing.example,\n\t})\n}\n\n/** Decides a request about every value in a scope. */\nexport const decideKind = (\n\tpolicy: Any,\n\trequest: ScopedRequest,\n): Decision.Decision => {\n\tconst scoped = scopedResiduals(policy, request)\n\tconst missing = search(scoped, refusedOf(scoped))\n\tswitch (missing._tag) {\n\t\tcase \"Unknown\":\n\t\t\treturn Decision.Deny({})\n\t\tcase \"Unsatisfiable\":\n\t\t\treturn search(scoped, scoped.scope)._tag === \"Satisfiable\"\n\t\t\t\t? Decision.Allow({})\n\t\t\t\t: Decision.Deny({})\n\t\tcase \"Satisfiable\":\n\t\t\t// A refused value is in the scope, so the scope has a value.\n\t\t\treturn search(scoped, scoped.deny)._tag === \"Unsatisfiable\"\n\t\t\t\t? Decision.NotApplicable\n\t\t\t\t: Decision.Deny({})\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorizer.d.ts","names":[],"sources":["../../src/internal/authorizer.ts"],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"authorizer.d.ts","names":[],"sources":["../../src/internal/authorizer.ts"],"mappings":";;;;;;;;;;;;;;;;qBAoCa,kBAAkB;;;;;qBAUlB,0BAA0B;;;;;;qBAK1B,gBAAgB"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { reason } from "../Decision.js";
|
|
2
|
+
import { adaptive } from "./trampoline.js";
|
|
2
3
|
import { kindOf } from "./resource.js";
|
|
3
4
|
import { descriptorOf } from "./domain.js";
|
|
4
5
|
import { provideContext } from "./policy.js";
|
|
5
6
|
import { decideAny, fitsKind, permittedFields } from "./decide.js";
|
|
6
7
|
import { coverageAny, decideKind } from "./analysis.js";
|
|
8
|
+
import * as Arr from "effect/Array";
|
|
7
9
|
import * as Cache from "effect/Cache";
|
|
8
10
|
import * as Chunk from "effect/Chunk";
|
|
9
11
|
import * as Context from "effect/Context";
|
|
@@ -87,12 +89,14 @@ var PrincipalKey = class PrincipalKey {
|
|
|
87
89
|
* `Object.values` does. So two keys need the same order too. A scalar is in
|
|
88
90
|
* the text with its type, so the items of a set or a map show their order.
|
|
89
91
|
*/
|
|
90
|
-
const orderOf = (value) => {
|
|
91
|
-
if (typeof value !== "object" || value === null) return typeof value === "string" ? JSON.stringify(value) : `${typeof value}:${String(value)}
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
const orderOf = adaptive((R, child) => (value, depth) => {
|
|
93
|
+
if (typeof value !== "object" || value === null) return R.succeed(typeof value === "string" ? JSON.stringify(value) : `${typeof value}:${String(value)}`);
|
|
94
|
+
const items = (list) => R.forEach(list, (item) => child(item, depth + 1));
|
|
95
|
+
if (Array.isArray(value)) return R.map(items(value), (texts) => `[${texts.join(",")}]`);
|
|
96
|
+
if (value instanceof Map || value instanceof Set || HashMap.isHashMap(value) || HashSet.isHashSet(value) || Chunk.isChunk(value)) return R.map(items(Array.from(value)), (texts) => `<${texts.join(",")}>`);
|
|
97
|
+
const names = Object.keys(value);
|
|
98
|
+
return R.map(items(Arr.map(names, (name) => value[name])), (texts) => `{${Arr.map(names, (name, index) => `${JSON.stringify(name)}:${texts[index]}`).join(",")}}`);
|
|
99
|
+
});
|
|
96
100
|
/**
|
|
97
101
|
* Gives the JSON codec of a Schema, or `undefined` when the Schema has no JSON
|
|
98
102
|
* form, as a struct with a symbol key has none.
|
|
@@ -186,7 +190,7 @@ const makeLayer = (service, options) => {
|
|
|
186
190
|
const checkWith = Effect.fnUntraced(function* ({ principal, policy: value }, target) {
|
|
187
191
|
const kind = target.resource === void 0 ? target.kind ?? "" : kindOf(target.resource) ?? "";
|
|
188
192
|
const descriptor = descriptorOf(domain, kind);
|
|
189
|
-
if (descriptor === void 0 || !fitsKind(descriptor, target)) return yield* forbidden(target, kind, void 0);
|
|
193
|
+
if (descriptor === void 0 || !fitsKind(descriptor, target, domain.limits.fieldPathParts)) return yield* forbidden(target, kind, void 0);
|
|
190
194
|
const fromPolicy = target.resource === void 0 ? decideKind(value, {
|
|
191
195
|
...target,
|
|
192
196
|
principal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorizer.js","names":["Policies.provideContext","Decide.decideAny","ResourceData.kindOf","DomainData.descriptorOf","Decide.fitsKind","Analysis.decideKind","Decision.reason","Analysis.coverageAny","permittedFields","Decide.permittedFields"],"sources":["../../src/internal/authorizer.ts"],"sourcesContent":["/**\n * The values of authorizers: the errors, the service class and the layer.\n * The types are in `Authorizer.ts`.\n */\n// The service erases the error type and the service type of predicates,\n// principals and deciders to `unknown`. The public types keep them, so the\n// language service check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Cache from \"effect/Cache\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Context from \"effect/Context\"\nimport type * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as Equal from \"effect/Equal\"\nimport * as Hash from \"effect/Hash\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as Layer from \"effect/Layer\"\nimport * as Result from \"effect/Result\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Authorizer from \"../Authorizer.ts\"\nimport type * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport * as Analysis from \"./analysis.ts\"\nimport * as Decide from \"./decide.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Policies from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\n\ntype AnyServiceClass = Authorizer.AnyServiceClass\n\n/** The error when a policy says no. */\nexport class Forbidden extends Schema.TaggedError<Forbidden>(\n\t\"@nmnmcc/ability/Authorizer/Forbidden\",\n)(\"Forbidden\", {\n\taction: Schema.String,\n\tkind: Schema.String,\n\tfield: Schema.optionalKey(Schema.String),\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/** The error when the policy of the principal can not be loaded. */\nexport class PolicyUnavailable extends Schema.TaggedError<PolicyUnavailable>(\n\t\"@nmnmcc/ability/Authorizer/PolicyUnavailable\",\n)(\"PolicyUnavailable\", { cause: Schema.Defect() }) {}\n\n/** The error when a decider must wait for an answer. */\nexport class Pending extends Schema.TaggedError<Pending>(\n\t\"@nmnmcc/ability/Authorizer/Pending\",\n)(\"Pending\", {\n\tticket: Schema.String,\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/** Makes an authorizer service class for a domain. */\nexport const Service =\n\t() =>\n\t(\n\t\tid: string,\n\t\toptions: {\n\t\t\treadonly domain: Domain.Any\n\t\t\treadonly principal: Effect.Effect<any, any, any>\n\t\t},\n\t\t// The class has no name, and its type uses types of Effect that a\n\t\t// declaration file can not name. So this function gives a `Context.Key`.\n\t\t// The public module gives the full type.\n\t): Context.Key<any, any> => {\n\t\tconst Base = Context.Service<any, any>()(id)\n\t\treturn class extends Base {\n\t\t\tstatic readonly domain = options.domain\n\t\t\tstatic readonly principal = options.principal\n\t\t\tstatic readonly decide = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.decide(target))\n\t\t\tstatic readonly check = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.check(target))\n\t\t\tstatic readonly guard = (target: AnyTarget, options: unknown) =>\n\t\t\t\tthis.use((service: any) => service.guard(target, options))\n\t\t\tstatic readonly filter = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.filter(target))\n\t\t\tstatic readonly coverage = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.coverage(target))\n\t\t\tstatic readonly permittedFields = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.permittedFields(target))\n\t\t}\n\t}\n\ntype AnyTarget = {\n\treadonly action: string\n\treadonly resource?: object\n\treadonly kind?: string\n\treadonly field?: string\n\treadonly within?: unknown\n}\n\n/**\n * A cache key for a principal. Two keys are equal when they have the same\n * text, so two principals with the same key share one policy. A key from the\n * JSON form also needs equal decoded principals: a Schema may make data when\n * it decodes, as `expired` from a time, so one text may give two principals.\n * The key also keeps the principal, so the cache can load its policy. A plain\n * `Data` value can not do all this, so the key gives its own equality with\n * `Equal.Equal`.\n */\nclass PrincipalKey implements Equal.Equal {\n\treadonly principal: object\n\treadonly text: string\n\t/**\n\t * The order of the parts of the decoded principal, from {@link orderOf},\n\t * when the key is the JSON form. The decoded principals must be equal\n\t * too, with their parts in the same order. `undefined` for a key from\n\t * `cache.key`.\n\t */\n\treadonly order: string | undefined\n\tconstructor(principal: object, text: string, order: string | undefined) {\n\t\tthis.principal = principal\n\t\tthis.text = text\n\t\tthis.order = order\n\t}\n\t[Equal.symbol](that: unknown): boolean {\n\t\treturn (\n\t\t\tthat instanceof PrincipalKey &&\n\t\t\tthat.text === this.text &&\n\t\t\tthat.order === this.order &&\n\t\t\t(this.order === undefined || Equal.equals(that.principal, this.principal))\n\t\t)\n\t}\n\t[Hash.symbol](): number {\n\t\treturn Hash.string(this.text)\n\t}\n}\n\n/**\n * The text of the order of the parts of a value: the names of the fields of\n * each object, and the items of each list, set and map, with each scalar, in\n * the order that a loop sees them. `Equal` does not see the order of the\n * fields of an object or of the items of a map, but a loader may read it, as\n * `Object.values` does. So two keys need the same order too. A scalar is in\n * the text with its type, so the items of a set or a map show their order.\n */\nconst orderOf = (value: unknown): string => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn typeof value === \"string\"\n\t\t\t? JSON.stringify(value)\n\t\t\t: `${typeof value}:${String(value)}`\n\t}\n\tif (Array.isArray(value)) {\n\t\treturn `[${value.map(orderOf).join(\",\")}]`\n\t}\n\tif (\n\t\tvalue instanceof Map ||\n\t\tvalue instanceof Set ||\n\t\tHashMap.isHashMap(value) ||\n\t\tHashSet.isHashSet(value) ||\n\t\tChunk.isChunk(value)\n\t) {\n\t\treturn `<${Array.from(value as Iterable<unknown>, orderOf).join(\",\")}>`\n\t}\n\treturn `{${Object.keys(value)\n\t\t.map(\n\t\t\t(name) =>\n\t\t\t\t`${JSON.stringify(name)}:${orderOf((value as Record<string, unknown>)[name])}`,\n\t\t)\n\t\t.join(\",\")}}`\n}\n\n/**\n * Gives the JSON codec of a Schema, or `undefined` when the Schema has no JSON\n * form, as a struct with a symbol key has none.\n */\nconst jsonCodecOf = (\n\tschema: Schema.Top,\n): Schema.Codec<unknown, unknown> | undefined => {\n\ttry {\n\t\treturn Schema.toCodecJson(schema) as unknown as Schema.Codec<\n\t\t\tunknown,\n\t\t\tunknown\n\t\t>\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * Makes the cache key of a principal from its JSON form. The key keeps the\n * principal that the Schema decodes from that form, so the loader sees only\n * the data that the key shows. A principal gets no key when it does not\n * encode, or when the decoded value is not `Equal` to it. Two classes, or a\n * string and a number, may have one JSON form, as in a union, and `Equal`\n * tells them apart. A Schema may make data when it decodes, as the order of\n * the fields of a record, so the key keeps that order too. So two\n * principals with one key are equal, and the cache can not give one the\n * policy of the other.\n */\nconst jsonKey = (\n\tschema: Schema.Top,\n): ((principal: object) => PrincipalKey | undefined) => {\n\tconst codec = jsonCodecOf(schema)\n\t// A principal with no JSON form is never kept.\n\tif (codec === undefined) {\n\t\treturn () => undefined\n\t}\n\tconst encode = Schema.encodeUnknownResult(codec)\n\tconst decode = Schema.decodeUnknownResult(codec)\n\tconst keyOf = (principal: object): PrincipalKey | undefined =>\n\t\tResult.match(encode(principal), {\n\t\t\tonFailure: () => undefined,\n\t\t\tonSuccess: (json) => {\n\t\t\t\t// The key is the text, so the check decodes the text. The encoded\n\t\t\t\t// value may hold more than its text shows, as an object of a\n\t\t\t\t// `Schema.Any` field does: `JSON.stringify` leaves out a map.\n\t\t\t\tconst text = JSON.stringify(json)\n\t\t\t\treturn Result.match(decode(JSON.parse(text)), {\n\t\t\t\t\tonFailure: () => undefined,\n\t\t\t\t\tonSuccess: (decoded) =>\n\t\t\t\t\t\tEqual.equals(decoded, principal)\n\t\t\t\t\t\t\t? new PrincipalKey(decoded as object, text, orderOf(decoded))\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t})\n\t\t\t},\n\t\t})\n\treturn (principal) => {\n\t\t// A very deep principal may fill the stack of the encoder or of\n\t\t// `Equal`. Such a principal is not kept.\n\t\ttry {\n\t\t\treturn keyOf(principal)\n\t\t} catch {\n\t\t\treturn undefined\n\t\t}\n\t}\n}\n\nconst forbidden = (\n\ttarget: AnyTarget,\n\tkind: string,\n\treason: string | undefined,\n): Forbidden =>\n\tForbidden.make({\n\t\taction: target.action,\n\t\tkind,\n\t\t...(target.field === undefined ? {} : { field: target.field }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t})\n\n/** Makes the layer of an authorizer. */\nexport const makeLayer = (\n\tservice: AnyServiceClass,\n\toptions: {\n\t\treadonly policy: (\n\t\t\tprincipal: object,\n\t\t) => Effect.Effect<Policy.Any, unknown, unknown>\n\t\treadonly decider?: (\n\t\t\trequest: object,\n\t\t) => Effect.Effect<Decision.Decision, unknown, unknown>\n\t\treadonly cache?:\n\t\t\t| {\n\t\t\t\t\treadonly capacity?: number\n\t\t\t\t\treadonly timeToLive?: Duration.Input\n\t\t\t\t\treadonly key?: (principal: object) => string | undefined\n\t\t\t }\n\t\t\t| false\n\t},\n) => {\n\tconst domain: Domain.Any = service.domain\n\t// The JSON form of a principal, and the copy that the Schema decodes from\n\t// it. The loader always gets the copy, with a cache or not, so a cache can\n\t// not change an answer: a form may lose data that `Equal` does not see,\n\t// as `-0` becomes `0`. A principal with no such form gives `undefined`.\n\tconst canonicalOf = jsonKey(domain.principal)\n\tconst custom = options.cache === false ? undefined : options.cache?.key\n\t// It gives `undefined` when the principal has no key. Such a principal does\n\t// not use the cache.\n\tconst keyOf = (\n\t\tprincipal: object,\n\t\tcanonical: PrincipalKey | undefined,\n\t): PrincipalKey | undefined => {\n\t\tif (custom === undefined) {\n\t\t\treturn canonical\n\t\t}\n\t\tconst text = custom(principal)\n\t\treturn text === undefined\n\t\t\t? undefined\n\t\t\t: new PrincipalKey(canonical?.principal ?? principal, text, undefined)\n\t}\n\treturn Layer.effect(\n\t\tservice,\n\t\tEffect.gen(function* () {\n\t\t\t// The services of the policy loader and of the predicates come from the\n\t\t\t// layer, so a check does not need them.\n\t\t\tconst services = yield* Effect.context<never>()\n\n\t\t\t// The predicates of the policy get the services of the layer, so the\n\t\t\t// policy and its residual conditions need no services.\n\t\t\t// A policy of another domain may have the same types, but its resources\n\t\t\t// may have other paths. So it fails closed, as a policy that can not be\n\t\t\t// loaded.\n\t\t\tconst load = (principal: object) =>\n\t\t\t\toptions.policy(principal).pipe(\n\t\t\t\t\tEffect.provideContext(services),\n\t\t\t\t\tEffect.filterOrFail(\n\t\t\t\t\t\t(value) => value.domain === domain,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew globalThis.Error(\n\t\t\t\t\t\t\t\t\"The loaded policy is not a policy of this domain\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((cause) => PolicyUnavailable.make({ cause })),\n\t\t\t\t\tEffect.map((value) =>\n\t\t\t\t\t\tPolicies.provideContext(value, services as never),\n\t\t\t\t\t),\n\t\t\t\t)\n\n\t\t\tconst cache =\n\t\t\t\toptions.cache === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: yield* Cache.make({\n\t\t\t\t\t\t\tcapacity: options.cache?.capacity ?? 1000,\n\t\t\t\t\t\t\ttimeToLive: options.cache?.timeToLive ?? \"30 seconds\",\n\t\t\t\t\t\t\tlookup: (key: PrincipalKey) => load(key.principal),\n\t\t\t\t\t\t})\n\n\t\t\tconst policyFor = (principal: object) => {\n\t\t\t\tconst canonical = canonicalOf(principal)\n\t\t\t\tconst key =\n\t\t\t\t\tcache === undefined ? undefined : keyOf(principal, canonical)\n\t\t\t\tif (cache === undefined || key === undefined) {\n\t\t\t\t\treturn load(canonical?.principal ?? principal)\n\t\t\t\t}\n\t\t\t\t// A failed lookup is kept in the cache. Remove it, so the next check\n\t\t\t\t// tries again.\n\t\t\t\treturn Cache.get(cache, key).pipe(\n\t\t\t\t\tEffect.tapError(() => Cache.invalidate(cache, key)),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst withServices = <A, E>(\n\t\t\t\teffect: Effect.Effect<A, E, unknown>,\n\t\t\t): Effect.Effect<A, E, unknown> => Effect.provideContext(effect, services)\n\n\t\t\tconst current = Effect.gen(function* () {\n\t\t\t\tconst principal = yield* service.principal\n\t\t\t\tconst value = yield* policyFor(principal)\n\t\t\t\treturn { principal, policy: value }\n\t\t\t})\n\n\t\t\t// The principal and its policy, loaded one time. A check that asks\n\t\t\t// the policy two times, such as `guard`, uses one snapshot for both,\n\t\t\t// so the two answers come from one principal.\n\t\t\ttype Snapshot = Effect.Success<typeof current>\n\n\t\t\tconst decideWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst decision: Decision.Decision = yield* withServices(\n\t\t\t\t\tDecide.decideAny(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t\treturn decision\n\t\t\t})\n\n\t\t\tconst decide = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => decideWith(snapshot, target))\n\n\t\t\tconst checkWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst kind =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? (target.kind ?? \"\")\n\t\t\t\t\t\t: (ResourceData.kindOf(target.resource) ?? \"\")\n\t\t\t\t// A request that does not fit its kind gets a \"no\" at once. The\n\t\t\t\t// decider must not say yes to an action that the kind does not have.\n\t\t\t\tconst descriptor = DomainData.descriptorOf(domain, kind)\n\t\t\t\tif (descriptor === undefined || !Decide.fitsKind(descriptor, target)) {\n\t\t\t\t\treturn yield* forbidden(target, kind, undefined)\n\t\t\t\t}\n\t\t\t\tconst fromPolicy: Decision.Decision =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? Analysis.decideKind(value, { ...target, principal } as never)\n\t\t\t\t\t\t: yield* withServices(\n\t\t\t\t\t\t\t\tDecide.decideAny(value, { ...target, principal } as never),\n\t\t\t\t\t\t\t)\n\t\t\t\t// When no rule gives an answer, the decider gives it.\n\t\t\t\tconst decision: Decision.Decision =\n\t\t\t\t\tfromPolicy._tag === \"NotApplicable\" && options.decider !== undefined\n\t\t\t\t\t\t? yield* options.decider({ ...target, principal })\n\t\t\t\t\t\t: fromPolicy\n\t\t\t\tif (decision._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* forbidden(target, kind, Decision.reason(decision))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst check = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => checkWith(snapshot, target))\n\n\t\t\tconst guard = Effect.fnUntraced(function* (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t\tguardOptions: {\n\t\t\t\t\treadonly read?: string\n\t\t\t\t\treadonly hide: () => unknown\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst snapshot = yield* current\n\t\t\t\tconst read = guardOptions.read ?? \"read\"\n\t\t\t\tconst { field: _field, ...rest } = target\n\t\t\t\tconst visible: Decision.Decision = yield* decideWith(snapshot, {\n\t\t\t\t\t...rest,\n\t\t\t\t\taction: read,\n\t\t\t\t})\n\t\t\t\tif (visible._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* Effect.fail(guardOptions.hide())\n\t\t\t\t}\n\t\t\t\tif (target.action !== read || target.field !== undefined) {\n\t\t\t\t\tyield* checkWith(snapshot, target)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// The filter is the condition of the coverage: the scope for `All`,\n\t\t\t// `False` for `None`, and else the residual condition in the scope.\n\t\t\t// The residual throws an error when it is too large, so the filter\n\t\t\t// fails.\n\t\t\tconst filter = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Condition.Ast, unknown, unknown> =>\n\t\t\t\tEffect.map(\n\t\t\t\t\tcurrent,\n\t\t\t\t\t({ principal, policy: value }): Condition.Ast =>\n\t\t\t\t\t\tAnalysis.coverageAny(value, { ...target, principal } as never)\n\t\t\t\t\t\t\t.condition as Condition.Ast,\n\t\t\t\t)\n\n\t\t\tconst coverage = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Analysis.AnyCoverage, unknown, unknown> =>\n\t\t\t\tEffect.map(current, ({ principal, policy: value }) =>\n\t\t\t\t\tAnalysis.coverageAny(value, { ...target, principal } as never),\n\t\t\t\t)\n\n\t\t\tconst permittedFields = Effect.fnUntraced(function* (target: AnyTarget) {\n\t\t\t\tconst { principal, policy: value } = yield* current\n\t\t\t\treturn yield* withServices(\n\t\t\t\t\tDecide.permittedFields(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t})\n\n\t\t\tconst invalidate = (principal?: object) => {\n\t\t\t\tif (cache === undefined) {\n\t\t\t\t\treturn Effect.void\n\t\t\t\t}\n\t\t\t\tif (principal === undefined) {\n\t\t\t\t\treturn Cache.invalidateAll(cache)\n\t\t\t\t}\n\t\t\t\tconst key = keyOf(principal, canonicalOf(principal))\n\t\t\t\treturn key === undefined ? Effect.void : Cache.invalidate(cache, key)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tdomain,\n\t\t\t\tprincipal: service.principal,\n\t\t\t\tpolicy: Effect.map(current, ({ policy: value }) => value),\n\t\t\t\tdecide,\n\t\t\t\tcheck,\n\t\t\t\tguard,\n\t\t\t\tfilter,\n\t\t\t\tcoverage,\n\t\t\t\tpermittedFields,\n\t\t\t\tinvalidate,\n\t\t\t} as never\n\t\t}),\n\t)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"authorizer.js","names":["Trampoline.adaptive","Policies.provideContext","Decide.decideAny","ResourceData.kindOf","DomainData.descriptorOf","Decide.fitsKind","Analysis.decideKind","Decision.reason","Analysis.coverageAny","permittedFields","Decide.permittedFields"],"sources":["../../src/internal/authorizer.ts"],"sourcesContent":["/**\n * The values of authorizers: the errors, the service class and the layer.\n * The types are in `Authorizer.ts`.\n */\n// The service erases the error type and the service type of predicates,\n// principals and deciders to `unknown`. The public types keep them, so the\n// language service check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Arr from \"effect/Array\"\nimport * as Cache from \"effect/Cache\"\nimport * as Chunk from \"effect/Chunk\"\nimport * as Context from \"effect/Context\"\nimport type * as Duration from \"effect/Duration\"\nimport * as Effect from \"effect/Effect\"\nimport * as Equal from \"effect/Equal\"\nimport * as Hash from \"effect/Hash\"\nimport * as HashMap from \"effect/HashMap\"\nimport * as HashSet from \"effect/HashSet\"\nimport * as Layer from \"effect/Layer\"\nimport * as Result from \"effect/Result\"\nimport * as Schema from \"effect/Schema\"\nimport type * as Authorizer from \"../Authorizer.ts\"\nimport type * as Condition from \"../Condition.ts\"\nimport * as Decision from \"../Decision.ts\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Policy from \"../Policy.ts\"\nimport * as Analysis from \"./analysis.ts\"\nimport * as Decide from \"./decide.ts\"\nimport * as DomainData from \"./domain.ts\"\nimport * as Policies from \"./policy.ts\"\nimport * as ResourceData from \"./resource.ts\"\nimport * as Trampoline from \"./trampoline.ts\"\n\ntype AnyServiceClass = Authorizer.AnyServiceClass\n\n/** The error when a policy says no. */\nexport class Forbidden extends Schema.TaggedError<Forbidden>(\n\t\"@nmnmcc/ability/Authorizer/Forbidden\",\n)(\"Forbidden\", {\n\taction: Schema.String,\n\tkind: Schema.String,\n\tfield: Schema.optionalKey(Schema.String),\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/** The error when the policy of the principal can not be loaded. */\nexport class PolicyUnavailable extends Schema.TaggedError<PolicyUnavailable>(\n\t\"@nmnmcc/ability/Authorizer/PolicyUnavailable\",\n)(\"PolicyUnavailable\", { cause: Schema.Defect() }) {}\n\n/** The error when a decider must wait for an answer. */\nexport class Pending extends Schema.TaggedError<Pending>(\n\t\"@nmnmcc/ability/Authorizer/Pending\",\n)(\"Pending\", {\n\tticket: Schema.String,\n\treason: Schema.optionalKey(Schema.String),\n}) {}\n\n/** Makes an authorizer service class for a domain. */\nexport const Service =\n\t() =>\n\t(\n\t\tid: string,\n\t\toptions: {\n\t\t\treadonly domain: Domain.Any\n\t\t\treadonly principal: Effect.Effect<any, any, any>\n\t\t},\n\t\t// The class has no name, and its type uses types of Effect that a\n\t\t// declaration file can not name. So this function gives a `Context.Key`.\n\t\t// The public module gives the full type.\n\t): Context.Key<any, any> => {\n\t\tconst Base = Context.Service<any, any>()(id)\n\t\treturn class extends Base {\n\t\t\tstatic readonly domain = options.domain\n\t\t\tstatic readonly principal = options.principal\n\t\t\tstatic readonly decide = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.decide(target))\n\t\t\tstatic readonly check = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.check(target))\n\t\t\tstatic readonly guard = (target: AnyTarget, options: unknown) =>\n\t\t\t\tthis.use((service: any) => service.guard(target, options))\n\t\t\tstatic readonly filter = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.filter(target))\n\t\t\tstatic readonly coverage = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.coverage(target))\n\t\t\tstatic readonly permittedFields = (target: AnyTarget) =>\n\t\t\t\tthis.use((service: any) => service.permittedFields(target))\n\t\t}\n\t}\n\ntype AnyTarget = {\n\treadonly action: string\n\treadonly resource?: object\n\treadonly kind?: string\n\treadonly field?: string\n\treadonly within?: unknown\n}\n\n/**\n * A cache key for a principal. Two keys are equal when they have the same\n * text, so two principals with the same key share one policy. A key from the\n * JSON form also needs equal decoded principals: a Schema may make data when\n * it decodes, as `expired` from a time, so one text may give two principals.\n * The key also keeps the principal, so the cache can load its policy. A plain\n * `Data` value can not do all this, so the key gives its own equality with\n * `Equal.Equal`.\n */\nclass PrincipalKey implements Equal.Equal {\n\treadonly principal: object\n\treadonly text: string\n\t/**\n\t * The order of the parts of the decoded principal, from {@link orderOf},\n\t * when the key is the JSON form. The decoded principals must be equal\n\t * too, with their parts in the same order. `undefined` for a key from\n\t * `cache.key`.\n\t */\n\treadonly order: string | undefined\n\tconstructor(principal: object, text: string, order: string | undefined) {\n\t\tthis.principal = principal\n\t\tthis.text = text\n\t\tthis.order = order\n\t}\n\t[Equal.symbol](that: unknown): boolean {\n\t\treturn (\n\t\t\tthat instanceof PrincipalKey &&\n\t\t\tthat.text === this.text &&\n\t\t\tthat.order === this.order &&\n\t\t\t(this.order === undefined || Equal.equals(that.principal, this.principal))\n\t\t)\n\t}\n\t[Hash.symbol](): number {\n\t\treturn Hash.string(this.text)\n\t}\n}\n\n/**\n * The text of the order of the parts of a value: the names of the fields of\n * each object, and the items of each list, set and map, with each scalar, in\n * the order that a loop sees them. `Equal` does not see the order of the\n * fields of an object or of the items of a map, but a loader may read it, as\n * `Object.values` does. So two keys need the same order too. A scalar is in\n * the text with its type, so the items of a set or a map show their order.\n */\nconst orderOf: (value: unknown) => string = Trampoline.adaptive<\n\tunknown,\n\tstring\n>((R, child) => (value, depth) => {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn R.succeed(\n\t\t\ttypeof value === \"string\"\n\t\t\t\t? JSON.stringify(value)\n\t\t\t\t: `${typeof value}:${String(value)}`,\n\t\t)\n\t}\n\tconst items = (list: ReadonlyArray<unknown>) =>\n\t\tR.forEach(list, (item) => child(item, depth + 1))\n\tif (Array.isArray(value)) {\n\t\treturn R.map(items(value), (texts) => `[${texts.join(\",\")}]`)\n\t}\n\tif (\n\t\tvalue instanceof Map ||\n\t\tvalue instanceof Set ||\n\t\tHashMap.isHashMap(value) ||\n\t\tHashSet.isHashSet(value) ||\n\t\tChunk.isChunk(value)\n\t) {\n\t\treturn R.map(\n\t\t\titems(Array.from(value as Iterable<unknown>)),\n\t\t\t(texts) => `<${texts.join(\",\")}>`,\n\t\t)\n\t}\n\tconst names = Object.keys(value)\n\treturn R.map(\n\t\titems(Arr.map(names, (name) => (value as Record<string, unknown>)[name])),\n\t\t(texts) =>\n\t\t\t`{${Arr.map(names, (name, index) => `${JSON.stringify(name)}:${texts[index]!}`).join(\",\")}}`,\n\t)\n})\n\n/**\n * Gives the JSON codec of a Schema, or `undefined` when the Schema has no JSON\n * form, as a struct with a symbol key has none.\n */\nconst jsonCodecOf = (\n\tschema: Schema.Top,\n): Schema.Codec<unknown, unknown> | undefined => {\n\ttry {\n\t\treturn Schema.toCodecJson(schema) as unknown as Schema.Codec<\n\t\t\tunknown,\n\t\t\tunknown\n\t\t>\n\t} catch {\n\t\treturn undefined\n\t}\n}\n\n/**\n * Makes the cache key of a principal from its JSON form. The key keeps the\n * principal that the Schema decodes from that form, so the loader sees only\n * the data that the key shows. A principal gets no key when it does not\n * encode, or when the decoded value is not `Equal` to it. Two classes, or a\n * string and a number, may have one JSON form, as in a union, and `Equal`\n * tells them apart. A Schema may make data when it decodes, as the order of\n * the fields of a record, so the key keeps that order too. So two\n * principals with one key are equal, and the cache can not give one the\n * policy of the other.\n */\nconst jsonKey = (\n\tschema: Schema.Top,\n): ((principal: object) => PrincipalKey | undefined) => {\n\tconst codec = jsonCodecOf(schema)\n\t// A principal with no JSON form is never kept.\n\tif (codec === undefined) {\n\t\treturn () => undefined\n\t}\n\tconst encode = Schema.encodeUnknownResult(codec)\n\tconst decode = Schema.decodeUnknownResult(codec)\n\tconst keyOf = (principal: object): PrincipalKey | undefined =>\n\t\tResult.match(encode(principal), {\n\t\t\tonFailure: () => undefined,\n\t\t\tonSuccess: (json) => {\n\t\t\t\t// The key is the text, so the check decodes the text. The encoded\n\t\t\t\t// value may hold more than its text shows, as an object of a\n\t\t\t\t// `Schema.Any` field does: `JSON.stringify` leaves out a map.\n\t\t\t\tconst text = JSON.stringify(json)\n\t\t\t\treturn Result.match(decode(JSON.parse(text)), {\n\t\t\t\t\tonFailure: () => undefined,\n\t\t\t\t\tonSuccess: (decoded) =>\n\t\t\t\t\t\tEqual.equals(decoded, principal)\n\t\t\t\t\t\t\t? new PrincipalKey(decoded as object, text, orderOf(decoded))\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t})\n\t\t\t},\n\t\t})\n\treturn (principal) => {\n\t\t// A very deep principal may fill the stack of the encoder or of\n\t\t// `Equal`. Such a principal is not kept.\n\t\ttry {\n\t\t\treturn keyOf(principal)\n\t\t} catch {\n\t\t\treturn undefined\n\t\t}\n\t}\n}\n\nconst forbidden = (\n\ttarget: AnyTarget,\n\tkind: string,\n\treason: string | undefined,\n): Forbidden =>\n\tForbidden.make({\n\t\taction: target.action,\n\t\tkind,\n\t\t...(target.field === undefined ? {} : { field: target.field }),\n\t\t...(reason === undefined ? {} : { reason }),\n\t})\n\n/** Makes the layer of an authorizer. */\nexport const makeLayer = (\n\tservice: AnyServiceClass,\n\toptions: {\n\t\treadonly policy: (\n\t\t\tprincipal: object,\n\t\t) => Effect.Effect<Policy.Any, unknown, unknown>\n\t\treadonly decider?: (\n\t\t\trequest: object,\n\t\t) => Effect.Effect<Decision.Decision, unknown, unknown>\n\t\treadonly cache?:\n\t\t\t| {\n\t\t\t\t\treadonly capacity?: number\n\t\t\t\t\treadonly timeToLive?: Duration.Input\n\t\t\t\t\treadonly key?: (principal: object) => string | undefined\n\t\t\t }\n\t\t\t| false\n\t},\n) => {\n\tconst domain: Domain.Any = service.domain\n\t// The JSON form of a principal, and the copy that the Schema decodes from\n\t// it. The loader always gets the copy, with a cache or not, so a cache can\n\t// not change an answer: a form may lose data that `Equal` does not see,\n\t// as `-0` becomes `0`. A principal with no such form gives `undefined`.\n\tconst canonicalOf = jsonKey(domain.principal)\n\tconst custom = options.cache === false ? undefined : options.cache?.key\n\t// It gives `undefined` when the principal has no key. Such a principal does\n\t// not use the cache.\n\tconst keyOf = (\n\t\tprincipal: object,\n\t\tcanonical: PrincipalKey | undefined,\n\t): PrincipalKey | undefined => {\n\t\tif (custom === undefined) {\n\t\t\treturn canonical\n\t\t}\n\t\tconst text = custom(principal)\n\t\treturn text === undefined\n\t\t\t? undefined\n\t\t\t: new PrincipalKey(canonical?.principal ?? principal, text, undefined)\n\t}\n\treturn Layer.effect(\n\t\tservice,\n\t\tEffect.gen(function* () {\n\t\t\t// The services of the policy loader and of the predicates come from the\n\t\t\t// layer, so a check does not need them.\n\t\t\tconst services = yield* Effect.context<never>()\n\n\t\t\t// The predicates of the policy get the services of the layer, so the\n\t\t\t// policy and its residual conditions need no services.\n\t\t\t// A policy of another domain may have the same types, but its resources\n\t\t\t// may have other paths. So it fails closed, as a policy that can not be\n\t\t\t// loaded.\n\t\t\tconst load = (principal: object) =>\n\t\t\t\toptions.policy(principal).pipe(\n\t\t\t\t\tEffect.provideContext(services),\n\t\t\t\t\tEffect.filterOrFail(\n\t\t\t\t\t\t(value) => value.domain === domain,\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\tnew globalThis.Error(\n\t\t\t\t\t\t\t\t\"The loaded policy is not a policy of this domain\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t\tEffect.mapError((cause) => PolicyUnavailable.make({ cause })),\n\t\t\t\t\tEffect.map((value) =>\n\t\t\t\t\t\tPolicies.provideContext(value, services as never),\n\t\t\t\t\t),\n\t\t\t\t)\n\n\t\t\tconst cache =\n\t\t\t\toptions.cache === false\n\t\t\t\t\t? undefined\n\t\t\t\t\t: yield* Cache.make({\n\t\t\t\t\t\t\tcapacity: options.cache?.capacity ?? 1000,\n\t\t\t\t\t\t\ttimeToLive: options.cache?.timeToLive ?? \"30 seconds\",\n\t\t\t\t\t\t\tlookup: (key: PrincipalKey) => load(key.principal),\n\t\t\t\t\t\t})\n\n\t\t\tconst policyFor = (principal: object) => {\n\t\t\t\tconst canonical = canonicalOf(principal)\n\t\t\t\tconst key =\n\t\t\t\t\tcache === undefined ? undefined : keyOf(principal, canonical)\n\t\t\t\tif (cache === undefined || key === undefined) {\n\t\t\t\t\treturn load(canonical?.principal ?? principal)\n\t\t\t\t}\n\t\t\t\t// A failed lookup is kept in the cache. Remove it, so the next check\n\t\t\t\t// tries again.\n\t\t\t\treturn Cache.get(cache, key).pipe(\n\t\t\t\t\tEffect.tapError(() => Cache.invalidate(cache, key)),\n\t\t\t\t)\n\t\t\t}\n\n\t\t\tconst withServices = <A, E>(\n\t\t\t\teffect: Effect.Effect<A, E, unknown>,\n\t\t\t): Effect.Effect<A, E, unknown> => Effect.provideContext(effect, services)\n\n\t\t\tconst current = Effect.gen(function* () {\n\t\t\t\tconst principal = yield* service.principal\n\t\t\t\tconst value = yield* policyFor(principal)\n\t\t\t\treturn { principal, policy: value }\n\t\t\t})\n\n\t\t\t// The principal and its policy, loaded one time. A check that asks\n\t\t\t// the policy two times, such as `guard`, uses one snapshot for both,\n\t\t\t// so the two answers come from one principal.\n\t\t\ttype Snapshot = Effect.Success<typeof current>\n\n\t\t\tconst decideWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst decision: Decision.Decision = yield* withServices(\n\t\t\t\t\tDecide.decideAny(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t\treturn decision\n\t\t\t})\n\n\t\t\tconst decide = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => decideWith(snapshot, target))\n\n\t\t\tconst checkWith = Effect.fnUntraced(function* (\n\t\t\t\t{ principal, policy: value }: Snapshot,\n\t\t\t\ttarget: AnyTarget,\n\t\t\t) {\n\t\t\t\tconst kind =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? (target.kind ?? \"\")\n\t\t\t\t\t\t: (ResourceData.kindOf(target.resource) ?? \"\")\n\t\t\t\t// A request that does not fit its kind gets a \"no\" at once. The\n\t\t\t\t// decider must not say yes to an action that the kind does not have.\n\t\t\t\tconst descriptor = DomainData.descriptorOf(domain, kind)\n\t\t\t\tif (\n\t\t\t\t\tdescriptor === undefined ||\n\t\t\t\t\t!Decide.fitsKind(descriptor, target, domain.limits.fieldPathParts)\n\t\t\t\t) {\n\t\t\t\t\treturn yield* forbidden(target, kind, undefined)\n\t\t\t\t}\n\t\t\t\tconst fromPolicy: Decision.Decision =\n\t\t\t\t\ttarget.resource === undefined\n\t\t\t\t\t\t? Analysis.decideKind(value, { ...target, principal } as never)\n\t\t\t\t\t\t: yield* withServices(\n\t\t\t\t\t\t\t\tDecide.decideAny(value, { ...target, principal } as never),\n\t\t\t\t\t\t\t)\n\t\t\t\t// When no rule gives an answer, the decider gives it.\n\t\t\t\tconst decision: Decision.Decision =\n\t\t\t\t\tfromPolicy._tag === \"NotApplicable\" && options.decider !== undefined\n\t\t\t\t\t\t? yield* options.decider({ ...target, principal })\n\t\t\t\t\t\t: fromPolicy\n\t\t\t\tif (decision._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* forbidden(target, kind, Decision.reason(decision))\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst check = (target: AnyTarget) =>\n\t\t\t\tEffect.flatMap(current, (snapshot) => checkWith(snapshot, target))\n\n\t\t\tconst guard = Effect.fnUntraced(function* (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t\tguardOptions: {\n\t\t\t\t\treadonly read?: string\n\t\t\t\t\treadonly hide: () => unknown\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst snapshot = yield* current\n\t\t\t\tconst read = guardOptions.read ?? \"read\"\n\t\t\t\tconst { field: _field, ...rest } = target\n\t\t\t\tconst visible: Decision.Decision = yield* decideWith(snapshot, {\n\t\t\t\t\t...rest,\n\t\t\t\t\taction: read,\n\t\t\t\t})\n\t\t\t\tif (visible._tag !== \"Allow\") {\n\t\t\t\t\treturn yield* Effect.fail(guardOptions.hide())\n\t\t\t\t}\n\t\t\t\tif (target.action !== read || target.field !== undefined) {\n\t\t\t\t\tyield* checkWith(snapshot, target)\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// The filter is the condition of the coverage: the scope for `All`,\n\t\t\t// `False` for `None`, and else the residual condition in the scope.\n\t\t\t// The residual throws an error when it is too large, so the filter\n\t\t\t// fails.\n\t\t\tconst filter = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Condition.Ast, unknown, unknown> =>\n\t\t\t\tEffect.map(\n\t\t\t\t\tcurrent,\n\t\t\t\t\t({ principal, policy: value }): Condition.Ast =>\n\t\t\t\t\t\tAnalysis.coverageAny(value, { ...target, principal } as never)\n\t\t\t\t\t\t\t.condition as Condition.Ast,\n\t\t\t\t)\n\n\t\t\tconst coverage = (\n\t\t\t\ttarget: AnyTarget,\n\t\t\t): Effect.Effect<Analysis.AnyCoverage, unknown, unknown> =>\n\t\t\t\tEffect.map(current, ({ principal, policy: value }) =>\n\t\t\t\t\tAnalysis.coverageAny(value, { ...target, principal } as never),\n\t\t\t\t)\n\n\t\t\tconst permittedFields = Effect.fnUntraced(function* (target: AnyTarget) {\n\t\t\t\tconst { principal, policy: value } = yield* current\n\t\t\t\treturn yield* withServices(\n\t\t\t\t\tDecide.permittedFields(value, { ...target, principal } as never),\n\t\t\t\t)\n\t\t\t})\n\n\t\t\tconst invalidate = (principal?: object) => {\n\t\t\t\tif (cache === undefined) {\n\t\t\t\t\treturn Effect.void\n\t\t\t\t}\n\t\t\t\tif (principal === undefined) {\n\t\t\t\t\treturn Cache.invalidateAll(cache)\n\t\t\t\t}\n\t\t\t\tconst key = keyOf(principal, canonicalOf(principal))\n\t\t\t\treturn key === undefined ? Effect.void : Cache.invalidate(cache, key)\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tdomain,\n\t\t\t\tprincipal: service.principal,\n\t\t\t\tpolicy: Effect.map(current, ({ policy: value }) => value),\n\t\t\t\tdecide,\n\t\t\t\tcheck,\n\t\t\t\tguard,\n\t\t\t\tfilter,\n\t\t\t\tcoverage,\n\t\t\t\tpermittedFields,\n\t\t\t\tinvalidate,\n\t\t\t} as never\n\t\t}),\n\t)\n}\n"],"mappings":"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"}
|