@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 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.7",
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.7"
48
+ "@aooth/arbac-core": "0.1.8"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "vp pack",