@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.
- package/README.md +199 -31
- package/dist/Authorizer.d.ts +64 -39
- package/dist/Authorizer.d.ts.map +1 -1
- package/dist/Authorizer.js +291 -6
- package/dist/Authorizer.js.map +1 -1
- package/dist/Condition.d.ts +444 -65
- package/dist/Condition.d.ts.map +1 -1
- package/dist/Condition.js +1007 -25
- package/dist/Condition.js.map +1 -1
- package/dist/Domain.d.ts +27 -17
- package/dist/Domain.d.ts.map +1 -1
- package/dist/Domain.js +52 -10
- package/dist/Domain.js.map +1 -1
- package/dist/HttpApi.d.ts +84 -40
- package/dist/HttpApi.d.ts.map +1 -1
- package/dist/HttpApi.js +103 -13
- package/dist/HttpApi.js.map +1 -1
- package/dist/Policy.d.ts +314 -56
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +1215 -38
- package/dist/Policy.js.map +1 -1
- package/dist/Principal.js +2 -2
- package/dist/Principal.js.map +1 -1
- package/dist/Resource.d.ts +171 -20
- package/dist/Resource.d.ts.map +1 -1
- package/dist/Resource.js +788 -15
- package/dist/Resource.js.map +1 -1
- package/dist/Rule.d.ts +115 -29
- package/dist/Rule.d.ts.map +1 -1
- package/dist/Rule.js +219 -12
- package/dist/Rule.js.map +1 -1
- package/dist/index.js +4 -4
- package/package.json +2 -2
- package/src/Authorizer.ts +625 -61
- package/src/Condition.ts +2403 -112
- package/src/Domain.ts +80 -23
- package/src/HttpApi.ts +225 -25
- package/src/Policy.ts +2836 -96
- package/src/Principal.ts +2 -2
- package/src/Resource.ts +1544 -29
- package/src/Rule.ts +463 -45
- package/dist/internal/authorizer.d.ts +0 -28
- package/dist/internal/authorizer.d.ts.map +0 -1
- package/dist/internal/authorizer.js +0 -153
- package/dist/internal/authorizer.js.map +0 -1
- package/dist/internal/condition.d.ts +0 -11
- package/dist/internal/condition.d.ts.map +0 -1
- package/dist/internal/condition.js +0 -393
- package/dist/internal/condition.js.map +0 -1
- package/dist/internal/domain.js +0 -34
- package/dist/internal/domain.js.map +0 -1
- package/dist/internal/httpApi.js +0 -41
- package/dist/internal/httpApi.js.map +0 -1
- package/dist/internal/policy.js +0 -524
- package/dist/internal/policy.js.map +0 -1
- package/dist/internal/resource.js +0 -66
- package/dist/internal/resource.js.map +0 -1
- package/dist/internal/rule.js +0 -131
- package/dist/internal/rule.js.map +0 -1
- package/src/internal/authorizer.ts +0 -271
- package/src/internal/condition.ts +0 -607
- package/src/internal/domain.ts +0 -56
- package/src/internal/httpApi.ts +0 -84
- package/src/internal/policy.ts +0 -815
- package/src/internal/resource.ts +0 -98
- package/src/internal/rule.ts +0 -203
package/dist/internal/domain.js
DELETED
|
@@ -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"}
|
package/dist/internal/httpApi.js
DELETED
|
@@ -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"}
|
package/dist/internal/policy.js
DELETED
|
@@ -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
|