@nmnmcc/ability 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +322 -469
- package/dist/Authorizer.d.ts +216 -0
- package/dist/Authorizer.d.ts.map +1 -0
- package/dist/Authorizer.js +55 -0
- package/dist/Authorizer.js.map +1 -0
- package/dist/Condition.d.ts +536 -0
- package/dist/Condition.d.ts.map +1 -0
- package/dist/Condition.js +141 -0
- package/dist/Condition.js.map +1 -0
- package/dist/Decision.d.ts +131 -0
- package/dist/Decision.d.ts.map +1 -0
- package/dist/Decision.js +83 -0
- package/dist/Decision.js.map +1 -0
- package/dist/Domain.d.ts +148 -0
- package/dist/Domain.d.ts.map +1 -0
- package/dist/Domain.js +34 -0
- package/dist/Domain.js.map +1 -0
- package/dist/HttpApi.d.ts +129 -0
- package/dist/HttpApi.d.ts.map +1 -0
- package/dist/HttpApi.js +95 -0
- package/dist/HttpApi.js.map +1 -0
- package/dist/Policy.d.ts +429 -0
- package/dist/Policy.d.ts.map +1 -0
- package/dist/Policy.js +162 -0
- package/dist/Policy.js.map +1 -0
- package/dist/Principal.d.ts +35 -0
- package/dist/Principal.d.ts.map +1 -0
- package/dist/Principal.js +37 -0
- package/dist/Principal.js.map +1 -0
- package/dist/Resource.d.ts +213 -0
- package/dist/Resource.d.ts.map +1 -0
- package/dist/Resource.js +71 -0
- package/dist/Resource.js.map +1 -0
- package/dist/Rule.d.ts +189 -0
- package/dist/Rule.d.ts.map +1 -0
- package/dist/Rule.js +50 -0
- package/dist/Rule.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +1 -2
- package/dist/index.d.ts +9 -3
- package/dist/index.js +9 -3
- package/dist/internal/authorizer.d.ts +28 -0
- package/dist/internal/authorizer.d.ts.map +1 -0
- package/dist/internal/authorizer.js +153 -0
- package/dist/internal/authorizer.js.map +1 -0
- package/dist/internal/condition.d.ts +11 -0
- package/dist/internal/condition.d.ts.map +1 -0
- package/dist/internal/condition.js +393 -0
- package/dist/internal/condition.js.map +1 -0
- package/dist/internal/domain.js +34 -0
- package/dist/internal/domain.js.map +1 -0
- package/dist/internal/httpApi.js +41 -0
- package/dist/internal/httpApi.js.map +1 -0
- package/dist/internal/policy.js +524 -0
- package/dist/internal/policy.js.map +1 -0
- package/dist/internal/resource.js +66 -0
- package/dist/internal/resource.js.map +1 -0
- package/dist/internal/rule.js +131 -0
- package/dist/internal/rule.js.map +1 -0
- package/package.json +61 -72
- package/src/Authorizer.ts +330 -0
- package/src/Condition.ts +706 -0
- package/src/Decision.ts +117 -0
- package/src/Domain.ts +189 -0
- package/src/HttpApi.ts +148 -0
- package/src/Policy.ts +575 -0
- package/src/Principal.ts +41 -0
- package/src/Resource.ts +290 -0
- package/src/Rule.ts +265 -0
- package/src/index.ts +57 -0
- package/src/internal/authorizer.ts +271 -0
- package/src/internal/condition.ts +607 -0
- package/src/internal/domain.ts +56 -0
- package/src/internal/httpApi.ts +84 -0
- package/src/internal/policy.ts +815 -0
- package/src/internal/resource.ts +98 -0
- package/src/internal/rule.ts +203 -0
- package/dist/Ability.d.ts +0 -491
- package/dist/Ability.js +0 -207
- package/dist/AbilityExtra.d.ts +0 -79
- package/dist/AbilityExtra.js +0 -187
- package/dist/internal/ability.d.ts +0 -90
- package/dist/internal/ability.js +0 -550
- package/dist/internal/serializable.js +0 -22
package/dist/internal/ability.js
DELETED
|
@@ -1,550 +0,0 @@
|
|
|
1
|
-
import { cloneSerializable } from "./serializable.js";
|
|
2
|
-
import { createMongoAbility, mongoQueryMatcher } from "@casl/ability";
|
|
3
|
-
import * as Data from "effect/Data";
|
|
4
|
-
import * as Effect from "effect/Effect";
|
|
5
|
-
import * as Equal from "effect/Equal";
|
|
6
|
-
import * as Formatter from "effect/Formatter";
|
|
7
|
-
import { dual } from "effect/Function";
|
|
8
|
-
import * as Hash from "effect/Hash";
|
|
9
|
-
import * as Inspectable from "effect/Inspectable";
|
|
10
|
-
import * as Pipeable from "effect/Pipeable";
|
|
11
|
-
import * as Predicate from "effect/Predicate";
|
|
12
|
-
//#region src/internal/ability.ts
|
|
13
|
-
/**
|
|
14
|
-
* @since 0.1.0
|
|
15
|
-
*/
|
|
16
|
-
/**
|
|
17
|
-
* @internal
|
|
18
|
-
*/
|
|
19
|
-
const TypeId = Symbol.for("@nmnmcc/ability/Ability");
|
|
20
|
-
/**
|
|
21
|
-
* @internal
|
|
22
|
-
*/
|
|
23
|
-
const SubjectTypeId = Symbol.for("@nmnmcc/ability/Subject");
|
|
24
|
-
/**
|
|
25
|
-
* @internal
|
|
26
|
-
*/
|
|
27
|
-
const RuleTypeId = Symbol.for("@nmnmcc/ability/Rule");
|
|
28
|
-
const anyAction = "manage";
|
|
29
|
-
const anySubject = "all";
|
|
30
|
-
const CaslStateTypeId = Symbol.for("@nmnmcc/ability/Ability/caslState");
|
|
31
|
-
/**
|
|
32
|
-
* @since 0.1.0
|
|
33
|
-
* @category Errors
|
|
34
|
-
*/
|
|
35
|
-
var AuthorizationError = class extends Data.TaggedError("AuthorizationError") {};
|
|
36
|
-
/**
|
|
37
|
-
* @since 0.1.0
|
|
38
|
-
* @category Errors
|
|
39
|
-
*/
|
|
40
|
-
var ConditionError = class extends Data.TaggedError("ConditionError") {};
|
|
41
|
-
/**
|
|
42
|
-
* @since 0.1.0
|
|
43
|
-
* @category Errors
|
|
44
|
-
*/
|
|
45
|
-
var RawRuleError = class extends Data.TaggedError("RawRuleError") {};
|
|
46
|
-
/**
|
|
47
|
-
* @since 0.1.0
|
|
48
|
-
* @category Errors
|
|
49
|
-
*/
|
|
50
|
-
var SubjectDetectionError = class extends Data.TaggedError("SubjectDetectionError") {};
|
|
51
|
-
/**
|
|
52
|
-
* @since 0.1.0
|
|
53
|
-
* @category Errors
|
|
54
|
-
*/
|
|
55
|
-
var AliasError = class extends Data.TaggedError("AliasError") {};
|
|
56
|
-
/**
|
|
57
|
-
* @since 0.1.0
|
|
58
|
-
* @category Errors
|
|
59
|
-
*/
|
|
60
|
-
var QueryGenerationError = class extends Data.TaggedError("QueryGenerationError") {};
|
|
61
|
-
const copyArray = (value) => Object.freeze(Array.from(value));
|
|
62
|
-
const wrapArray = (value) => Array.isArray(value) ? value : [value];
|
|
63
|
-
const normalizeArray = (value) => Array.isArray(value) ? copyArray(value) : value;
|
|
64
|
-
const unique = (values) => Object.freeze(Array.from(new Set(values)));
|
|
65
|
-
const aliasError = (reason, action) => {
|
|
66
|
-
const error = { reason };
|
|
67
|
-
if (action !== void 0) error.action = action;
|
|
68
|
-
return new AliasError(error);
|
|
69
|
-
};
|
|
70
|
-
const normalizeActionAliases = (aliases) => {
|
|
71
|
-
if (aliases === void 0) return Object.freeze({});
|
|
72
|
-
const normalized = {};
|
|
73
|
-
for (const action of Object.keys(aliases)) {
|
|
74
|
-
if (action === anyAction) throw aliasError(`Cannot use "${anyAction}" as an action alias`, action);
|
|
75
|
-
const targets = wrapArray(aliases[action]);
|
|
76
|
-
if (targets.length === 0) throw aliasError("Action alias cannot target an empty action list", action);
|
|
77
|
-
for (const target of targets) if (target === anyAction) throw aliasError(`Cannot create an alias to reserved action "${anyAction}"`, action);
|
|
78
|
-
normalized[action] = copyArray(targets);
|
|
79
|
-
}
|
|
80
|
-
const visiting = /* @__PURE__ */ new Set();
|
|
81
|
-
const visited = /* @__PURE__ */ new Set();
|
|
82
|
-
const visit = (action, path) => {
|
|
83
|
-
if (visited.has(action)) return;
|
|
84
|
-
if (visiting.has(action)) throw aliasError(`Detected cyclic action alias: ${[...path, action].join(" -> ")}`, action);
|
|
85
|
-
const targets = normalized[action];
|
|
86
|
-
if (targets === void 0) return;
|
|
87
|
-
visiting.add(action);
|
|
88
|
-
for (const target of targets) {
|
|
89
|
-
if (target === action) throw aliasError(`Action alias "${action}" cannot reference itself`, action);
|
|
90
|
-
visit(target, [...path, action]);
|
|
91
|
-
}
|
|
92
|
-
visiting.delete(action);
|
|
93
|
-
visited.add(action);
|
|
94
|
-
};
|
|
95
|
-
for (const action of Object.keys(normalized)) visit(action, []);
|
|
96
|
-
return Object.freeze(normalized);
|
|
97
|
-
};
|
|
98
|
-
const expandAction = (aliases, action, output) => {
|
|
99
|
-
if (output.has(action)) return;
|
|
100
|
-
output.add(action);
|
|
101
|
-
const targets = aliases[action];
|
|
102
|
-
if (targets === void 0) return;
|
|
103
|
-
for (const target of targets) expandAction(aliases, target, output);
|
|
104
|
-
};
|
|
105
|
-
/**
|
|
106
|
-
* @internal
|
|
107
|
-
*/
|
|
108
|
-
const createAliasResolver = (aliases) => {
|
|
109
|
-
const normalized = normalizeActionAliases(aliases);
|
|
110
|
-
return (action) => {
|
|
111
|
-
const actions = /* @__PURE__ */ new Set();
|
|
112
|
-
for (const value of wrapArray(action)) expandAction(normalized, value, actions);
|
|
113
|
-
return unique(actions);
|
|
114
|
-
};
|
|
115
|
-
};
|
|
116
|
-
function* ruleIterator(rule) {
|
|
117
|
-
return yield rule;
|
|
118
|
-
}
|
|
119
|
-
const inspectableToString = function() {
|
|
120
|
-
return Formatter.format(this.toJSON(), {
|
|
121
|
-
ignoreToString: true,
|
|
122
|
-
space: 2
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
const inspectableNode = function() {
|
|
126
|
-
return this.toJSON();
|
|
127
|
-
};
|
|
128
|
-
const ruleHashInput = (rule) => ({
|
|
129
|
-
_tag: rule._tag,
|
|
130
|
-
action: rule.action,
|
|
131
|
-
subject: rule.subject,
|
|
132
|
-
fields: rule.fields,
|
|
133
|
-
conditions: rule.conditions,
|
|
134
|
-
reason: rule.reason
|
|
135
|
-
});
|
|
136
|
-
const RuleProto = {
|
|
137
|
-
...Pipeable.Prototype,
|
|
138
|
-
[RuleTypeId]: RuleTypeId,
|
|
139
|
-
[Symbol.iterator]() {
|
|
140
|
-
return ruleIterator(this);
|
|
141
|
-
},
|
|
142
|
-
asEffect() {
|
|
143
|
-
return Effect.succeed(this);
|
|
144
|
-
},
|
|
145
|
-
[Equal.symbol](that) {
|
|
146
|
-
return isRule(that) && Equal.equals(ruleHashInput(this), ruleHashInput(that));
|
|
147
|
-
},
|
|
148
|
-
[Hash.symbol]() {
|
|
149
|
-
return Hash.structure(ruleHashInput(this));
|
|
150
|
-
},
|
|
151
|
-
toString: inspectableToString,
|
|
152
|
-
toJSON() {
|
|
153
|
-
const json = {
|
|
154
|
-
_tag: this._tag,
|
|
155
|
-
action: this.action,
|
|
156
|
-
subject: this.subject
|
|
157
|
-
};
|
|
158
|
-
if (this.fields !== void 0) json.fields = this.fields;
|
|
159
|
-
if (this.conditions !== void 0) json.conditions = this.conditions;
|
|
160
|
-
if (this.reason !== void 0) json.reason = this.reason;
|
|
161
|
-
return json;
|
|
162
|
-
},
|
|
163
|
-
[Inspectable.NodeInspectSymbol]: inspectableNode
|
|
164
|
-
};
|
|
165
|
-
const AbilityProto = {
|
|
166
|
-
...Pipeable.Prototype,
|
|
167
|
-
[TypeId]: TypeId,
|
|
168
|
-
[Equal.symbol](that) {
|
|
169
|
-
return isAbility(that) && Equal.equals(this.rules, that.rules) && Equal.equals(this.options, that.options);
|
|
170
|
-
},
|
|
171
|
-
[Hash.symbol]() {
|
|
172
|
-
return Hash.structure({
|
|
173
|
-
rules: this.rules,
|
|
174
|
-
options: this.options
|
|
175
|
-
});
|
|
176
|
-
},
|
|
177
|
-
toString: inspectableToString,
|
|
178
|
-
toJSON() {
|
|
179
|
-
return {
|
|
180
|
-
_id: "Ability",
|
|
181
|
-
rules: this.rules,
|
|
182
|
-
options: this.options
|
|
183
|
-
};
|
|
184
|
-
},
|
|
185
|
-
[Inspectable.NodeInspectSymbol]: inspectableNode
|
|
186
|
-
};
|
|
187
|
-
const SubjectProto = {
|
|
188
|
-
...Pipeable.Prototype,
|
|
189
|
-
[SubjectTypeId]: SubjectTypeId,
|
|
190
|
-
[Equal.symbol](that) {
|
|
191
|
-
return isSubject(that) && this.subject === that.subject && Equal.equals(this.value, that.value);
|
|
192
|
-
},
|
|
193
|
-
[Hash.symbol]() {
|
|
194
|
-
return Hash.structure({
|
|
195
|
-
subject: this.subject,
|
|
196
|
-
value: this.value
|
|
197
|
-
});
|
|
198
|
-
},
|
|
199
|
-
toString: inspectableToString,
|
|
200
|
-
toJSON() {
|
|
201
|
-
return {
|
|
202
|
-
_id: "Subject",
|
|
203
|
-
subject: this.subject,
|
|
204
|
-
value: this.value
|
|
205
|
-
};
|
|
206
|
-
},
|
|
207
|
-
[Inspectable.NodeInspectSymbol]: inspectableNode
|
|
208
|
-
};
|
|
209
|
-
const freezeOptions = (options) => {
|
|
210
|
-
const copy = {};
|
|
211
|
-
if (options?.detectSubjectType !== void 0) copy.detectSubjectType = options.detectSubjectType;
|
|
212
|
-
if (options?.actionAliases !== void 0) copy.actionAliases = normalizeActionAliases(options.actionAliases);
|
|
213
|
-
return Object.freeze(copy);
|
|
214
|
-
};
|
|
215
|
-
const freezeRule = (rule) => {
|
|
216
|
-
const copy = Object.create(RuleProto);
|
|
217
|
-
copy._tag = rule._tag;
|
|
218
|
-
copy.action = normalizeArray(rule.action);
|
|
219
|
-
copy.subject = normalizeArray(rule.subject);
|
|
220
|
-
if (rule.fields !== void 0) copy.fields = copyArray(rule.fields);
|
|
221
|
-
if (rule.conditions !== void 0) copy.conditions = cloneSerializable(rule.conditions);
|
|
222
|
-
if (rule.reason !== void 0) copy.reason = rule.reason;
|
|
223
|
-
return Object.freeze(copy);
|
|
224
|
-
};
|
|
225
|
-
const toCaslRawRule = (rule) => {
|
|
226
|
-
const raw = {
|
|
227
|
-
action: cloneSerializable(rule.action),
|
|
228
|
-
subject: cloneSerializable(rule.subject)
|
|
229
|
-
};
|
|
230
|
-
if (rule.fields !== void 0) raw.fields = cloneSerializable(rule.fields);
|
|
231
|
-
if (rule.conditions !== void 0) raw.conditions = cloneSerializable(rule.conditions);
|
|
232
|
-
if (rule._tag === "Deny") raw.inverted = true;
|
|
233
|
-
if (rule.reason !== void 0) raw.reason = rule.reason;
|
|
234
|
-
return raw;
|
|
235
|
-
};
|
|
236
|
-
const buildCaslState = (rules, aliases) => {
|
|
237
|
-
const resolver = createAliasResolver(aliases ?? {});
|
|
238
|
-
const rulesByRawRule = /* @__PURE__ */ new WeakMap();
|
|
239
|
-
const indexByRule = /* @__PURE__ */ new Map();
|
|
240
|
-
const rawRules = [];
|
|
241
|
-
let ruleIndex = 0;
|
|
242
|
-
for (const rule of rules) {
|
|
243
|
-
const rawRule = toCaslRawRule(rule);
|
|
244
|
-
rawRules.push(rawRule);
|
|
245
|
-
rulesByRawRule.set(rawRule, rule);
|
|
246
|
-
indexByRule.set(rule, ruleIndex);
|
|
247
|
-
ruleIndex = ruleIndex + 1;
|
|
248
|
-
}
|
|
249
|
-
return Object.freeze({
|
|
250
|
-
ability: createMongoAbility(rawRules, {
|
|
251
|
-
anyAction,
|
|
252
|
-
anySubjectType: anySubject,
|
|
253
|
-
resolveAction: (action) => resolver(action)
|
|
254
|
-
}),
|
|
255
|
-
rulesByRawRule,
|
|
256
|
-
indexByRule: Object.freeze(indexByRule)
|
|
257
|
-
});
|
|
258
|
-
};
|
|
259
|
-
const getCaslState = (self) => self[CaslStateTypeId];
|
|
260
|
-
/**
|
|
261
|
-
* @internal
|
|
262
|
-
*/
|
|
263
|
-
const make = (rules, options) => {
|
|
264
|
-
const self = Object.create(AbilityProto);
|
|
265
|
-
self.rules = Object.freeze(Array.from(rules, freezeRule));
|
|
266
|
-
self.options = freezeOptions(options);
|
|
267
|
-
self[CaslStateTypeId] = buildCaslState(self.rules, self.options.actionAliases);
|
|
268
|
-
return Object.freeze(self);
|
|
269
|
-
};
|
|
270
|
-
const makeRule = (kind, action, subjectName, options) => {
|
|
271
|
-
const rule = Object.create(RuleProto);
|
|
272
|
-
rule._tag = kind;
|
|
273
|
-
rule.action = normalizeArray(action);
|
|
274
|
-
rule.subject = normalizeArray(subjectName);
|
|
275
|
-
if (options?.fields !== void 0) rule.fields = copyArray(wrapArray(options.fields));
|
|
276
|
-
if (options?.conditions !== void 0) rule.conditions = cloneSerializable(options.conditions);
|
|
277
|
-
if (options?.reason !== void 0) rule.reason = options.reason;
|
|
278
|
-
return Object.freeze(rule);
|
|
279
|
-
};
|
|
280
|
-
const builder = () => ({
|
|
281
|
-
allow: (action, subjectName, options) => makeRule("Allow", action, subjectName, options),
|
|
282
|
-
deny: (action, subjectName, options) => makeRule("Deny", action, subjectName, options)
|
|
283
|
-
});
|
|
284
|
-
/**
|
|
285
|
-
* @internal
|
|
286
|
-
*/
|
|
287
|
-
const define = (() => (body, options) => {
|
|
288
|
-
const rules = [];
|
|
289
|
-
const iterator = body(builder());
|
|
290
|
-
let state = iterator.next();
|
|
291
|
-
while (!state.done) {
|
|
292
|
-
rules.push(state.value);
|
|
293
|
-
state = iterator.next(state.value);
|
|
294
|
-
}
|
|
295
|
-
return make(rules, options);
|
|
296
|
-
});
|
|
297
|
-
/**
|
|
298
|
-
* @internal
|
|
299
|
-
*/
|
|
300
|
-
const subject = (subjectName, value) => {
|
|
301
|
-
const wrapped = Object.create(SubjectProto);
|
|
302
|
-
wrapped._tag = "Subject";
|
|
303
|
-
wrapped.subject = subjectName;
|
|
304
|
-
wrapped.value = value;
|
|
305
|
-
return Object.freeze(wrapped);
|
|
306
|
-
};
|
|
307
|
-
/**
|
|
308
|
-
* @internal
|
|
309
|
-
*/
|
|
310
|
-
const isSubject = (value) => Predicate.hasProperty(value, SubjectTypeId);
|
|
311
|
-
/**
|
|
312
|
-
* @internal
|
|
313
|
-
*/
|
|
314
|
-
const isRule = (value) => Predicate.hasProperty(value, RuleTypeId);
|
|
315
|
-
/**
|
|
316
|
-
* @internal
|
|
317
|
-
*/
|
|
318
|
-
const isAbility = (value) => Predicate.hasProperty(value, TypeId);
|
|
319
|
-
/**
|
|
320
|
-
* @internal
|
|
321
|
-
*/
|
|
322
|
-
const unwrapSubject = (value) => isSubject(value) ? value.value : value;
|
|
323
|
-
const detectFromConstructor = (value) => {
|
|
324
|
-
const constructor = value.constructor;
|
|
325
|
-
if (typeof constructor?.modelName === "string" && constructor.modelName.length > 0) return constructor.modelName;
|
|
326
|
-
if (typeof constructor?.name === "string" && constructor.name.length > 0) return constructor.name;
|
|
327
|
-
};
|
|
328
|
-
/**
|
|
329
|
-
* @internal
|
|
330
|
-
*/
|
|
331
|
-
const detectSubjectType = Effect.fnUntraced(function* (self, value) {
|
|
332
|
-
if (isSubject(value)) return value.subject;
|
|
333
|
-
if (self.options.detectSubjectType !== void 0) return yield* Effect.try({
|
|
334
|
-
try: () => self.options.detectSubjectType?.(value),
|
|
335
|
-
catch: (cause) => new SubjectDetectionError({
|
|
336
|
-
reason: "Failed to detect subject type with the configured detector",
|
|
337
|
-
cause
|
|
338
|
-
})
|
|
339
|
-
});
|
|
340
|
-
const detected = detectFromConstructor(value);
|
|
341
|
-
if (detected !== void 0) return detected;
|
|
342
|
-
return yield* Effect.fail(new SubjectDetectionError({ reason: "Unable to detect subject type" }));
|
|
343
|
-
});
|
|
344
|
-
const hasOwnSubject = (request) => "subject" in request && typeof request.subject === "string";
|
|
345
|
-
const hasOwnValue = (request) => "value" in request && typeof request.value === "object" && request.value !== null;
|
|
346
|
-
const toRuntimeRequest = Effect.fnUntraced(function* (self, request) {
|
|
347
|
-
const runtime = {
|
|
348
|
-
action: request.action,
|
|
349
|
-
subject: ""
|
|
350
|
-
};
|
|
351
|
-
if (hasOwnSubject(request)) runtime.subject = request.subject;
|
|
352
|
-
else if (hasOwnValue(request)) runtime.subject = yield* detectSubjectType(self, request.value);
|
|
353
|
-
else return yield* Effect.fail(new SubjectDetectionError({ reason: "A check request must include either a subject or a value" }));
|
|
354
|
-
if (hasOwnValue(request)) runtime.value = unwrapSubject(request.value);
|
|
355
|
-
if (request.field !== void 0) runtime.field = request.field;
|
|
356
|
-
return runtime;
|
|
357
|
-
});
|
|
358
|
-
const toRuntimeSubjectRequest = Effect.fnUntraced(function* (self, request) {
|
|
359
|
-
const runtime = { subject: "" };
|
|
360
|
-
if (hasOwnSubject(request)) runtime.subject = request.subject;
|
|
361
|
-
else if (hasOwnValue(request)) runtime.subject = yield* detectSubjectType(self, request.value);
|
|
362
|
-
else return yield* Effect.fail(new SubjectDetectionError({ reason: "A subject query request must include either a subject or a value" }));
|
|
363
|
-
if (hasOwnValue(request)) runtime.value = unwrapSubject(request.value);
|
|
364
|
-
return runtime;
|
|
365
|
-
});
|
|
366
|
-
const matchesField = (candidate, request) => candidate.caslRule.matchesField(request.field);
|
|
367
|
-
const isEmptyCondition = (conditions) => Object.keys(conditions).length === 0;
|
|
368
|
-
const makeConditionError = (request, cause) => {
|
|
369
|
-
const error = {
|
|
370
|
-
action: request.action,
|
|
371
|
-
subject: request.subject,
|
|
372
|
-
cause
|
|
373
|
-
};
|
|
374
|
-
if (request.field !== void 0) error.field = request.field;
|
|
375
|
-
return new ConditionError(error);
|
|
376
|
-
};
|
|
377
|
-
const matchesConditions = Effect.fnUntraced(function* (candidate, request) {
|
|
378
|
-
const { rule } = candidate;
|
|
379
|
-
if (rule.conditions === void 0) return true;
|
|
380
|
-
if (request.value === void 0) return rule._tag === "Allow" || isEmptyCondition(rule.conditions);
|
|
381
|
-
return yield* Effect.try({
|
|
382
|
-
try: () => candidate.caslRule.matchesConditions(request.value),
|
|
383
|
-
catch: (cause) => makeConditionError(request, cause)
|
|
384
|
-
});
|
|
385
|
-
});
|
|
386
|
-
const matchesCandidateRule = Effect.fnUntraced(function* (candidate, request) {
|
|
387
|
-
if (!matchesField(candidate, request)) return false;
|
|
388
|
-
return yield* matchesConditions(candidate, request);
|
|
389
|
-
});
|
|
390
|
-
const getRuntimeRule = (state, caslRule) => {
|
|
391
|
-
const rule = state.rulesByRawRule.get(caslRule.origin);
|
|
392
|
-
if (rule === void 0) throw new Error("CASL rule is not owned by this Ability");
|
|
393
|
-
return rule;
|
|
394
|
-
};
|
|
395
|
-
const collectRuleCandidates = (self, request, order) => {
|
|
396
|
-
const state = getCaslState(self);
|
|
397
|
-
const candidates = state.ability.possibleRulesFor(request.action, request.subject).map((caslRule) => ({
|
|
398
|
-
rule: getRuntimeRule(state, caslRule),
|
|
399
|
-
caslRule
|
|
400
|
-
}));
|
|
401
|
-
const sorted = order === "descending" ? candidates : candidates.slice().sort((a, b) => (state.indexByRule.get(a.rule) ?? 0) - (state.indexByRule.get(b.rule) ?? 0));
|
|
402
|
-
return Object.freeze(sorted);
|
|
403
|
-
};
|
|
404
|
-
const possibleRulesForRequest = (self, request) => Object.freeze(collectRuleCandidates(self, request, "descending").map((candidate) => candidate.rule));
|
|
405
|
-
const possibleRuleCandidatesForDefinitionOrder = (self, request) => collectRuleCandidates(self, request, "ascending");
|
|
406
|
-
const relevantRuleForEffect = Effect.fnUntraced(function* (self, request) {
|
|
407
|
-
const candidates = collectRuleCandidates(self, request, "descending");
|
|
408
|
-
let index = 0;
|
|
409
|
-
while (index < candidates.length) {
|
|
410
|
-
const candidate = candidates[index];
|
|
411
|
-
if (candidate !== void 0) {
|
|
412
|
-
if (yield* matchesCandidateRule(candidate, request)) return candidate.rule;
|
|
413
|
-
}
|
|
414
|
-
index = index + 1;
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
const makeAuthorizationError = (request, rule) => {
|
|
418
|
-
const error = {
|
|
419
|
-
action: request.action,
|
|
420
|
-
subject: request.subject
|
|
421
|
-
};
|
|
422
|
-
if (request.field !== void 0) error.field = request.field;
|
|
423
|
-
if (rule?.reason !== void 0) error.reason = rule.reason;
|
|
424
|
-
return new AuthorizationError(error);
|
|
425
|
-
};
|
|
426
|
-
/**
|
|
427
|
-
* @internal
|
|
428
|
-
*/
|
|
429
|
-
const check = dual(2, Effect.fnUntraced(function* (self, request) {
|
|
430
|
-
const runtimeRequest = yield* toRuntimeRequest(self, request);
|
|
431
|
-
const rule = yield* relevantRuleForEffect(self, runtimeRequest);
|
|
432
|
-
if (rule?._tag === "Allow") return;
|
|
433
|
-
return yield* Effect.fail(makeAuthorizationError(runtimeRequest, rule));
|
|
434
|
-
}));
|
|
435
|
-
/**
|
|
436
|
-
* @internal
|
|
437
|
-
*/
|
|
438
|
-
const possibleRulesFor = dual(2, Effect.fnUntraced(function* (self, request) {
|
|
439
|
-
return possibleRulesForRequest(self, yield* toRuntimeRequest(self, request));
|
|
440
|
-
}));
|
|
441
|
-
/**
|
|
442
|
-
* @internal
|
|
443
|
-
*/
|
|
444
|
-
const rulesFor = dual(2, Effect.fnUntraced(function* (self, request) {
|
|
445
|
-
const runtimeRequest = yield* toRuntimeRequest(self, request);
|
|
446
|
-
return Object.freeze(collectRuleCandidates(self, runtimeRequest, "descending").filter((candidate) => matchesField(candidate, runtimeRequest)).map((candidate) => candidate.rule));
|
|
447
|
-
}));
|
|
448
|
-
/**
|
|
449
|
-
* @internal
|
|
450
|
-
*/
|
|
451
|
-
const relevantRuleFor = dual(2, Effect.fnUntraced(function* (self, request) {
|
|
452
|
-
return yield* relevantRuleForEffect(self, yield* toRuntimeRequest(self, request));
|
|
453
|
-
}));
|
|
454
|
-
/**
|
|
455
|
-
* @internal
|
|
456
|
-
*/
|
|
457
|
-
const actionsFor = dual(2, Effect.fnUntraced(function* (self, request) {
|
|
458
|
-
const runtimeRequest = yield* toRuntimeSubjectRequest(self, request);
|
|
459
|
-
return unique(getCaslState(self).ability.actionsFor(runtimeRequest.subject));
|
|
460
|
-
}));
|
|
461
|
-
const rawRuleError = (reason, ruleIndex, cause) => {
|
|
462
|
-
const error = {
|
|
463
|
-
reason,
|
|
464
|
-
ruleIndex
|
|
465
|
-
};
|
|
466
|
-
if (cause !== void 0) error.cause = cause;
|
|
467
|
-
return new RawRuleError(error);
|
|
468
|
-
};
|
|
469
|
-
const validateArrayValue = (value, name, index) => {
|
|
470
|
-
if (Array.isArray(value) && value.length === 0) return Effect.fail(rawRuleError(`rawRule.${name} cannot be an empty array`, index));
|
|
471
|
-
return Effect.void;
|
|
472
|
-
};
|
|
473
|
-
const validateRawConditions = (conditions, index) => {
|
|
474
|
-
if (conditions === void 0) return Effect.void;
|
|
475
|
-
return Effect.asVoid(Effect.try({
|
|
476
|
-
try: () => mongoQueryMatcher(conditions),
|
|
477
|
-
catch: (cause) => rawRuleError("rawRule.conditions cannot be compiled", index, cause)
|
|
478
|
-
}));
|
|
479
|
-
};
|
|
480
|
-
/**
|
|
481
|
-
* @internal
|
|
482
|
-
*/
|
|
483
|
-
const fromRawRules = Effect.fnUntraced(function* (rules, options) {
|
|
484
|
-
const built = [];
|
|
485
|
-
let index = 0;
|
|
486
|
-
for (const rawRule of rules) {
|
|
487
|
-
yield* validateArrayValue(rawRule.action, "action", index);
|
|
488
|
-
yield* validateArrayValue(rawRule.subject, "subject", index);
|
|
489
|
-
if (rawRule.fields !== void 0) yield* validateArrayValue(rawRule.fields, "fields", index);
|
|
490
|
-
yield* validateRawConditions(rawRule.conditions, index);
|
|
491
|
-
const ruleOptions = {};
|
|
492
|
-
if (rawRule.fields !== void 0) ruleOptions.fields = rawRule.fields;
|
|
493
|
-
if (rawRule.conditions !== void 0) ruleOptions.conditions = rawRule.conditions;
|
|
494
|
-
if (rawRule.reason !== void 0) ruleOptions.reason = rawRule.reason;
|
|
495
|
-
built.push(makeRule(rawRule.inverted === true ? "Deny" : "Allow", rawRule.action, rawRule.subject, ruleOptions));
|
|
496
|
-
index = index + 1;
|
|
497
|
-
}
|
|
498
|
-
return yield* Effect.try({
|
|
499
|
-
try: () => make(built, options),
|
|
500
|
-
catch: (cause) => cause instanceof AliasError ? cause : rawRuleError("Unable to construct Ability from raw rules", index, cause)
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
const updateEffect = (self, rules) => fromRawRules(rules, self.options);
|
|
504
|
-
/**
|
|
505
|
-
* @internal
|
|
506
|
-
*/
|
|
507
|
-
const update = dual(2, updateEffect);
|
|
508
|
-
const ruleToRaw = (rule) => {
|
|
509
|
-
const raw = {
|
|
510
|
-
action: cloneSerializable(rule.action),
|
|
511
|
-
subject: cloneSerializable(rule.subject)
|
|
512
|
-
};
|
|
513
|
-
if (rule.fields !== void 0) raw.fields = cloneSerializable(rule.fields);
|
|
514
|
-
if (rule.conditions !== void 0) raw.conditions = cloneSerializable(rule.conditions);
|
|
515
|
-
if (rule._tag === "Deny") raw.inverted = true;
|
|
516
|
-
if (rule.reason !== void 0) raw.reason = rule.reason;
|
|
517
|
-
return Object.freeze(raw);
|
|
518
|
-
};
|
|
519
|
-
/**
|
|
520
|
-
* @internal
|
|
521
|
-
*/
|
|
522
|
-
const toRawRules = Effect.fnUntraced(function* (self) {
|
|
523
|
-
const rawRules = [];
|
|
524
|
-
for (const rule of self.rules) rawRules.push(ruleToRaw(rule));
|
|
525
|
-
return Object.freeze(rawRules);
|
|
526
|
-
});
|
|
527
|
-
const fieldsForRule = (rule, options) => rule.fields ?? options.fieldsFrom(rule);
|
|
528
|
-
/**
|
|
529
|
-
* @internal
|
|
530
|
-
*/
|
|
531
|
-
const permittedFields = dual(3, Effect.fnUntraced(function* (self, request, options) {
|
|
532
|
-
const runtimeRequest = yield* toRuntimeRequest(self, request);
|
|
533
|
-
const candidates = possibleRuleCandidatesForDefinitionOrder(self, runtimeRequest);
|
|
534
|
-
const fields = /* @__PURE__ */ new Set();
|
|
535
|
-
let index = 0;
|
|
536
|
-
while (index < candidates.length) {
|
|
537
|
-
const candidate = candidates[index];
|
|
538
|
-
if (candidate !== void 0) {
|
|
539
|
-
if (yield* matchesConditions(candidate, runtimeRequest)) {
|
|
540
|
-
const ruleFields = fieldsForRule(candidate.rule, options);
|
|
541
|
-
for (const field of ruleFields) if (candidate.rule._tag === "Allow") fields.add(field);
|
|
542
|
-
else fields.delete(field);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
index = index + 1;
|
|
546
|
-
}
|
|
547
|
-
return Object.freeze(Array.from(fields));
|
|
548
|
-
}));
|
|
549
|
-
//#endregion
|
|
550
|
-
export { AliasError, AuthorizationError, ConditionError, QueryGenerationError, RawRuleError, RuleTypeId, SubjectDetectionError, SubjectTypeId, TypeId, actionsFor, check, createAliasResolver, define, detectSubjectType, fromRawRules, isAbility, isRule, isSubject, make, permittedFields, possibleRulesFor, relevantRuleFor, rulesFor, subject, toRawRules, unwrapSubject, update };
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
//#region src/internal/serializable.ts
|
|
2
|
-
/**
|
|
3
|
-
* @since 0.1.0
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* @internal
|
|
7
|
-
*/
|
|
8
|
-
const isPlainObject = (value) => value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype;
|
|
9
|
-
/**
|
|
10
|
-
* @internal
|
|
11
|
-
*/
|
|
12
|
-
const cloneSerializable = (value) => {
|
|
13
|
-
if (Array.isArray(value)) return Object.freeze(value.map(cloneSerializable));
|
|
14
|
-
if (isPlainObject(value)) {
|
|
15
|
-
const output = {};
|
|
16
|
-
for (const key of Object.keys(value)) output[key] = cloneSerializable(value[key]);
|
|
17
|
-
return Object.freeze(output);
|
|
18
|
-
}
|
|
19
|
-
return value;
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
|
-
export { cloneSerializable, isPlainObject };
|