@nmnmcc/ability 0.3.0 → 1.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 (66) hide show
  1. package/README.md +199 -31
  2. package/dist/Authorizer.d.ts +64 -39
  3. package/dist/Authorizer.d.ts.map +1 -1
  4. package/dist/Authorizer.js +291 -6
  5. package/dist/Authorizer.js.map +1 -1
  6. package/dist/Condition.d.ts +444 -65
  7. package/dist/Condition.d.ts.map +1 -1
  8. package/dist/Condition.js +1007 -25
  9. package/dist/Condition.js.map +1 -1
  10. package/dist/Domain.d.ts +27 -17
  11. package/dist/Domain.d.ts.map +1 -1
  12. package/dist/Domain.js +52 -10
  13. package/dist/Domain.js.map +1 -1
  14. package/dist/HttpApi.d.ts +84 -40
  15. package/dist/HttpApi.d.ts.map +1 -1
  16. package/dist/HttpApi.js +103 -13
  17. package/dist/HttpApi.js.map +1 -1
  18. package/dist/Policy.d.ts +314 -56
  19. package/dist/Policy.d.ts.map +1 -1
  20. package/dist/Policy.js +1215 -38
  21. package/dist/Policy.js.map +1 -1
  22. package/dist/Principal.js +2 -2
  23. package/dist/Principal.js.map +1 -1
  24. package/dist/Resource.d.ts +171 -20
  25. package/dist/Resource.d.ts.map +1 -1
  26. package/dist/Resource.js +788 -15
  27. package/dist/Resource.js.map +1 -1
  28. package/dist/Rule.d.ts +115 -29
  29. package/dist/Rule.d.ts.map +1 -1
  30. package/dist/Rule.js +219 -12
  31. package/dist/Rule.js.map +1 -1
  32. package/dist/index.js +4 -4
  33. package/package.json +2 -2
  34. package/src/Authorizer.ts +625 -61
  35. package/src/Condition.ts +2403 -112
  36. package/src/Domain.ts +80 -23
  37. package/src/HttpApi.ts +225 -25
  38. package/src/Policy.ts +2836 -96
  39. package/src/Principal.ts +2 -2
  40. package/src/Resource.ts +1544 -29
  41. package/src/Rule.ts +463 -45
  42. package/dist/internal/authorizer.d.ts +0 -28
  43. package/dist/internal/authorizer.d.ts.map +0 -1
  44. package/dist/internal/authorizer.js +0 -153
  45. package/dist/internal/authorizer.js.map +0 -1
  46. package/dist/internal/condition.d.ts +0 -11
  47. package/dist/internal/condition.d.ts.map +0 -1
  48. package/dist/internal/condition.js +0 -393
  49. package/dist/internal/condition.js.map +0 -1
  50. package/dist/internal/domain.js +0 -34
  51. package/dist/internal/domain.js.map +0 -1
  52. package/dist/internal/httpApi.js +0 -41
  53. package/dist/internal/httpApi.js.map +0 -1
  54. package/dist/internal/policy.js +0 -524
  55. package/dist/internal/policy.js.map +0 -1
  56. package/dist/internal/resource.js +0 -66
  57. package/dist/internal/resource.js.map +0 -1
  58. package/dist/internal/rule.js +0 -131
  59. package/dist/internal/rule.js.map +0 -1
  60. package/src/internal/authorizer.ts +0 -271
  61. package/src/internal/condition.ts +0 -607
  62. package/src/internal/domain.ts +0 -56
  63. package/src/internal/httpApi.ts +0 -84
  64. package/src/internal/policy.ts +0 -815
  65. package/src/internal/resource.ts +0 -98
  66. package/src/internal/rule.ts +0 -203
