@via-profit/ability 2.0.0-rc.8 → 3.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/CHANGELOG.md +64 -0
- package/CONTRIBUTING.md +14 -0
- package/LICENSE +21 -0
- package/README.md +1081 -166
- package/SECURITY.md +33 -0
- package/dist/AbilityCode.d.ts +6 -6
- package/dist/AbilityCompare.d.ts +2 -2
- package/dist/AbilityCondition.d.ts +13 -10
- package/dist/AbilityError.d.ts +0 -3
- package/dist/AbilityExplain.d.ts +27 -0
- package/dist/AbilityMatch.d.ts +2 -2
- package/dist/AbilityParser.d.ts +45 -4
- package/dist/AbilityPolicy.d.ts +23 -12
- package/dist/AbilityPolicyEffect.d.ts +2 -2
- package/dist/AbilityResolver.d.ts +2 -0
- package/dist/AbilityRule.d.ts +28 -5
- package/dist/AbilityRuleSet.d.ts +15 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +441 -101
- package/package.json +11 -3
- package/assets/ability-01.drawio.png +0 -0
- package/build/playground.js +0 -814
- package/build/playground.js.map +0 -1
- package/dist/AbilityPolicyResult.d.ts +0 -6
- package/dist/playground.d.ts +0 -26
package/dist/index.js
CHANGED
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
10
10
|
exports.AbilityCode = void 0;
|
|
11
11
|
class AbilityCode {
|
|
12
|
-
|
|
12
|
+
_code;
|
|
13
13
|
constructor(code) {
|
|
14
|
-
this.
|
|
14
|
+
this._code = code;
|
|
15
|
+
}
|
|
16
|
+
get code() {
|
|
17
|
+
return this._code;
|
|
15
18
|
}
|
|
16
19
|
isEqual(compareWith) {
|
|
17
20
|
return compareWith !== null && this.code === compareWith.code;
|
|
@@ -19,18 +22,6 @@ class AbilityCode {
|
|
|
19
22
|
isNotEqual(compareWith) {
|
|
20
23
|
return !this.isEqual(compareWith);
|
|
21
24
|
}
|
|
22
|
-
static fromLiteral(literal) {
|
|
23
|
-
let Code = null;
|
|
24
|
-
Object.keys(this).forEach(member => {
|
|
25
|
-
if (member === literal) {
|
|
26
|
-
Code = this[member];
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
if (!Code) {
|
|
30
|
-
throw new Error(`Mismatch error. The literal ${literal} can not be find as a member of this class`);
|
|
31
|
-
}
|
|
32
|
-
return new this(Code.code);
|
|
33
|
-
}
|
|
34
25
|
}
|
|
35
26
|
exports.AbilityCode = AbilityCode;
|
|
36
27
|
exports["default"] = AbilityCode;
|
|
@@ -68,15 +59,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
68
59
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
69
60
|
exports.AbilityCondition = void 0;
|
|
70
61
|
const AbilityCode_1 = __importDefault(__webpack_require__(19));
|
|
62
|
+
const AbilityError_1 = __webpack_require__(122);
|
|
71
63
|
class AbilityCondition extends AbilityCode_1.default {
|
|
72
|
-
static
|
|
73
|
-
static
|
|
74
|
-
static
|
|
75
|
-
static
|
|
76
|
-
static
|
|
77
|
-
static
|
|
78
|
-
static
|
|
79
|
-
static
|
|
64
|
+
static equal = new AbilityCondition('=');
|
|
65
|
+
static not_equal = new AbilityCondition('<>');
|
|
66
|
+
static more_than = new AbilityCondition('>');
|
|
67
|
+
static less_than = new AbilityCondition('<');
|
|
68
|
+
static less_or_equal = new AbilityCondition('<=');
|
|
69
|
+
static more_or_equal = new AbilityCondition('>=');
|
|
70
|
+
static in = new AbilityCondition('in');
|
|
71
|
+
static not_in = new AbilityCondition('not in');
|
|
72
|
+
static fromLiteral(literal) {
|
|
73
|
+
switch (literal) {
|
|
74
|
+
case 'equal':
|
|
75
|
+
return this.equal;
|
|
76
|
+
case 'not_equal':
|
|
77
|
+
return this.not_equal;
|
|
78
|
+
case 'more_than':
|
|
79
|
+
return this.more_than;
|
|
80
|
+
case 'less_than':
|
|
81
|
+
return this.less_than;
|
|
82
|
+
case 'less_or_equal':
|
|
83
|
+
return this.less_or_equal;
|
|
84
|
+
case 'more_or_equal':
|
|
85
|
+
return this.more_or_equal;
|
|
86
|
+
case 'in':
|
|
87
|
+
return this.in;
|
|
88
|
+
case 'not_in':
|
|
89
|
+
return this.not_in;
|
|
90
|
+
default:
|
|
91
|
+
throw new AbilityError_1.AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
get literal() {
|
|
95
|
+
const literal = Object.keys(AbilityCondition).find(member => {
|
|
96
|
+
const val = AbilityCondition[member];
|
|
97
|
+
return val.code === this.code;
|
|
98
|
+
});
|
|
99
|
+
if (typeof literal === 'undefined') {
|
|
100
|
+
throw new Error(`Literal value does not found in class AbilityCondition`);
|
|
101
|
+
}
|
|
102
|
+
return literal;
|
|
103
|
+
}
|
|
80
104
|
}
|
|
81
105
|
exports.AbilityCondition = AbilityCondition;
|
|
82
106
|
exports["default"] = AbilityCondition;
|
|
@@ -89,7 +113,7 @@ exports["default"] = AbilityCondition;
|
|
|
89
113
|
|
|
90
114
|
|
|
91
115
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
92
|
-
exports.
|
|
116
|
+
exports.AbilityParserError = exports.AbilityError = void 0;
|
|
93
117
|
class AbilityError extends Error {
|
|
94
118
|
constructor(message) {
|
|
95
119
|
super(message);
|
|
@@ -102,12 +126,74 @@ class AbilityParserError extends Error {
|
|
|
102
126
|
}
|
|
103
127
|
}
|
|
104
128
|
exports.AbilityParserError = AbilityParserError;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
/***/ }),
|
|
132
|
+
|
|
133
|
+
/***/ 363:
|
|
134
|
+
/***/ (function(__unused_webpack_module, exports, __webpack_require__) {
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
138
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
139
|
+
};
|
|
140
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
141
|
+
exports.AbilityExplainPolicy = exports.AbilityExplainRuleSet = exports.AbilityExplainRule = exports.AbilityExplain = void 0;
|
|
142
|
+
const AbilityMatch_1 = __importDefault(__webpack_require__(909));
|
|
143
|
+
class AbilityExplain {
|
|
144
|
+
type;
|
|
145
|
+
children;
|
|
146
|
+
name;
|
|
147
|
+
match;
|
|
148
|
+
constructor(config, children = []) {
|
|
149
|
+
this.type = config.type;
|
|
150
|
+
this.children = children;
|
|
151
|
+
this.name = config.name;
|
|
152
|
+
this.match = config.match;
|
|
153
|
+
}
|
|
154
|
+
toString(indent = 0) {
|
|
155
|
+
const pad = ' '.repeat(indent);
|
|
156
|
+
const mark = this.match.code === AbilityMatch_1.default.match.code ? '✓' : '✗';
|
|
157
|
+
let out = `${pad}${mark} ${this.type} «${this.name}» is ${this.match.code}`;
|
|
158
|
+
this.children.forEach(child => {
|
|
159
|
+
out += '\n' + child.toString(indent + 1);
|
|
160
|
+
});
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
exports.AbilityExplain = AbilityExplain;
|
|
165
|
+
class AbilityExplainRule extends AbilityExplain {
|
|
166
|
+
constructor(rule) {
|
|
167
|
+
super({
|
|
168
|
+
type: 'rule',
|
|
169
|
+
match: rule.state,
|
|
170
|
+
name: rule.name,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.AbilityExplainRule = AbilityExplainRule;
|
|
175
|
+
class AbilityExplainRuleSet extends AbilityExplain {
|
|
176
|
+
constructor(ruleSet) {
|
|
177
|
+
const children = ruleSet.rules.map(rule => new AbilityExplainRule(rule));
|
|
178
|
+
super({
|
|
179
|
+
type: 'ruleSet',
|
|
180
|
+
match: ruleSet.state,
|
|
181
|
+
name: ruleSet.name,
|
|
182
|
+
}, children);
|
|
108
183
|
}
|
|
109
184
|
}
|
|
110
|
-
exports.
|
|
185
|
+
exports.AbilityExplainRuleSet = AbilityExplainRuleSet;
|
|
186
|
+
class AbilityExplainPolicy extends AbilityExplain {
|
|
187
|
+
constructor(policy) {
|
|
188
|
+
const children = policy.ruleSet.map(ruleSet => new AbilityExplainRuleSet(ruleSet));
|
|
189
|
+
super({
|
|
190
|
+
type: 'policy',
|
|
191
|
+
name: policy.name,
|
|
192
|
+
match: policy.matchState,
|
|
193
|
+
}, children);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.AbilityExplainPolicy = AbilityExplainPolicy;
|
|
111
197
|
|
|
112
198
|
|
|
113
199
|
/***/ }),
|
|
@@ -145,20 +231,6 @@ exports.AbilityParser = void 0;
|
|
|
145
231
|
const AbilityError_1 = __webpack_require__(122);
|
|
146
232
|
const AbilityCondition_1 = __importDefault(__webpack_require__(261));
|
|
147
233
|
class AbilityParser {
|
|
148
|
-
/*
|
|
149
|
-
*
|
|
150
|
-
* readonly ['order.update']: {
|
|
151
|
-
* readonly user: {
|
|
152
|
-
* readonly roles: readonly string[];
|
|
153
|
-
* readonly department: string;
|
|
154
|
-
* };
|
|
155
|
-
* readonly order: {
|
|
156
|
-
* readonly estimatedArrivalAt: number;
|
|
157
|
-
* readonly status: string;
|
|
158
|
-
* }
|
|
159
|
-
* }
|
|
160
|
-
*
|
|
161
|
-
* */
|
|
162
234
|
/**
|
|
163
235
|
* Sets a value in a nested object structure based on a dot/bracket notation path.
|
|
164
236
|
* @param object - The target object to modify.
|
|
@@ -166,52 +238,222 @@ class AbilityParser {
|
|
|
166
238
|
* @param value - The value to set at the specified path.
|
|
167
239
|
*/
|
|
168
240
|
static setValueDotValue(object, path, value) {
|
|
169
|
-
|
|
241
|
+
if (!path || path.trim().length === 0) {
|
|
242
|
+
throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
|
|
243
|
+
}
|
|
244
|
+
const way = path.replace(/\[/g, '.').replace(/]/g, '').split('.');
|
|
170
245
|
const last = way.pop();
|
|
171
246
|
if (!last) {
|
|
172
247
|
throw new AbilityError_1.AbilityParserError(`Invalid path provided on a [${path}]`);
|
|
173
248
|
}
|
|
174
|
-
way.reduce((
|
|
175
|
-
|
|
176
|
-
|
|
249
|
+
const lastObj = way.reduce((acc, key, index, array) => {
|
|
250
|
+
const currentValue = acc[key];
|
|
251
|
+
const nextKey = array[index + 1];
|
|
252
|
+
const shouldBeArray = nextKey !== undefined && isFinite(Number(nextKey));
|
|
253
|
+
if (currentValue === undefined || currentValue === null) {
|
|
254
|
+
// Create missing property
|
|
255
|
+
const newValue = shouldBeArray ? [] : {};
|
|
256
|
+
acc[key] = newValue;
|
|
257
|
+
return newValue;
|
|
258
|
+
}
|
|
259
|
+
if (typeof currentValue !== 'object') {
|
|
260
|
+
throw new AbilityError_1.AbilityParserError(`Cannot set property '${key}' on non-object value at path: ${path}`);
|
|
177
261
|
}
|
|
178
|
-
return
|
|
179
|
-
}, object)
|
|
262
|
+
return currentValue;
|
|
263
|
+
}, object);
|
|
264
|
+
const existingValue = lastObj[last];
|
|
265
|
+
if (existingValue !== undefined &&
|
|
266
|
+
typeof existingValue === 'object' &&
|
|
267
|
+
existingValue !== null &&
|
|
268
|
+
!Array.isArray(existingValue)) {
|
|
269
|
+
throw new AbilityError_1.AbilityParserError(`Cannot set primitive value on existing object at path: ${path}`);
|
|
270
|
+
}
|
|
271
|
+
lastObj[last] = value;
|
|
180
272
|
}
|
|
181
273
|
/**
|
|
182
274
|
* Generates TypeScript type definitions based on the provided policies.
|
|
183
275
|
* @param policies - An array of AbilityPolicy instances.
|
|
184
|
-
* @
|
|
185
|
-
* @returns A record containing the generated type definitions.
|
|
276
|
+
* @returns A generated type definitions.
|
|
186
277
|
*/
|
|
187
|
-
static generateTypeDefs(policies
|
|
188
|
-
|
|
278
|
+
static generateTypeDefs(policies) {
|
|
279
|
+
// Structure to store types: { [action]: { [subjectPath]: type } }
|
|
280
|
+
const typeStructure = {};
|
|
281
|
+
// Iterate through all policies
|
|
189
282
|
policies.forEach(policy => {
|
|
283
|
+
const action = policy.action;
|
|
284
|
+
// Initialize object for action if it doesn't exist
|
|
285
|
+
if (!typeStructure[action]) {
|
|
286
|
+
typeStructure[action] = {};
|
|
287
|
+
}
|
|
288
|
+
// Iterate through all ruleSets in the policy
|
|
190
289
|
policy.ruleSet.forEach(ruleSet => {
|
|
290
|
+
// Iterate through all rules in the ruleSet
|
|
191
291
|
ruleSet.rules.forEach(rule => {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
break;
|
|
202
|
-
case rule.condition.isEqual(AbilityCondition_1.default.MORE_OR_EQUAL):
|
|
203
|
-
case rule.condition.isEqual(AbilityCondition_1.default.MORE_THAN):
|
|
204
|
-
case rule.condition.isEqual(AbilityCondition_1.default.LESS_OR_EQUAL):
|
|
205
|
-
case rule.condition.isEqual(AbilityCondition_1.default.LESS_THAN):
|
|
206
|
-
value = 'number';
|
|
207
|
-
break;
|
|
292
|
+
const subjectPath = rule.subject;
|
|
293
|
+
const existingType = typeStructure[action][subjectPath];
|
|
294
|
+
const ruleType = this.determineTypeFromRule(rule);
|
|
295
|
+
// If a type already exists for this path, create a union
|
|
296
|
+
if (existingType && existingType !== ruleType) {
|
|
297
|
+
typeStructure[action][subjectPath] = `${existingType} | ${ruleType}`;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
typeStructure[action][subjectPath] = ruleType;
|
|
208
301
|
}
|
|
209
|
-
AbilityParser.setValueDotValue(record, rule.subject, value);
|
|
210
302
|
});
|
|
211
303
|
});
|
|
212
304
|
});
|
|
213
|
-
|
|
214
|
-
|
|
305
|
+
// Transform flat structure into nested structure for easier use
|
|
306
|
+
const nestedStructure = this.buildNestedStructure(typeStructure);
|
|
307
|
+
return this.formatTypeDefinitions(nestedStructure);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Determines TypeScript type based on the rule
|
|
311
|
+
* @param rule - The rule to analyze
|
|
312
|
+
* @returns TypeScript type as string
|
|
313
|
+
*/
|
|
314
|
+
static determineTypeFromRule(rule) {
|
|
315
|
+
// Numeric comparisons - always number
|
|
316
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.more_than) ||
|
|
317
|
+
rule.condition.isEqual(AbilityCondition_1.default.less_than) ||
|
|
318
|
+
rule.condition.isEqual(AbilityCondition_1.default.more_or_equal) ||
|
|
319
|
+
rule.condition.isEqual(AbilityCondition_1.default.less_or_equal)) {
|
|
320
|
+
return 'number';
|
|
321
|
+
}
|
|
322
|
+
// Array operations
|
|
323
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.in) ||
|
|
324
|
+
rule.condition.isEqual(AbilityCondition_1.default.not_in)) {
|
|
325
|
+
return this.getArrayType(rule.resource);
|
|
326
|
+
}
|
|
327
|
+
// Equality/Inequality operations
|
|
328
|
+
if (rule.condition.isEqual(AbilityCondition_1.default.equal) ||
|
|
329
|
+
rule.condition.isEqual(AbilityCondition_1.default.not_equal)) {
|
|
330
|
+
return this.getPrimitiveType(rule.resource);
|
|
331
|
+
}
|
|
332
|
+
return 'any';
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Gets TypeScript type for array values
|
|
336
|
+
* @param resource - The resource value to analyze
|
|
337
|
+
* @returns TypeScript array type as string
|
|
338
|
+
*/
|
|
339
|
+
static getArrayType(resource) {
|
|
340
|
+
if (Array.isArray(resource)) {
|
|
341
|
+
if (resource.length === 0)
|
|
342
|
+
return 'any[]';
|
|
343
|
+
// Determine types of array elements
|
|
344
|
+
const elementTypes = new Set(resource.map(item => this.getPrimitiveType(item)));
|
|
345
|
+
const elementType = elementTypes.size === 1
|
|
346
|
+
? Array.from(elementTypes)[0]
|
|
347
|
+
: `(${Array.from(elementTypes).join(' | ')})`;
|
|
348
|
+
return `${elementType}[]`;
|
|
349
|
+
}
|
|
350
|
+
// If resource is not an array but condition is in/not_in,
|
|
351
|
+
// it expects an array of such elements
|
|
352
|
+
return `${this.getPrimitiveType(resource)}[]`;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Gets primitive TypeScript type for a value
|
|
356
|
+
* @param value - The value to analyze
|
|
357
|
+
* @returns TypeScript primitive type as string
|
|
358
|
+
*/
|
|
359
|
+
static getPrimitiveType(value) {
|
|
360
|
+
if (value === null)
|
|
361
|
+
return 'null';
|
|
362
|
+
if (value === undefined)
|
|
363
|
+
return 'undefined';
|
|
364
|
+
switch (typeof value) {
|
|
365
|
+
case 'string':
|
|
366
|
+
return 'string';
|
|
367
|
+
case 'number':
|
|
368
|
+
return 'number';
|
|
369
|
+
case 'boolean':
|
|
370
|
+
return 'boolean';
|
|
371
|
+
case 'object':
|
|
372
|
+
if (Array.isArray(value)) {
|
|
373
|
+
return 'array'; // special marker, handled separately
|
|
374
|
+
}
|
|
375
|
+
return 'object';
|
|
376
|
+
default:
|
|
377
|
+
return 'any';
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Builds nested structure from flat paths
|
|
382
|
+
* Example: 'user.profile.name' -> { user: { profile: { name: 'string' } } }
|
|
383
|
+
* @param flatStructure - Flat structure with dot notation paths
|
|
384
|
+
* @returns Nested object structure
|
|
385
|
+
*/
|
|
386
|
+
static buildNestedStructure(flatStructure) {
|
|
387
|
+
const result = {};
|
|
388
|
+
Object.entries(flatStructure).forEach(([action, paths]) => {
|
|
389
|
+
result[action] = {};
|
|
390
|
+
Object.entries(paths).forEach(([path, type]) => {
|
|
391
|
+
const parts = path.split('.');
|
|
392
|
+
let current = result[action];
|
|
393
|
+
// Iterate through all parts except the last one
|
|
394
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
395
|
+
const part = parts[i];
|
|
396
|
+
const currentValue = current[part];
|
|
397
|
+
if (!currentValue || typeof currentValue !== 'object') {
|
|
398
|
+
const newObj = {};
|
|
399
|
+
current[part] = newObj;
|
|
400
|
+
current = newObj;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
current = currentValue;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// Set type for the last part
|
|
407
|
+
const lastPart = parts[parts.length - 1];
|
|
408
|
+
current[lastPart] = type;
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Formats type structure into a string
|
|
415
|
+
* @param structure - Nested type structure
|
|
416
|
+
* @returns Formatted TypeScript type definition string
|
|
417
|
+
*/
|
|
418
|
+
static formatTypeDefinitions(structure) {
|
|
419
|
+
let output = '// Automatically generated by via-profit/ability\n';
|
|
420
|
+
output += '// Do not edit manually\n\n';
|
|
421
|
+
output += 'export type Resources = {\n';
|
|
422
|
+
// Sort actions for stable output
|
|
423
|
+
const sortedActions = Object.keys(structure).sort();
|
|
424
|
+
sortedActions.forEach(action => {
|
|
425
|
+
output += ` ['${action}']: {\n`;
|
|
426
|
+
output += this.formatNestedObject(structure[action], 4);
|
|
427
|
+
output += ' };\n';
|
|
428
|
+
});
|
|
429
|
+
output += '}\n';
|
|
430
|
+
return output;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Recursively formats nested object
|
|
434
|
+
* @param obj - Object to format
|
|
435
|
+
* @param indent - Current indentation level
|
|
436
|
+
* @returns Formatted string
|
|
437
|
+
*/
|
|
438
|
+
static formatNestedObject(obj, indent) {
|
|
439
|
+
const spaces = ' '.repeat(indent);
|
|
440
|
+
let output = '';
|
|
441
|
+
// Sort keys for stable output
|
|
442
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
443
|
+
sortedKeys.forEach(key => {
|
|
444
|
+
const value = obj[key];
|
|
445
|
+
if (typeof value === 'object' && value !== null) {
|
|
446
|
+
// Nested object
|
|
447
|
+
output += `${spaces}readonly ${key}: {\n`;
|
|
448
|
+
output += this.formatNestedObject(value, indent + 2);
|
|
449
|
+
output += `${spaces}};\n`;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
// Primitive type
|
|
453
|
+
output += `${spaces}readonly ${key}: ${value};\n`;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
return output;
|
|
215
457
|
}
|
|
216
458
|
}
|
|
217
459
|
exports.AbilityParser = AbilityParser;
|
|
@@ -233,6 +475,8 @@ const AbilityRuleSet_1 = __importDefault(__webpack_require__(402));
|
|
|
233
475
|
const AbilityMatch_1 = __importDefault(__webpack_require__(909));
|
|
234
476
|
const AbilityCompare_1 = __importDefault(__webpack_require__(923));
|
|
235
477
|
const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(277));
|
|
478
|
+
const AbilityExplain_1 = __webpack_require__(363);
|
|
479
|
+
const AbilityError_1 = __webpack_require__(122);
|
|
236
480
|
class AbilityPolicy {
|
|
237
481
|
matchState = AbilityMatch_1.default.pending;
|
|
238
482
|
/**
|
|
@@ -259,15 +503,17 @@ class AbilityPolicy {
|
|
|
259
503
|
*/
|
|
260
504
|
id;
|
|
261
505
|
/**
|
|
262
|
-
*
|
|
506
|
+
* Running the `enforce` or `resolve` method
|
|
507
|
+
* will select only those from all passed policies that fall under the specified action.
|
|
263
508
|
*/
|
|
264
509
|
action;
|
|
265
510
|
constructor(params) {
|
|
266
|
-
const { name, id, action, effect } = params;
|
|
511
|
+
const { name, id, action, effect, compareMethod = AbilityCompare_1.default.and } = params;
|
|
267
512
|
this.name = name;
|
|
268
513
|
this.id = id;
|
|
269
514
|
this.action = action;
|
|
270
515
|
this.effect = effect;
|
|
516
|
+
this.compareMethod = compareMethod;
|
|
271
517
|
}
|
|
272
518
|
/**
|
|
273
519
|
* Add rule set to the policy
|
|
@@ -301,6 +547,20 @@ class AbilityPolicy {
|
|
|
301
547
|
}
|
|
302
548
|
return this.matchState;
|
|
303
549
|
}
|
|
550
|
+
explain() {
|
|
551
|
+
if (this.matchState === AbilityMatch_1.default.pending) {
|
|
552
|
+
throw new AbilityError_1.AbilityError('First, run the check method, then explain');
|
|
553
|
+
}
|
|
554
|
+
return new AbilityExplain_1.AbilityExplainPolicy(this);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Parses an array of policy configurations into an array of AbilityPolicy instances.
|
|
558
|
+
* @param configs - Array of policy configurations
|
|
559
|
+
* @returns Array of AbilityPolicy instances
|
|
560
|
+
*/
|
|
561
|
+
static parseAll(configs) {
|
|
562
|
+
return configs.map(config => this.parse(config));
|
|
563
|
+
}
|
|
304
564
|
/**
|
|
305
565
|
* Parse the config JSON format to Policy class instance
|
|
306
566
|
*/
|
|
@@ -313,7 +573,7 @@ class AbilityPolicy {
|
|
|
313
573
|
action,
|
|
314
574
|
effect: new AbilityPolicyEffect_1.default(effect),
|
|
315
575
|
});
|
|
316
|
-
policy.compareMethod = AbilityCompare_1.default
|
|
576
|
+
policy.compareMethod = new AbilityCompare_1.default(compareMethod);
|
|
317
577
|
ruleSet.forEach(ruleSetConfig => {
|
|
318
578
|
policy.addRuleSet(AbilityRuleSet_1.default.parse(ruleSetConfig));
|
|
319
579
|
});
|
|
@@ -368,6 +628,7 @@ exports.AbilityResolver = void 0;
|
|
|
368
628
|
const AbilityPolicyEffect_1 = __importDefault(__webpack_require__(277));
|
|
369
629
|
const AbilityMatch_1 = __importDefault(__webpack_require__(909));
|
|
370
630
|
const AbilityError_1 = __webpack_require__(122);
|
|
631
|
+
const AbilityExplain_1 = __webpack_require__(363);
|
|
371
632
|
class AbilityResolver {
|
|
372
633
|
policies;
|
|
373
634
|
constructor(policyOrListOfPolicies) {
|
|
@@ -389,12 +650,15 @@ class AbilityResolver {
|
|
|
389
650
|
this.policies = filteredPolicies;
|
|
390
651
|
return this;
|
|
391
652
|
}
|
|
653
|
+
resolveWithExplain(action, resource) {
|
|
654
|
+
return this.resolve(action, resource).policies.map(policy => {
|
|
655
|
+
return new AbilityExplain_1.AbilityExplainPolicy(policy);
|
|
656
|
+
});
|
|
657
|
+
}
|
|
392
658
|
enforce(action, resource) {
|
|
393
659
|
const resolver = this.resolve(action, resource);
|
|
394
|
-
if (resolver) {
|
|
395
|
-
|
|
396
|
-
throw new AbilityError_1.PermissionError(resolver.getMatchedPolicy()?.name?.toString() || 'Unknown permission error');
|
|
397
|
-
}
|
|
660
|
+
if (resolver.isDeny()) {
|
|
661
|
+
throw new AbilityError_1.AbilityError(resolver.getMatchedPolicy()?.name?.toString() || 'Unknown permission error');
|
|
398
662
|
}
|
|
399
663
|
}
|
|
400
664
|
/**
|
|
@@ -437,8 +701,7 @@ class AbilityResolver {
|
|
|
437
701
|
const actionBArray = String(actionB).split('.');
|
|
438
702
|
const a = actionAArray.length >= actionBArray.length ? actionAArray : actionBArray;
|
|
439
703
|
const b = actionBArray.length <= actionAArray.length ? actionBArray : actionAArray;
|
|
440
|
-
const c = a
|
|
441
|
-
.reduce((acc, chunk, index) => {
|
|
704
|
+
const c = a.reduce((acc, chunk, index) => {
|
|
442
705
|
const iterationRes = chunk === b[index] || b[index] === '*' || chunk === '*';
|
|
443
706
|
return acc.concat(iterationRes);
|
|
444
707
|
}, []);
|
|
@@ -462,6 +725,9 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
|
462
725
|
exports.AbilityRule = void 0;
|
|
463
726
|
const AbilityMatch_1 = __importDefault(__webpack_require__(909));
|
|
464
727
|
const AbilityCondition_1 = __importDefault(__webpack_require__(261));
|
|
728
|
+
/**
|
|
729
|
+
* Represents a rule that defines a condition to be checked against a subject and resource.
|
|
730
|
+
*/
|
|
465
731
|
class AbilityRule {
|
|
466
732
|
/**
|
|
467
733
|
* Subject key path like a 'user.name'
|
|
@@ -475,13 +741,22 @@ class AbilityRule {
|
|
|
475
741
|
name;
|
|
476
742
|
id;
|
|
477
743
|
state = AbilityMatch_1.default.pending;
|
|
744
|
+
/**
|
|
745
|
+
* Creates an instance of AbilityRule.
|
|
746
|
+
* @param {string} options.id - The unique identifier of the rule.
|
|
747
|
+
* @param {string} options.name - The name of the rule.
|
|
748
|
+
* @param {AbilityCondition} options.condition - The condition to evaluate.
|
|
749
|
+
* @param {string} options.subject - The subject of the rule.
|
|
750
|
+
* @param {string} options.resource - The resource to compare against.
|
|
751
|
+
* @param params
|
|
752
|
+
*/
|
|
478
753
|
constructor(params) {
|
|
479
754
|
const { id, name, subject, resource, condition } = params;
|
|
480
|
-
this.
|
|
481
|
-
this.
|
|
755
|
+
this.name = name || `${JSON.stringify(subject)} ${condition.code} ${JSON.stringify(resource)}`;
|
|
756
|
+
this.id = id || this.name;
|
|
482
757
|
this.subject = subject;
|
|
483
758
|
this.resource = resource;
|
|
484
|
-
this.condition =
|
|
759
|
+
this.condition = condition;
|
|
485
760
|
}
|
|
486
761
|
/**
|
|
487
762
|
* Check if the rule is matched
|
|
@@ -490,25 +765,25 @@ class AbilityRule {
|
|
|
490
765
|
check(resource) {
|
|
491
766
|
let is = false;
|
|
492
767
|
const [valueS, valueO] = this.extractValues(resource);
|
|
493
|
-
if (AbilityCondition_1.default.
|
|
768
|
+
if (AbilityCondition_1.default.less_than.isEqual(this.condition)) {
|
|
494
769
|
is = Number(valueS) < Number(valueO);
|
|
495
770
|
}
|
|
496
|
-
if (AbilityCondition_1.default.
|
|
771
|
+
if (AbilityCondition_1.default.less_or_equal.isEqual(this.condition)) {
|
|
497
772
|
is = Number(valueS) <= Number(valueO);
|
|
498
773
|
}
|
|
499
|
-
if (AbilityCondition_1.default.
|
|
774
|
+
if (AbilityCondition_1.default.more_than.isEqual(this.condition)) {
|
|
500
775
|
is = Number(valueS) > Number(valueO);
|
|
501
776
|
}
|
|
502
|
-
if (AbilityCondition_1.default.
|
|
777
|
+
if (AbilityCondition_1.default.more_or_equal.isEqual(this.condition)) {
|
|
503
778
|
is = Number(valueS) >= Number(valueO);
|
|
504
779
|
}
|
|
505
|
-
if (AbilityCondition_1.default.
|
|
780
|
+
if (AbilityCondition_1.default.equal.isEqual(this.condition)) {
|
|
506
781
|
is = valueS === valueO;
|
|
507
782
|
}
|
|
508
|
-
if (AbilityCondition_1.default.
|
|
783
|
+
if (AbilityCondition_1.default.not_equal.isEqual(this.condition)) {
|
|
509
784
|
is = valueS !== valueO;
|
|
510
785
|
}
|
|
511
|
-
if (AbilityCondition_1.default.
|
|
786
|
+
if (AbilityCondition_1.default.in.isEqual(this.condition)) {
|
|
512
787
|
// [<some>] and [<some>]
|
|
513
788
|
if (Array.isArray(valueS) && Array.isArray(valueO)) {
|
|
514
789
|
is = valueS.some(v => valueO.find(v1 => v1 === v));
|
|
@@ -522,7 +797,7 @@ class AbilityRule {
|
|
|
522
797
|
is = valueS.includes(valueO);
|
|
523
798
|
}
|
|
524
799
|
}
|
|
525
|
-
if (AbilityCondition_1.default.
|
|
800
|
+
if (AbilityCondition_1.default.not_in.isEqual(this.condition)) {
|
|
526
801
|
// [<some>] and [<some>]
|
|
527
802
|
if (Array.isArray(valueS) && Array.isArray(valueO)) {
|
|
528
803
|
is = !valueS.some(v => valueO.find(v1 => v1 === v));
|
|
@@ -601,7 +876,7 @@ class AbilityRule {
|
|
|
601
876
|
name,
|
|
602
877
|
subject,
|
|
603
878
|
resource,
|
|
604
|
-
condition,
|
|
879
|
+
condition: new AbilityCondition_1.default(condition),
|
|
605
880
|
});
|
|
606
881
|
}
|
|
607
882
|
/**
|
|
@@ -616,6 +891,62 @@ class AbilityRule {
|
|
|
616
891
|
condition: this.condition.code,
|
|
617
892
|
};
|
|
618
893
|
}
|
|
894
|
+
static equal(subject, resource) {
|
|
895
|
+
return new AbilityRule({
|
|
896
|
+
condition: AbilityCondition_1.default.equal,
|
|
897
|
+
subject,
|
|
898
|
+
resource,
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
static notIn(subject, resource) {
|
|
902
|
+
return new AbilityRule({
|
|
903
|
+
condition: AbilityCondition_1.default.not_in,
|
|
904
|
+
subject,
|
|
905
|
+
resource,
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
static in(subject, resource) {
|
|
909
|
+
return new AbilityRule({
|
|
910
|
+
condition: AbilityCondition_1.default.in,
|
|
911
|
+
subject,
|
|
912
|
+
resource,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
static notEqual(subject, resource) {
|
|
916
|
+
return new AbilityRule({
|
|
917
|
+
condition: AbilityCondition_1.default.not_equal,
|
|
918
|
+
subject,
|
|
919
|
+
resource,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
static lessThan(subject, resource) {
|
|
923
|
+
return new AbilityRule({
|
|
924
|
+
condition: AbilityCondition_1.default.less_than,
|
|
925
|
+
subject,
|
|
926
|
+
resource,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
static lessOrEqual(subject, resource) {
|
|
930
|
+
return new AbilityRule({
|
|
931
|
+
condition: AbilityCondition_1.default.less_or_equal,
|
|
932
|
+
subject,
|
|
933
|
+
resource,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
static moreThan(subject, resource) {
|
|
937
|
+
return new AbilityRule({
|
|
938
|
+
condition: AbilityCondition_1.default.more_than,
|
|
939
|
+
subject,
|
|
940
|
+
resource,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
static moreOrEqual(subject, resource) {
|
|
944
|
+
return new AbilityRule({
|
|
945
|
+
condition: AbilityCondition_1.default.more_or_equal,
|
|
946
|
+
subject,
|
|
947
|
+
resource,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
619
950
|
}
|
|
620
951
|
exports.AbilityRule = AbilityRule;
|
|
621
952
|
exports["default"] = AbilityRule;
|
|
@@ -658,18 +989,16 @@ class AbilityRuleSet {
|
|
|
658
989
|
id;
|
|
659
990
|
constructor(params) {
|
|
660
991
|
const { name, id, compareMethod } = params;
|
|
661
|
-
this.name = name;
|
|
662
|
-
this.id = id;
|
|
663
|
-
this.compareMethod =
|
|
664
|
-
// this.compareMethod = new AbilityCompare(compareMethod);
|
|
992
|
+
this.name = name || 'No name';
|
|
993
|
+
this.id = id || this.name;
|
|
994
|
+
this.compareMethod = compareMethod;
|
|
665
995
|
}
|
|
666
|
-
addRule(rule
|
|
996
|
+
addRule(rule) {
|
|
667
997
|
this.rules.push(rule);
|
|
668
|
-
this.compareMethod = compareMethod;
|
|
669
998
|
return this;
|
|
670
999
|
}
|
|
671
|
-
addRules(rules
|
|
672
|
-
rules.forEach(rule => this.addRule(rule
|
|
1000
|
+
addRules(rules) {
|
|
1001
|
+
rules.forEach(rule => this.addRule(rule));
|
|
673
1002
|
return this;
|
|
674
1003
|
}
|
|
675
1004
|
check(resources) {
|
|
@@ -698,14 +1027,14 @@ class AbilityRuleSet {
|
|
|
698
1027
|
static parse(config) {
|
|
699
1028
|
const { id, name, rules, compareMethod } = config;
|
|
700
1029
|
const ruleSet = new AbilityRuleSet({
|
|
701
|
-
compareMethod,
|
|
1030
|
+
compareMethod: new AbilityCompare_1.default(compareMethod),
|
|
702
1031
|
name,
|
|
703
1032
|
id,
|
|
704
1033
|
});
|
|
705
1034
|
// Adding rules if exists
|
|
706
1035
|
if (rules && rules.length > 0) {
|
|
707
1036
|
const abilityRules = rules.map(ruleConfig => AbilityRule_1.default.parse(ruleConfig));
|
|
708
|
-
ruleSet.addRules(abilityRules
|
|
1037
|
+
ruleSet.addRules(abilityRules);
|
|
709
1038
|
}
|
|
710
1039
|
return ruleSet;
|
|
711
1040
|
}
|
|
@@ -717,6 +1046,16 @@ class AbilityRuleSet {
|
|
|
717
1046
|
rules: this.rules.map(rule => rule.export()),
|
|
718
1047
|
};
|
|
719
1048
|
}
|
|
1049
|
+
static and(rules) {
|
|
1050
|
+
return new AbilityRuleSet({
|
|
1051
|
+
compareMethod: AbilityCompare_1.default.and,
|
|
1052
|
+
}).addRules(rules);
|
|
1053
|
+
}
|
|
1054
|
+
static or(rules) {
|
|
1055
|
+
return new AbilityRuleSet({
|
|
1056
|
+
compareMethod: AbilityCompare_1.default.or,
|
|
1057
|
+
}).addRules(rules);
|
|
1058
|
+
}
|
|
720
1059
|
}
|
|
721
1060
|
exports.AbilityRuleSet = AbilityRuleSet;
|
|
722
1061
|
exports["default"] = AbilityRuleSet;
|
|
@@ -754,6 +1093,7 @@ __exportStar(__webpack_require__(277), exports);
|
|
|
754
1093
|
__exportStar(__webpack_require__(668), exports);
|
|
755
1094
|
__exportStar(__webpack_require__(476), exports);
|
|
756
1095
|
__exportStar(__webpack_require__(402), exports);
|
|
1096
|
+
__exportStar(__webpack_require__(363), exports);
|
|
757
1097
|
|
|
758
1098
|
|
|
759
1099
|
/***/ })
|