@aooth/arbac 0.1.7 → 0.1.8
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/dist/index.cjs +63 -0
- package/dist/index.d.cts +33 -1
- package/dist/index.d.mts +33 -1
- package/dist/index.mjs +62 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -290,6 +290,28 @@ function mergeScopeFilters(scopes) {
|
|
|
290
290
|
}
|
|
291
291
|
return { $or: scopes };
|
|
292
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Conjoin two ALREADY-UNIONED scope filters (each the output of
|
|
295
|
+
* {@link mergeScopeFilters} for one authority pass) under `$and` semantics — a
|
|
296
|
+
* row survives only if BOTH sides admit it. This is the credential-attenuation
|
|
297
|
+
* combiner: it clips any widening the credential pass might introduce.
|
|
298
|
+
*
|
|
299
|
+
* Polarity is the **opposite** of {@link mergeScopeFilters}: an empty `{}` /
|
|
300
|
+
* `undefined` filter is the universe and acts as the **identity** here
|
|
301
|
+
* (dropped from the `$and`, contributing NO constraint) — never the absorbing
|
|
302
|
+
* "unrestricted wins". Never object-spreads the two filters (credential keys
|
|
303
|
+
* could overwrite user keys and silently widen).
|
|
304
|
+
*
|
|
305
|
+
* @returns the conjoined filter, or `undefined` when BOTH sides are unrestricted.
|
|
306
|
+
*/
|
|
307
|
+
function conjoinScopeFilters(a, b) {
|
|
308
|
+
const aEmpty = !a || Object.keys(a).length === 0;
|
|
309
|
+
const bEmpty = !b || Object.keys(b).length === 0;
|
|
310
|
+
if (aEmpty && bEmpty) return void 0;
|
|
311
|
+
if (aEmpty) return b;
|
|
312
|
+
if (bEmpty) return a;
|
|
313
|
+
return { $and: [a, b] };
|
|
314
|
+
}
|
|
293
315
|
function canOptimizeToIn(scopes) {
|
|
294
316
|
const firstKeys = Object.keys(scopes[0]);
|
|
295
317
|
if (firstKeys.length !== 1) return false;
|
|
@@ -380,6 +402,45 @@ function unionControlsPolicy(scopes) {
|
|
|
380
402
|
}
|
|
381
403
|
return result;
|
|
382
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Conjoin two ALREADY-UNIONED controls policies (each the output of
|
|
407
|
+
* {@link unionControlsPolicy} for one authority pass) under DENY-WINS
|
|
408
|
+
* intersection — a control is permitted only if BOTH passes permit it. This is
|
|
409
|
+
* the credential-attenuation combiner; polarity is the **opposite** of
|
|
410
|
+
* {@link unionControlsPolicy} (which is additive / silence-wins).
|
|
411
|
+
*
|
|
412
|
+
* Per control key (absent ≡ allowed, matching {@link unionControlsPolicy}):
|
|
413
|
+
* - denied (`false`) on EITHER side → `false`.
|
|
414
|
+
* - allowed on one side → the OTHER side's gate (`allow ∧ X = X`).
|
|
415
|
+
* - whitelist ∧ whitelist → the INTERSECTION of the two arrays (only values
|
|
416
|
+
* allowed by both; may be empty = nothing permitted).
|
|
417
|
+
*
|
|
418
|
+
* @returns merged policy; keys absent from the result mean "allowed" (callers
|
|
419
|
+
* treat a missing key as `true`), matching {@link unionControlsPolicy}.
|
|
420
|
+
*/
|
|
421
|
+
function intersectControlsPolicy(a, b) {
|
|
422
|
+
const result = {};
|
|
423
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
424
|
+
for (const key of keys) {
|
|
425
|
+
const av = key in a ? a[key] : true;
|
|
426
|
+
const bv = key in b ? b[key] : true;
|
|
427
|
+
if (av === false || bv === false) {
|
|
428
|
+
result[key] = false;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (av === true) {
|
|
432
|
+
if (Array.isArray(bv)) result[key] = [...bv];
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (bv === true) {
|
|
436
|
+
if (Array.isArray(av)) result[key] = [...av];
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const bset = new Set(bv);
|
|
440
|
+
result[key] = [...new Set(av)].filter((v) => bset.has(v)).toSorted();
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
383
444
|
//#endregion
|
|
384
445
|
//#region src/codegen/extract.ts
|
|
385
446
|
function hasWildcard(s) {
|
|
@@ -455,11 +516,13 @@ function generateResourceTypes(map, options) {
|
|
|
455
516
|
exports.allowTableAction = allowTableAction;
|
|
456
517
|
exports.allowTableRead = allowTableRead;
|
|
457
518
|
exports.allowTableWrite = allowTableWrite;
|
|
519
|
+
exports.conjoinScopeFilters = conjoinScopeFilters;
|
|
458
520
|
exports.definePrivilege = definePrivilege;
|
|
459
521
|
exports.defineRole = defineRole;
|
|
460
522
|
exports.extractResourceActions = extractResourceActions;
|
|
461
523
|
exports.generateResourceTypes = generateResourceTypes;
|
|
462
524
|
exports.getProjectionMode = getProjectionMode;
|
|
525
|
+
exports.intersectControlsPolicy = intersectControlsPolicy;
|
|
463
526
|
exports.isFieldAllowed = isFieldAllowed;
|
|
464
527
|
exports.mergeScopeFilters = mergeScopeFilters;
|
|
465
528
|
exports.restrictProjection = restrictProjection;
|
package/dist/index.d.cts
CHANGED
|
@@ -206,6 +206,21 @@ declare function restrictProjection(desired: TProjection, accessControl: TProjec
|
|
|
206
206
|
* @returns the merged filter, or `undefined` for unrestricted access
|
|
207
207
|
*/
|
|
208
208
|
declare function mergeScopeFilters(scopes: TScopeFilter[]): TScopeFilter | undefined;
|
|
209
|
+
/**
|
|
210
|
+
* Conjoin two ALREADY-UNIONED scope filters (each the output of
|
|
211
|
+
* {@link mergeScopeFilters} for one authority pass) under `$and` semantics — a
|
|
212
|
+
* row survives only if BOTH sides admit it. This is the credential-attenuation
|
|
213
|
+
* combiner: it clips any widening the credential pass might introduce.
|
|
214
|
+
*
|
|
215
|
+
* Polarity is the **opposite** of {@link mergeScopeFilters}: an empty `{}` /
|
|
216
|
+
* `undefined` filter is the universe and acts as the **identity** here
|
|
217
|
+
* (dropped from the `$and`, contributing NO constraint) — never the absorbing
|
|
218
|
+
* "unrestricted wins". Never object-spreads the two filters (credential keys
|
|
219
|
+
* could overwrite user keys and silently widen).
|
|
220
|
+
*
|
|
221
|
+
* @returns the conjoined filter, or `undefined` when BOTH sides are unrestricted.
|
|
222
|
+
*/
|
|
223
|
+
declare function conjoinScopeFilters(a: TScopeFilter | undefined, b: TScopeFilter | undefined): TScopeFilter | undefined;
|
|
209
224
|
//#endregion
|
|
210
225
|
//#region src/scope/controls.d.ts
|
|
211
226
|
/**
|
|
@@ -243,6 +258,23 @@ declare function mergeScopeFilters(scopes: TScopeFilter[]): TScopeFilter | undef
|
|
|
243
258
|
declare function unionControlsPolicy(scopes: ReadonlyArray<{
|
|
244
259
|
controls?: Record<string, ControlGate>;
|
|
245
260
|
}>): Record<string, ControlGate>;
|
|
261
|
+
/**
|
|
262
|
+
* Conjoin two ALREADY-UNIONED controls policies (each the output of
|
|
263
|
+
* {@link unionControlsPolicy} for one authority pass) under DENY-WINS
|
|
264
|
+
* intersection — a control is permitted only if BOTH passes permit it. This is
|
|
265
|
+
* the credential-attenuation combiner; polarity is the **opposite** of
|
|
266
|
+
* {@link unionControlsPolicy} (which is additive / silence-wins).
|
|
267
|
+
*
|
|
268
|
+
* Per control key (absent ≡ allowed, matching {@link unionControlsPolicy}):
|
|
269
|
+
* - denied (`false`) on EITHER side → `false`.
|
|
270
|
+
* - allowed on one side → the OTHER side's gate (`allow ∧ X = X`).
|
|
271
|
+
* - whitelist ∧ whitelist → the INTERSECTION of the two arrays (only values
|
|
272
|
+
* allowed by both; may be empty = nothing permitted).
|
|
273
|
+
*
|
|
274
|
+
* @returns merged policy; keys absent from the result mean "allowed" (callers
|
|
275
|
+
* treat a missing key as `true`), matching {@link unionControlsPolicy}.
|
|
276
|
+
*/
|
|
277
|
+
declare function intersectControlsPolicy(a: Record<string, ControlGate>, b: Record<string, ControlGate>): Record<string, ControlGate>;
|
|
246
278
|
//#endregion
|
|
247
279
|
//#region src/codegen/extract.d.ts
|
|
248
280
|
interface TResourceActionMap {
|
|
@@ -279,4 +311,4 @@ interface TCodegenOptions {
|
|
|
279
311
|
*/
|
|
280
312
|
declare function generateResourceTypes(map: TResourceActionMap, options?: TCodegenOptions): string;
|
|
281
313
|
//#endregion
|
|
282
|
-
export { type ControlGate, type RoleBuilder, type TCodegenOptions, type TPrivilegeFunction, type TProjection, type TProjectionMode, type TResourceActionMap, type TScopeFilter, allowTableAction, allowTableRead, allowTableWrite, definePrivilege, defineRole, extractResourceActions, generateResourceTypes, getProjectionMode, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections };
|
|
314
|
+
export { type ControlGate, type RoleBuilder, type TCodegenOptions, type TPrivilegeFunction, type TProjection, type TProjectionMode, type TResourceActionMap, type TScopeFilter, allowTableAction, allowTableRead, allowTableWrite, conjoinScopeFilters, definePrivilege, defineRole, extractResourceActions, generateResourceTypes, getProjectionMode, intersectControlsPolicy, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections };
|
package/dist/index.d.mts
CHANGED
|
@@ -206,6 +206,21 @@ declare function restrictProjection(desired: TProjection, accessControl: TProjec
|
|
|
206
206
|
* @returns the merged filter, or `undefined` for unrestricted access
|
|
207
207
|
*/
|
|
208
208
|
declare function mergeScopeFilters(scopes: TScopeFilter[]): TScopeFilter | undefined;
|
|
209
|
+
/**
|
|
210
|
+
* Conjoin two ALREADY-UNIONED scope filters (each the output of
|
|
211
|
+
* {@link mergeScopeFilters} for one authority pass) under `$and` semantics — a
|
|
212
|
+
* row survives only if BOTH sides admit it. This is the credential-attenuation
|
|
213
|
+
* combiner: it clips any widening the credential pass might introduce.
|
|
214
|
+
*
|
|
215
|
+
* Polarity is the **opposite** of {@link mergeScopeFilters}: an empty `{}` /
|
|
216
|
+
* `undefined` filter is the universe and acts as the **identity** here
|
|
217
|
+
* (dropped from the `$and`, contributing NO constraint) — never the absorbing
|
|
218
|
+
* "unrestricted wins". Never object-spreads the two filters (credential keys
|
|
219
|
+
* could overwrite user keys and silently widen).
|
|
220
|
+
*
|
|
221
|
+
* @returns the conjoined filter, or `undefined` when BOTH sides are unrestricted.
|
|
222
|
+
*/
|
|
223
|
+
declare function conjoinScopeFilters(a: TScopeFilter | undefined, b: TScopeFilter | undefined): TScopeFilter | undefined;
|
|
209
224
|
//#endregion
|
|
210
225
|
//#region src/scope/controls.d.ts
|
|
211
226
|
/**
|
|
@@ -243,6 +258,23 @@ declare function mergeScopeFilters(scopes: TScopeFilter[]): TScopeFilter | undef
|
|
|
243
258
|
declare function unionControlsPolicy(scopes: ReadonlyArray<{
|
|
244
259
|
controls?: Record<string, ControlGate>;
|
|
245
260
|
}>): Record<string, ControlGate>;
|
|
261
|
+
/**
|
|
262
|
+
* Conjoin two ALREADY-UNIONED controls policies (each the output of
|
|
263
|
+
* {@link unionControlsPolicy} for one authority pass) under DENY-WINS
|
|
264
|
+
* intersection — a control is permitted only if BOTH passes permit it. This is
|
|
265
|
+
* the credential-attenuation combiner; polarity is the **opposite** of
|
|
266
|
+
* {@link unionControlsPolicy} (which is additive / silence-wins).
|
|
267
|
+
*
|
|
268
|
+
* Per control key (absent ≡ allowed, matching {@link unionControlsPolicy}):
|
|
269
|
+
* - denied (`false`) on EITHER side → `false`.
|
|
270
|
+
* - allowed on one side → the OTHER side's gate (`allow ∧ X = X`).
|
|
271
|
+
* - whitelist ∧ whitelist → the INTERSECTION of the two arrays (only values
|
|
272
|
+
* allowed by both; may be empty = nothing permitted).
|
|
273
|
+
*
|
|
274
|
+
* @returns merged policy; keys absent from the result mean "allowed" (callers
|
|
275
|
+
* treat a missing key as `true`), matching {@link unionControlsPolicy}.
|
|
276
|
+
*/
|
|
277
|
+
declare function intersectControlsPolicy(a: Record<string, ControlGate>, b: Record<string, ControlGate>): Record<string, ControlGate>;
|
|
246
278
|
//#endregion
|
|
247
279
|
//#region src/codegen/extract.d.ts
|
|
248
280
|
interface TResourceActionMap {
|
|
@@ -279,4 +311,4 @@ interface TCodegenOptions {
|
|
|
279
311
|
*/
|
|
280
312
|
declare function generateResourceTypes(map: TResourceActionMap, options?: TCodegenOptions): string;
|
|
281
313
|
//#endregion
|
|
282
|
-
export { type ControlGate, type RoleBuilder, type TCodegenOptions, type TPrivilegeFunction, type TProjection, type TProjectionMode, type TResourceActionMap, type TScopeFilter, allowTableAction, allowTableRead, allowTableWrite, definePrivilege, defineRole, extractResourceActions, generateResourceTypes, getProjectionMode, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections };
|
|
314
|
+
export { type ControlGate, type RoleBuilder, type TCodegenOptions, type TPrivilegeFunction, type TProjection, type TProjectionMode, type TResourceActionMap, type TScopeFilter, allowTableAction, allowTableRead, allowTableWrite, conjoinScopeFilters, definePrivilege, defineRole, extractResourceActions, generateResourceTypes, getProjectionMode, intersectControlsPolicy, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections };
|
package/dist/index.mjs
CHANGED
|
@@ -290,6 +290,28 @@ function mergeScopeFilters(scopes) {
|
|
|
290
290
|
}
|
|
291
291
|
return { $or: scopes };
|
|
292
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Conjoin two ALREADY-UNIONED scope filters (each the output of
|
|
295
|
+
* {@link mergeScopeFilters} for one authority pass) under `$and` semantics — a
|
|
296
|
+
* row survives only if BOTH sides admit it. This is the credential-attenuation
|
|
297
|
+
* combiner: it clips any widening the credential pass might introduce.
|
|
298
|
+
*
|
|
299
|
+
* Polarity is the **opposite** of {@link mergeScopeFilters}: an empty `{}` /
|
|
300
|
+
* `undefined` filter is the universe and acts as the **identity** here
|
|
301
|
+
* (dropped from the `$and`, contributing NO constraint) — never the absorbing
|
|
302
|
+
* "unrestricted wins". Never object-spreads the two filters (credential keys
|
|
303
|
+
* could overwrite user keys and silently widen).
|
|
304
|
+
*
|
|
305
|
+
* @returns the conjoined filter, or `undefined` when BOTH sides are unrestricted.
|
|
306
|
+
*/
|
|
307
|
+
function conjoinScopeFilters(a, b) {
|
|
308
|
+
const aEmpty = !a || Object.keys(a).length === 0;
|
|
309
|
+
const bEmpty = !b || Object.keys(b).length === 0;
|
|
310
|
+
if (aEmpty && bEmpty) return void 0;
|
|
311
|
+
if (aEmpty) return b;
|
|
312
|
+
if (bEmpty) return a;
|
|
313
|
+
return { $and: [a, b] };
|
|
314
|
+
}
|
|
293
315
|
function canOptimizeToIn(scopes) {
|
|
294
316
|
const firstKeys = Object.keys(scopes[0]);
|
|
295
317
|
if (firstKeys.length !== 1) return false;
|
|
@@ -380,6 +402,45 @@ function unionControlsPolicy(scopes) {
|
|
|
380
402
|
}
|
|
381
403
|
return result;
|
|
382
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Conjoin two ALREADY-UNIONED controls policies (each the output of
|
|
407
|
+
* {@link unionControlsPolicy} for one authority pass) under DENY-WINS
|
|
408
|
+
* intersection — a control is permitted only if BOTH passes permit it. This is
|
|
409
|
+
* the credential-attenuation combiner; polarity is the **opposite** of
|
|
410
|
+
* {@link unionControlsPolicy} (which is additive / silence-wins).
|
|
411
|
+
*
|
|
412
|
+
* Per control key (absent ≡ allowed, matching {@link unionControlsPolicy}):
|
|
413
|
+
* - denied (`false`) on EITHER side → `false`.
|
|
414
|
+
* - allowed on one side → the OTHER side's gate (`allow ∧ X = X`).
|
|
415
|
+
* - whitelist ∧ whitelist → the INTERSECTION of the two arrays (only values
|
|
416
|
+
* allowed by both; may be empty = nothing permitted).
|
|
417
|
+
*
|
|
418
|
+
* @returns merged policy; keys absent from the result mean "allowed" (callers
|
|
419
|
+
* treat a missing key as `true`), matching {@link unionControlsPolicy}.
|
|
420
|
+
*/
|
|
421
|
+
function intersectControlsPolicy(a, b) {
|
|
422
|
+
const result = {};
|
|
423
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
424
|
+
for (const key of keys) {
|
|
425
|
+
const av = key in a ? a[key] : true;
|
|
426
|
+
const bv = key in b ? b[key] : true;
|
|
427
|
+
if (av === false || bv === false) {
|
|
428
|
+
result[key] = false;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (av === true) {
|
|
432
|
+
if (Array.isArray(bv)) result[key] = [...bv];
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (bv === true) {
|
|
436
|
+
if (Array.isArray(av)) result[key] = [...av];
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const bset = new Set(bv);
|
|
440
|
+
result[key] = [...new Set(av)].filter((v) => bset.has(v)).toSorted();
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
383
444
|
//#endregion
|
|
384
445
|
//#region src/codegen/extract.ts
|
|
385
446
|
function hasWildcard(s) {
|
|
@@ -452,4 +513,4 @@ function generateResourceTypes(map, options) {
|
|
|
452
513
|
return lines.join("\n");
|
|
453
514
|
}
|
|
454
515
|
//#endregion
|
|
455
|
-
export { allowTableAction, allowTableRead, allowTableWrite, definePrivilege, defineRole, extractResourceActions, generateResourceTypes, getProjectionMode, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections };
|
|
516
|
+
export { allowTableAction, allowTableRead, allowTableWrite, conjoinScopeFilters, definePrivilege, defineRole, extractResourceActions, generateResourceTypes, getProjectionMode, intersectControlsPolicy, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aooth/arbac",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Batteries-included RBAC: builder API, privilege factories, scope merge utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"access-control",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@aooth/arbac-core": "0.1.
|
|
48
|
+
"@aooth/arbac-core": "0.1.8"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "vp pack",
|