@@ -1,34 +0,0 @@
1
- import { descriptor } from "./resource.js";
2
- import * as Predicate from "effect/Predicate";
3
- // src/internal/domain.ts
4
- /** @internal */
5
- const TypeId = "~@nmnmcc/ability/Domain";
6
- /** @internal */
7
- const make = (options) => {
8
- const resources = /* @__PURE__ */ new Map();
9
- for (const resource of options.resources) {
10
- const value = descriptor(resource);
11
- if (resources.has(value.kind)) throw new Error(`Two resources have the kind name "${value.kind}"`);
12
- if (value.kind === "*") throw new Error(`A resource may not have the kind name "*"`);
13
- resources.set(value.kind, value);
14
- }
15
- return Object.freeze({
16
- [TypeId]: TypeId,
17
- principal: options.principal,
18
- resources,
19
- meta: options.meta
20
- });
21
- };
22
- /** @internal */
23
- const isDomain = (u) => Predicate.hasProperty(u, TypeId);
24
- /** @internal */
25
- const getDescriptor = (domain, kind) => domain.resources.get(kind);
26
- /**
27
- * Gives the kind name of a resource value, from its `_tag` field.
28
- *
29
- * @internal
30
- */
31
- const kindOf = (value) => Predicate.hasProperty(value, "_tag") && typeof value._tag === "string" ? value._tag : void 0;
32
- export { TypeId, getDescriptor, isDomain, kindOf, make };
33
-
34
- //# sourceMappingURL=domain.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"domain.js","names":[],"sources":["../../src/internal/domain.ts"],"sourcesContent":["import * as Predicate from \"effect/Predicate\"\nimport type * as Schema from \"effect/Schema\"\nimport type * as Domain from \"../Domain.ts\"\nimport type * as Principal from \"../Principal.ts\"\nimport type * as Resource from \"../Resource.ts\"\nimport { descriptor } from \"./resource.ts\"\n\n/** @internal */\nexport const TypeId = \"~@nmnmcc/ability/Domain\" as const\n\n/** @internal */\nexport const make = (options: {\n\treadonly principal: Principal.Any\n\treadonly resources: ReadonlyArray<Resource.Any>\n\treadonly meta?: Schema.Top\n}): any => {\n\tconst resources = new Map<string, Resource.Descriptor>()\n\tfor (const resource of options.resources) {\n\t\tconst value = descriptor(resource)\n\t\tif (resources.has(value.kind)) {\n\t\t\t// Two resources with one kind name is a mistake in the code, not in the\n\t\t\t// data, so it is a defect.\n\t\t\tthrow new Error(`Two resources have the kind name \"${value.kind}\"`)\n\t\t}\n\t\tif (value.kind === \"*\") {\n\t\t\tthrow new Error(`A resource may not have the kind name \"*\"`)\n\t\t}\n\t\tresources.set(value.kind, value)\n\t}\n\treturn Object.freeze({\n\t\t[TypeId]: TypeId,\n\t\tprincipal: options.principal,\n\t\tresources,\n\t\tmeta: options.meta,\n\t})\n}\n\n/** @internal */\nexport const isDomain = (u: unknown): u is Domain.Any =>\n\tPredicate.hasProperty(u, TypeId)\n\n/** @internal */\nexport const getDescriptor = (\n\tdomain: Domain.Any,\n\tkind: string,\n): Resource.Descriptor | undefined => domain.resources.get(kind)\n\n/**\n * Gives the kind name of a resource value, from its `_tag` field.\n *\n * @internal\n */\nexport const kindOf = (value: unknown): string | undefined =>\n\tPredicate.hasProperty(value, \"_tag\") && typeof value._tag === \"string\"\n\t\t? value._tag\n\t\t: undefined\n"],"mappings":";;;;AAQA,MAAa,SAAS;;AAGtB,MAAa,QAAQ,YAIV;CACV,MAAM,4BAAY,IAAI,IAAiC;CACvD,KAAK,MAAM,YAAY,QAAQ,WAAW;EACzC,MAAM,QAAQ,WAAW,QAAQ;EACjC,IAAI,UAAU,IAAI,MAAM,IAAI,GAG3B,MAAM,IAAI,MAAM,qCAAqC,MAAM,KAAK,EAAE;EAEnE,IAAI,MAAM,SAAS,KAClB,MAAM,IAAI,MAAM,2CAA2C;EAE5D,UAAU,IAAI,MAAM,MAAM,KAAK;CAChC;CACA,OAAO,OAAO,OAAO;GACnB,SAAS;EACV,WAAW,QAAQ;EACnB;EACA,MAAM,QAAQ;CACf,CAAC;AACF;;AAGA,MAAa,YAAY,MACxB,UAAU,YAAY,GAAG,MAAM;;AAGhC,MAAa,iBACZ,QACA,SACqC,OAAO,UAAU,IAAI,IAAI;;;;;;AAO/D,MAAa,UAAU,UACtB,UAAU,YAAY,OAAO,MAAM,KAAK,OAAO,MAAM,SAAS,WAC3D,MAAM,OACN,KAAA"}
@@ -1,41 +0,0 @@
1
- import { GroupRequired, Required } from "../HttpApi.js";
2
- import * as Context from "effect/Context";
3
- import * as Effect from "effect/Effect";
4
- import * as Layer from "effect/Layer";
5
- import * as Option from "effect/Option";
6
- import { HttpRouter } from "effect/http";
7
- // src/internal/httpApi.ts
8
- /** @internal */
9
- const requires = (_authorizer, action, kind, load) => Object.freeze(load === void 0 ? {
10
- action,
11
- kind
12
- } : {
13
- action,
14
- kind,
15
- load
16
- });
17
- /** @internal */
18
- const layer = (middleware, authorizer) => Layer.effect(middleware, Effect.gen(function* () {
19
- const service = yield* authorizer;
20
- const services = yield* Effect.context();
21
- return Effect.fnUntraced(function* (httpEffect, options) {
22
- const requirements = Option.orElse(Context.getOption(options.endpoint.annotations, Required), () => Option.map(Context.getOption(options.group.annotations, GroupRequired), (rules) => options.endpoint.method === "GET" ? rules.read : rules.write));
23
- if (Option.isNone(requirements)) return yield* Effect.die(/* @__PURE__ */ new Error(`The endpoint ${options.group.identifier}.${options.endpoint.name} does not say what it needs. Give it the HttpApi.Required annotation.`));
24
- for (const requirement of requirements.value) if (requirement.load === void 0) yield* service.check({
25
- action: requirement.action,
26
- kind: requirement.kind
27
- });
28
- else {
29
- const params = yield* HttpRouter.params;
30
- const value = yield* Effect.provideContext(requirement.load(params), services);
31
- yield* service.check({
32
- action: requirement.action,
33
- resource: value
34
- });
35
- }
36
- return yield* httpEffect;
37
- });
38
- }));
39
- export { layer, requires };
40
-
41
- //# sourceMappingURL=httpApi.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"httpApi.js","names":[],"sources":["../../src/internal/httpApi.ts"],"sourcesContent":["// A `load` function of a requirement is kept in an annotation, so its error\n// type and its service type are erased to `unknown`. The language service\n// check for `unknown` in an Effect is off here.\n// @effect-diagnostics anyUnknownInErrorContext:off\nimport * as Context from \"effect/Context\"\nimport * as Effect from \"effect/Effect\"\nimport * as Layer from \"effect/Layer\"\nimport * as Option from \"effect/Option\"\nimport { HttpRouter } from \"effect/http\"\nimport type * as Authorizer from \"../Authorizer.ts\"\nimport type * as HttpApi from \"../HttpApi.ts\"\nimport { GroupRequired, Required } from \"../HttpApi.ts\"\n\n/** @internal */\nexport const requires = (\n\t_authorizer: unknown,\n\taction: string,\n\tkind: string,\n\tload?: HttpApi.Requirement[\"load\"],\n): HttpApi.Requirement =>\n\tObject.freeze(load === undefined ? { action, kind } : { action, kind, load })\n\n/** @internal */\nexport const layer = (\n\tmiddleware: Context.ServiceClass<any, string, any>,\n\tauthorizer: Authorizer.AnyServiceClass,\n) =>\n\tLayer.effect(\n\t\tmiddleware,\n\t\tEffect.gen(function* () {\n\t\t\tconst service = yield* authorizer\n\t\t\tconst services = yield* Effect.context<never>()\n\t\t\treturn Effect.fnUntraced(function* (\n\t\t\t\thttpEffect: Effect.Effect<unknown, unknown, unknown>,\n\t\t\t\toptions: {\n\t\t\t\t\treadonly endpoint: {\n\t\t\t\t\t\treadonly name: string\n\t\t\t\t\t\treadonly method: string\n\t\t\t\t\t\treadonly annotations: Context.Context<never>\n\t\t\t\t\t}\n\t\t\t\t\treadonly group: {\n\t\t\t\t\t\treadonly identifier: string\n\t\t\t\t\t\treadonly annotations: Context.Context<never>\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t) {\n\t\t\t\tconst requirements = Option.orElse(\n\t\t\t\t\tContext.getOption(options.endpoint.annotations, Required),\n\t\t\t\t\t() =>\n\t\t\t\t\t\tOption.map(\n\t\t\t\t\t\t\tContext.getOption(options.group.annotations, GroupRequired),\n\t\t\t\t\t\t\t(rules) =>\n\t\t\t\t\t\t\t\toptions.endpoint.method === \"GET\" ? rules.read : rules.write,\n\t\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t\tif (Option.isNone(requirements)) {\n\t\t\t\t\treturn yield* Effect.die(\n\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t`The endpoint ${options.group.identifier}.${options.endpoint.name} does not say what it needs. Give it the HttpApi.Required annotation.`,\n\t\t\t\t\t\t),\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tfor (const requirement of requirements.value) {\n\t\t\t\t\tif (requirement.load === undefined) {\n\t\t\t\t\t\tyield* service.check({\n\t\t\t\t\t\t\taction: requirement.action,\n\t\t\t\t\t\t\tkind: requirement.kind,\n\t\t\t\t\t\t} as never)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst params = yield* HttpRouter.params\n\t\t\t\t\t\tconst value = yield* Effect.provideContext(\n\t\t\t\t\t\t\trequirement.load(params),\n\t\t\t\t\t\t\tservices,\n\t\t\t\t\t\t)\n\t\t\t\t\t\tyield* service.check({\n\t\t\t\t\t\t\taction: requirement.action,\n\t\t\t\t\t\t\tresource: value,\n\t\t\t\t\t\t} as never)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn yield* httpEffect\n\t\t\t})\n\t\t}),\n\t)\n"],"mappings":";;;;;;;;AAcA,MAAa,YACZ,aACA,QACA,MACA,SAEA,OAAO,OAAO,SAAS,KAAA,IAAY;CAAE;CAAQ;AAAK,IAAI;CAAE;CAAQ;CAAM;AAAK,CAAC;;AAG7E,MAAa,SACZ,YACA,eAEA,MAAM,OACL,YACA,OAAO,IAAI,aAAa;CACvB,MAAM,UAAU,OAAO;CACvB,MAAM,WAAW,OAAO,OAAO,QAAe;CAC9C,OAAO,OAAO,WAAW,WACxB,YACA,SAWC;EACD,MAAM,eAAe,OAAO,OAC3B,QAAQ,UAAU,QAAQ,SAAS,aAAa,QAAQ,SAEvD,OAAO,IACN,QAAQ,UAAU,QAAQ,MAAM,aAAa,aAAa,IACzD,UACA,QAAQ,SAAS,WAAW,QAAQ,MAAM,OAAO,MAAM,KACzD,CACF;EACA,IAAI,OAAO,OAAO,YAAY,GAC7B,OAAO,OAAO,OAAO,oBACpB,IAAI,MACH,gBAAgB,QAAQ,MAAM,WAAW,GAAG,QAAQ,SAAS,KAAK,sEACnE,CACD;EAED,KAAK,MAAM,eAAe,aAAa,OACtC,IAAI,YAAY,SAAS,KAAA,GACxB,OAAO,QAAQ,MAAM;GACpB,QAAQ,YAAY;GACpB,MAAM,YAAY;EACnB,CAAU;OACJ;GACN,MAAM,SAAS,OAAO,WAAW;GACjC,MAAM,QAAQ,OAAO,OAAO,eAC3B,YAAY,KAAK,MAAM,GACvB,QACD;GACA,OAAO,QAAQ,MAAM;IACpB,QAAQ,YAAY;IACpB,UAAU;GACX,CAAU;EACX;EAED,OAAO,OAAO;CACf,CAAC;AACF,CAAC,CACF"}
@@ -1,524 +0,0 @@
1
- import { Allow, Deny, NotApplicable } from "../Decision.js";
2
- import { getDescriptor, kindOf } from "./domain.js";
3
- import { and as and$1, condition_exports, evaluateOf, fields, not as not$1, or as or$1, partialOf, schema as schema$1 } from "./condition.js";
4
- import { builder, make, matchesTarget, toData } from "./rule.js";
5
- import * as Effect from "effect/Effect";
6
- import * as Equal from "effect/Equal";
7
- import * as Hash from "effect/Hash";
8
- import * as Schema from "effect/Schema";
9
- import * as Predicate from "effect/Predicate";
10
- import * as Formatter from "effect/Formatter";
11
- import { dual } from "effect/Function";
12
- import * as Inspectable from "effect/Inspectable";
13
- import * as Pipeable from "effect/Pipeable";
14
- import * as SchemaTransformation from "effect/SchemaTransformation";
15
- // src/internal/policy.ts
16
- /** @internal */
17
- const TypeId = "~@nmnmcc/ability/Policy";
18
- /** @internal */
19
- const encodeNode = (node) => {
20
- switch (node._tag) {
21
- case "Rules": return {
22
- _tag: "Rules",
23
- algorithm: node.algorithm,
24
- rules: node.rules.map((value) => toData(value))
25
- };
26
- case "All":
27
- case "Any":
28
- case "FirstOf": return {
29
- _tag: node._tag,
30
- policies: node.policies.map(encodeNode)
31
- };
32
- case "Ceiling": return {
33
- _tag: "Ceiling",
34
- cap: encodeNode(node.cap),
35
- policy: encodeNode(node.policy)
36
- };
37
- }
38
- };
39
- const Proto = {
40
- ...Pipeable.Prototype,
41
- [TypeId]: TypeId,
42
- [Equal.symbol](that) {
43
- return isPolicy(that) && this.domain === that.domain && Equal.equals(this.node, that.node);
44
- },
45
- [Hash.symbol]() {
46
- return Hash.hash(this.node);
47
- },
48
- toJSON() {
49
- return encodeNode(this.node);
50
- },
51
- toString() {
52
- return Formatter.format(encodeNode(this.node), { space: 2 });
53
- },
54
- [Inspectable.NodeInspectSymbol]() {
55
- return encodeNode(this.node);
56
- }
57
- };
58
- /** @internal */
59
- const isPolicy = (u) => Predicate.hasProperty(u, TypeId);
60
- /** @internal */
61
- const fromNode = (domain, node) => {
62
- const self = Object.create(Proto);
63
- self.domain = domain;
64
- self.node = node;
65
- return Object.freeze(self);
66
- };
67
- const nodeOf = (policy) => policy.node;
68
- const domainOf = (policy) => policy.domain;
69
- /** @internal */
70
- const rules = (domain, values, options) => fromNode(domain, Object.freeze({
71
- _tag: "Rules",
72
- algorithm: options?.algorithm ?? "denyOverrides",
73
- rules: Object.freeze(Array.from(values))
74
- }));
75
- /** @internal */
76
- const empty = (domain) => rules(domain, []);
77
- /** @internal */
78
- const define = (domain, options) => (body) => {
79
- const collected = [];
80
- const iterator = body(builder(domain));
81
- let state = iterator.next();
82
- while (state.done !== true) {
83
- collected.push(state.value);
84
- state = iterator.next(state.value);
85
- }
86
- return rules(domain, collected, options);
87
- };
88
- const combine = (tag) => (policies) => {
89
- const domain = domainOf(policies[0]);
90
- for (const policy of policies) if (domainOf(policy) !== domain) throw new Error("Policies from two domains can not be put together");
91
- return fromNode(domain, Object.freeze({
92
- _tag: tag,
93
- policies: Object.freeze(policies.map(nodeOf))
94
- }));
95
- };
96
- /** @internal */
97
- const all = combine("All");
98
- /** @internal */
99
- const any = combine("Any");
100
- /** @internal */
101
- const firstOf = combine("FirstOf");
102
- /** @internal */
103
- const ceiling = dual(2, (policy, cap) => {
104
- if (domainOf(policy) !== domainOf(cap)) throw new Error("Policies from two domains can not be put together");
105
- return fromNode(domainOf(policy), Object.freeze({
106
- _tag: "Ceiling",
107
- cap: nodeOf(cap),
108
- policy: nodeOf(policy)
109
- }));
110
- });
111
- const ruleApplies = (value, context, path) => {
112
- if (!matchesTarget(value, context.action, context.kind, context.field)) return Effect.succeed(false);
113
- const test = value.when === void 0 ? Effect.succeed(true) : evaluateOf(value.when, context.input);
114
- return context.trace === void 0 ? test : Effect.tap(test, (applies) => Effect.sync(() => {
115
- context.trace.push({
116
- path,
117
- rule: value,
118
- applies
119
- });
120
- }));
121
- };
122
- const fromRule = (value) => value._tag === "Allow" ? Allow({ rule: value }) : value.reason === void 0 ? Deny({ rule: value }) : Deny({
123
- rule: value,
124
- reason: value.reason
125
- });
126
- const firstApplying = Effect.fnUntraced(function* (candidates, context, path) {
127
- for (const [value, index] of candidates) if (yield* ruleApplies(value, context, [...path, index])) return value;
128
- });
129
- const decideRules = Effect.fnUntraced(function* (node, context, path) {
130
- const indexed = node.rules.map((value, index) => [value, index]);
131
- const denies = indexed.filter(([value]) => value._tag === "Deny");
132
- const allows = indexed.filter(([value]) => value._tag === "Allow");
133
- let found;
134
- switch (node.algorithm) {
135
- case "denyOverrides":
136
- found = (yield* firstApplying(denies, context, path)) ?? (yield* firstApplying(allows, context, path));
137
- break;
138
- case "permitOverrides":
139
- found = (yield* firstApplying(allows, context, path)) ?? (yield* firstApplying(denies, context, path));
140
- break;
141
- case "firstApplicable":
142
- found = yield* firstApplying(indexed, context, path);
143
- break;
144
- case "lastApplicable": found = yield* firstApplying(indexed.slice().reverse(), context, path);
145
- }
146
- return found === void 0 ? NotApplicable : fromRule(found);
147
- });
148
- const decideNode = (node, context, path) => {
149
- switch (node._tag) {
150
- case "Rules": return decideRules(node, context, path);
151
- case "All": return Effect.gen(function* () {
152
- let firstAllow;
153
- let allAllow = node.policies.length > 0;
154
- for (const [index, child] of node.policies.entries()) {
155
- const result = yield* decideNode(child, context, [...path, index]);
156
- if (result._tag === "Deny") return result;
157
- if (result._tag === "Allow") firstAllow ??= result;
158
- else allAllow = false;
159
- }
160
- return allAllow && firstAllow !== void 0 ? firstAllow : NotApplicable;
161
- });
162
- case "Any": return Effect.gen(function* () {
163
- let firstDeny;
164
- for (const [index, child] of node.policies.entries()) {
165
- const result = yield* decideNode(child, context, [...path, index]);
166
- if (result._tag === "Allow") return result;
167
- if (result._tag === "Deny") firstDeny ??= result;
168
- }
169
- return firstDeny ?? NotApplicable;
170
- });
171
- case "FirstOf": return Effect.gen(function* () {
172
- for (const [index, child] of node.policies.entries()) {
173
- const result = yield* decideNode(child, context, [...path, index]);
174
- if (result._tag !== "NotApplicable") return result;
175
- }
176
- return NotApplicable;
177
- });
178
- case "Ceiling": return Effect.gen(function* () {
179
- const capped = yield* decideNode(node.cap, context, [...path, 0]);
180
- if (capped._tag === "Deny") return capped;
181
- return yield* decideNode(node.policy, context, [...path, 1]);
182
- });
183
- }
184
- };
185
- const makeContext = (domain, request, trace) => {
186
- const kind = kindOf(request.resource);
187
- const descriptor = kind === void 0 ? void 0 : getDescriptor(domain, kind);
188
- if (kind === void 0 || descriptor === void 0) return;
189
- return {
190
- principal: request.principal,
191
- action: request.action,
192
- kind,
193
- value: request.resource,
194
- field: request.field,
195
- input: {
196
- value: request.resource,
197
- principal: request.principal,
198
- path: descriptor.path
199
- },
200
- trace
201
- };
202
- };
203
- /** @internal */
204
- const decideOf = (policy, request) => {
205
- const context = makeContext(domainOf(policy), request, void 0);
206
- return context === void 0 ? Effect.succeed(NotApplicable) : decideNode(nodeOf(policy), context, []);
207
- };
208
- /** @internal */
209
- const decide = dual(2, decideOf);
210
- /** @internal */
211
- const decideSync = dual(2, (policy, request) => Effect.runSync(decideOf(policy, request)));
212
- /** @internal */
213
- const explainOf = (policy, request) => {
214
- const trace = [];
215
- const context = makeContext(domainOf(policy), request, trace);
216
- if (context === void 0) return Effect.succeed({
217
- decision: NotApplicable,
218
- steps: []
219
- });
220
- return Effect.map(decideNode(nodeOf(policy), context, []), (decision) => ({
221
- decision,
222
- steps: Object.freeze(trace)
223
- }));
224
- };
225
- /** @internal */
226
- const explain = dual(2, explainOf);
227
- /** @internal */
228
- const permittedFieldsOf = Effect.fnUntraced(function* (policy, request) {
229
- const domain = domainOf(policy);
230
- const kind = kindOf(request.resource);
231
- const descriptor = kind === void 0 ? void 0 : getDescriptor(domain, kind);
232
- if (descriptor === void 0) return [];
233
- const output = [];
234
- for (const field of descriptor.fieldPaths) if ((yield* decideOf(policy, {
235
- ...request,
236
- field
237
- }))._tag === "Allow") output.push(field);
238
- return Object.freeze(output);
239
- });
240
- /** @internal */
241
- const permittedFields = dual(2, permittedFieldsOf);
242
- const { always, never, simplify } = condition_exports;
243
- const and = (...values) => simplify(and$1(...values));
244
- const or = (...values) => simplify(or$1(...values));
245
- const not = (value) => simplify(not$1(value));
246
- const ruleCondition = (value, context) => !matchesTarget(value, context.action, context.kind, context.field) ? never : value.when === void 0 ? always : partialOf(value.when, context.input);
247
- /**
248
- * Gives the conditions for "the first rule that applies is an allow rule" and
249
- * "the first rule that applies is a deny rule".
250
- */
251
- const firstApplicableResidual = (values, context) => {
252
- let allow = never;
253
- let deny = never;
254
- for (const value of values.slice().reverse()) {
255
- const applies = ruleCondition(value, context);
256
- if (value._tag === "Allow") {
257
- allow = or(applies, allow);
258
- deny = and(not(applies), deny);
259
- } else {
260
- deny = or(applies, deny);
261
- allow = and(not(applies), allow);
262
- }
263
- }
264
- return {
265
- allow,
266
- deny
267
- };
268
- };
269
- const residualNode = (node, context) => {
270
- switch (node._tag) {
271
- case "Rules": switch (node.algorithm) {
272
- case "denyOverrides": {
273
- const deny = or(...node.rules.filter((value) => value._tag === "Deny").map((value) => ruleCondition(value, context)));
274
- return {
275
- allow: and(or(...node.rules.filter((value) => value._tag === "Allow").map((value) => ruleCondition(value, context))), not(deny)),
276
- deny
277
- };
278
- }
279
- case "permitOverrides": {
280
- const allow = or(...node.rules.filter((value) => value._tag === "Allow").map((value) => ruleCondition(value, context)));
281
- return {
282
- allow,
283
- deny: and(or(...node.rules.filter((value) => value._tag === "Deny").map((value) => ruleCondition(value, context))), not(allow))
284
- };
285
- }
286
- case "firstApplicable": return firstApplicableResidual(node.rules, context);
287
- case "lastApplicable": return firstApplicableResidual(node.rules.slice().reverse(), context);
288
- }
289
- case "All": {
290
- if (node.policies.length === 0) return {
291
- allow: never,
292
- deny: never
293
- };
294
- const pairs = node.policies.map((child) => residualNode(child, context));
295
- const deny = or(...pairs.map((pair) => pair.deny));
296
- return {
297
- allow: and(...pairs.map((pair) => pair.allow), not(deny)),
298
- deny
299
- };
300
- }
301
- case "Any": {
302
- const pairs = node.policies.map((child) => residualNode(child, context));
303
- const allow = or(...pairs.map((pair) => pair.allow));
304
- return {
305
- allow,
306
- deny: and(or(...pairs.map((pair) => pair.deny)), not(allow))
307
- };
308
- }
309
- case "FirstOf": {
310
- let allow = never;
311
- let deny = never;
312
- for (const child of node.policies.slice().reverse()) {
313
- const pair = residualNode(child, context);
314
- const nextAllow = or(pair.allow, and(not(pair.deny), allow));
315
- deny = or(pair.deny, and(not(pair.allow), deny));
316
- allow = nextAllow;
317
- }
318
- return {
319
- allow,
320
- deny
321
- };
322
- }
323
- case "Ceiling": {
324
- const cap = residualNode(node.cap, context);
325
- const inner = residualNode(node.policy, context);
326
- return {
327
- allow: and(not(cap.deny), inner.allow),
328
- deny: or(cap.deny, inner.deny)
329
- };
330
- }
331
- }
332
- };
333
- /** @internal */
334
- const residualPair = (policy, request) => {
335
- const domain = domainOf(policy);
336
- const descriptor = getDescriptor(domain, request.kind);
337
- if (descriptor === void 0) return {
338
- allow: never,
339
- deny: never
340
- };
341
- return residualNode(nodeOf(policy), {
342
- principal: request.principal,
343
- action: request.action,
344
- kind: request.kind,
345
- field: request.field,
346
- input: {
347
- principal: request.principal,
348
- path: descriptor.path
349
- }
350
- });
351
- };
352
- /** @internal */
353
- const residualOf = (policy, request) => residualPair(policy, request).allow;
354
- /** @internal */
355
- const residual = dual(2, residualOf);
356
- const Algorithm = Schema.Literals([
357
- "denyOverrides",
358
- "permitOverrides",
359
- "firstApplicable",
360
- "lastApplicable"
361
- ]);
362
- const encodedRuleSchema = (meta) => Schema.Struct({
363
- _tag: Schema.Literals(["Allow", "Deny"]),
364
- actions: Schema.Array(Schema.String),
365
- resources: Schema.Array(Schema.String),
366
- when: Schema.optionalKey(schema$1),
367
- fields: Schema.String.pipe(Schema.Array, Schema.optionalKey),
368
- reason: Schema.optionalKey(Schema.String),
369
- id: Schema.optionalKey(Schema.String),
370
- meta: Schema.optionalKey(meta ?? Schema.Never)
371
- });
372
- const encodedNodeSchema = (meta) => {
373
- const self = Schema.Union([
374
- Schema.TaggedStruct("Rules", {
375
- algorithm: Algorithm,
376
- rules: Schema.Array(encodedRuleSchema(meta))
377
- }),
378
- Schema.TaggedStruct("All", { policies: Schema.Array(Schema.suspend(() => self)) }),
379
- Schema.TaggedStruct("Any", { policies: Schema.Array(Schema.suspend(() => self)) }),
380
- Schema.TaggedStruct("FirstOf", { policies: Schema.Array(Schema.suspend(() => self)) }),
381
- Schema.TaggedStruct("Ceiling", {
382
- cap: Schema.suspend(() => self),
383
- policy: Schema.suspend(() => self)
384
- })
385
- ]);
386
- return self;
387
- };
388
- const principalFieldPaths = (domain) => {
389
- const output = /* @__PURE__ */ new Set();
390
- const visit = (schema, prefix) => {
391
- if (!Predicate.hasProperty(schema, "fields") || !Predicate.isObject(schema.fields)) return;
392
- const fields = schema.fields;
393
- for (const key of Object.keys(fields)) {
394
- const path = prefix === "" ? key : `${prefix}.${key}`;
395
- output.add(path);
396
- visit(fields[key], path);
397
- }
398
- };
399
- visit(domain.principal, "");
400
- return output;
401
- };
402
- const principalRefs = (ast) => {
403
- const output = [];
404
- const visitOperand = (operand) => {
405
- if (operand._tag === "Principal") output.push(operand.path);
406
- };
407
- const visit = (node) => {
408
- switch (node._tag) {
409
- case "Compare":
410
- visitOperand(node.value);
411
- return;
412
- case "In":
413
- node.values.forEach(visitOperand);
414
- return;
415
- case "Under":
416
- node.prefix.forEach(visitOperand);
417
- return;
418
- case "And":
419
- case "Or":
420
- node.conditions.forEach(visit);
421
- return;
422
- case "Not":
423
- visit(node.condition);
424
- return;
425
- default: return;
426
- }
427
- };
428
- visit(ast);
429
- return output;
430
- };
431
- /**
432
- * Checks the names in an encoded policy against the domain. It gives one
433
- * message for each problem.
434
- */
435
- const validate = (domain, root) => {
436
- const issues = [];
437
- const principalPaths = principalFieldPaths(domain);
438
- const visit = (node, path) => {
439
- switch (node._tag) {
440
- case "Rules":
441
- node.rules.forEach((value, index) => {
442
- const at = [
443
- ...path,
444
- "rules",
445
- index
446
- ];
447
- const kinds = value.resources.includes("*") ? void 0 : value.resources;
448
- const descriptors = [];
449
- for (const kind of value.resources) {
450
- if (kind === "*") continue;
451
- const descriptor = getDescriptor(domain, kind);
452
- if (descriptor === void 0) issues.push({
453
- path: [...at, "resources"],
454
- issue: `There is no resource with the kind "${kind}"`
455
- });
456
- else descriptors.push(descriptor);
457
- }
458
- const known = kinds === void 0 ? Array.from(domain.resources.values()) : descriptors;
459
- for (const action of value.actions) {
460
- if (action === "*") continue;
461
- if (!known.some((descriptor) => descriptor.actions.includes(action))) issues.push({
462
- path: [...at, "actions"],
463
- issue: `The action "${action}" is not an action of these resources`
464
- });
465
- }
466
- const usedFields = [...(value.fields ?? []).filter((field) => !field.includes("*")), ...value.when === void 0 ? [] : fields(value.when)];
467
- for (const field of usedFields) if (!(kinds !== void 0 && descriptors.length > 0 && descriptors.every((descriptor) => descriptor.fieldPaths.includes(field)))) issues.push({
468
- path: at,
469
- issue: `The field "${field}" is not a field of every resource of the rule`
470
- });
471
- if (value.when !== void 0) {
472
- for (const ref of principalRefs(value.when)) if (!principalPaths.has(ref)) issues.push({
473
- path: [...at, "when"],
474
- issue: `The principal has no field "${ref}"`
475
- });
476
- }
477
- });
478
- return;
479
- case "All":
480
- case "Any":
481
- case "FirstOf":
482
- node.policies.forEach((child, index) => visit(child, [
483
- ...path,
484
- "policies",
485
- index
486
- ]));
487
- return;
488
- case "Ceiling":
489
- visit(node.cap, [...path, "cap"]);
490
- visit(node.policy, [...path, "policy"]);
491
- return;
492
- }
493
- };
494
- visit(root, []);
495
- return issues;
496
- };
497
- const decodeNode = (node) => {
498
- switch (node._tag) {
499
- case "Rules": return Object.freeze({
500
- _tag: "Rules",
501
- algorithm: node.algorithm,
502
- rules: Object.freeze(node.rules.map((value) => make(value)))
503
- });
504
- case "All":
505
- case "Any":
506
- case "FirstOf": return Object.freeze({
507
- _tag: node._tag,
508
- policies: Object.freeze(node.policies.map(decodeNode))
509
- });
510
- case "Ceiling": return Object.freeze({
511
- _tag: "Ceiling",
512
- cap: decodeNode(node.cap),
513
- policy: decodeNode(node.policy)
514
- });
515
- }
516
- };
517
- /** @internal */
518
- const schema = (domain) => encodedNodeSchema(domain.meta).pipe(Schema.check(Schema.makeFilter((node) => validate(domain, node))), Schema.decodeTo(Schema.declare((u) => isPolicy(u) && domainOf(u) === domain), SchemaTransformation.transform({
519
- decode: (node) => fromNode(domain, decodeNode(node)),
520
- encode: (policy) => encodeNode(policy.node)
521
- })));
522
- export { TypeId, all, any, ceiling, decide, decideOf, decideSync, define, empty, encodeNode, explain, explainOf, firstOf, fromNode, isPolicy, permittedFields, permittedFieldsOf, residual, residualOf, residualPair, rules, schema };
523
-
524
- //# sourceMappingURL=policy.js.map