@cqrs-ddd/pipeline-casl 0.5.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.
Files changed (60) hide show
  1. package/COMMERCIAL_LICENSE.txt +34 -0
  2. package/LICENSE +661 -0
  3. package/README.md +38 -0
  4. package/dist/casl.behavior.d.ts +27 -0
  5. package/dist/casl.behavior.d.ts.map +1 -0
  6. package/dist/casl.behavior.js +57 -0
  7. package/dist/casl.behavior.js.map +1 -0
  8. package/dist/constants/tokens.d.ts +29 -0
  9. package/dist/constants/tokens.d.ts.map +1 -0
  10. package/dist/constants/tokens.js +28 -0
  11. package/dist/constants/tokens.js.map +1 -0
  12. package/dist/errors/missing-ability.error.d.ts +14 -0
  13. package/dist/errors/missing-ability.error.d.ts.map +1 -0
  14. package/dist/errors/missing-ability.error.js +19 -0
  15. package/dist/errors/missing-ability.error.js.map +1 -0
  16. package/dist/errors/unauthorized-action.exception.d.ts +19 -0
  17. package/dist/errors/unauthorized-action.exception.d.ts.map +1 -0
  18. package/dist/errors/unauthorized-action.exception.js +26 -0
  19. package/dist/errors/unauthorized-action.exception.js.map +1 -0
  20. package/dist/helpers/ability-digest.d.ts +47 -0
  21. package/dist/helpers/ability-digest.d.ts.map +1 -0
  22. package/dist/helpers/ability-digest.js +64 -0
  23. package/dist/helpers/ability-digest.js.map +1 -0
  24. package/dist/helpers/ability.d.ts +52 -0
  25. package/dist/helpers/ability.d.ts.map +1 -0
  26. package/dist/helpers/ability.js +148 -0
  27. package/dist/helpers/ability.js.map +1 -0
  28. package/dist/helpers/authorizer.d.ts +73 -0
  29. package/dist/helpers/authorizer.d.ts.map +1 -0
  30. package/dist/helpers/authorizer.js +173 -0
  31. package/dist/helpers/authorizer.js.map +1 -0
  32. package/dist/helpers/capability.d.ts +80 -0
  33. package/dist/helpers/capability.d.ts.map +1 -0
  34. package/dist/helpers/capability.js +287 -0
  35. package/dist/helpers/capability.js.map +1 -0
  36. package/dist/helpers/projection.d.ts +11 -0
  37. package/dist/helpers/projection.d.ts.map +1 -0
  38. package/dist/helpers/projection.js +140 -0
  39. package/dist/helpers/projection.js.map +1 -0
  40. package/dist/helpers/requires.d.ts +27 -0
  41. package/dist/helpers/requires.d.ts.map +1 -0
  42. package/dist/helpers/requires.js +28 -0
  43. package/dist/helpers/requires.js.map +1 -0
  44. package/dist/http.d.ts +19 -0
  45. package/dist/http.d.ts.map +1 -0
  46. package/dist/http.js +25 -0
  47. package/dist/http.js.map +1 -0
  48. package/dist/index.d.ts +17 -0
  49. package/dist/index.d.ts.map +1 -0
  50. package/dist/index.js +16 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/interfaces/permission-source.interface.d.ts +20 -0
  53. package/dist/interfaces/permission-source.interface.d.ts.map +1 -0
  54. package/dist/interfaces/permission-source.interface.js +3 -0
  55. package/dist/interfaces/permission-source.interface.js.map +1 -0
  56. package/dist/types/casl.types.d.ts +25 -0
  57. package/dist/types/casl.types.d.ts.map +1 -0
  58. package/dist/types/casl.types.js +3 -0
  59. package/dist/types/casl.types.js.map +1 -0
  60. package/package.json +64 -0
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @cqrs-ddd/pipeline-casl
2
+
3
+ CASL authorization for `@cqrs-ddd/pipeline`. `CaslBehavior` checks type-level
4
+ requirements before the handler runs (`requires({ action, subject })`), loading the
5
+ caller's rules through your `ICaslPermissionSource`; `CaslAuthorizer` checks the loaded
6
+ entity and projects readable fields inside the handler. `toHttpResponse()` from
7
+ `@cqrs-ddd/pipeline-casl/http` turns a denial into a 403 answer.
8
+
9
+ **Documentation:** [guide](https://aristoteliss.github.io/ddd-cqrs/packages/pipeline-casl/) · [API reference](https://aristoteliss.github.io/ddd-cqrs/api/cqrs-ddd/pipeline-casl/) · [all packages](https://aristoteliss.github.io/ddd-cqrs/)
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @cqrs-ddd/pipeline-casl @cqrs-ddd/pipeline @casl/ability
15
+ ```
16
+
17
+ Requires Node.js 22.12 or later. Published as an ES module; a CommonJS application loads
18
+ it with `require()`.
19
+
20
+ ## Example
21
+
22
+ ```ts
23
+ const pipeline = createPipeline({ behaviors: [new CaslBehavior(permissionSource)] });
24
+
25
+ export const deletePost = pipeline.wrap(
26
+ { name: 'deletePost', kind: 'command' },
27
+ requires({ action: 'delete', subject: 'Post' }),
28
+ )(async (id: string) => posts.remove(id));
29
+ ```
30
+
31
+ A type-level check does not replace the entity check after loading: a cache or
32
+ idempotency hit skips the handler, so their keys must include the principal and its
33
+ permission scope.
34
+
35
+ ## License
36
+
37
+ Dual-licensed under **AGPLv3** and a **Commercial License**. See `LICENSE` and
38
+ `COMMERCIAL_LICENSE.txt`.
@@ -0,0 +1,27 @@
1
+ import { IPipelineBehavior, IPipelineContext, NextDelegate, PIPELINE_BEHAVIOR_ID } from '@cqrs-ddd/pipeline';
2
+ import type { ICaslPermissionSource } from './interfaces/permission-source.interface.js';
3
+ import type { AbilityRequirement } from './types/casl.types.js';
4
+ /** Stable behavior identity; cache and idempotency order themselves after it. */
5
+ export declare const CASL_BEHAVIOR_ID = "@cqrs-ddd/pipeline-casl:CaslBehavior";
6
+ /** Per-handler options, usually declared with `requires(...)`. */
7
+ export interface CaslBehaviorOptions {
8
+ /** Type-level requirements, all of which must pass. */
9
+ rules: readonly [AbilityRequirement, ...AbilityRequirement[]];
10
+ }
11
+ /**
12
+ * Type-level authorization before the handler runs.
13
+ *
14
+ * Loads the caller through `ICaslPermissionSource`, builds the ability, stores
15
+ * it and the principal in `context.items` for `CaslAuthorizer` and
16
+ * security-scoped keys, and throws `UnauthorizedActionException` unless every
17
+ * requirement passes. An unauthenticated caller (`null` input) is denied.
18
+ * Errors from the source or from malformed rules propagate unchanged.
19
+ * A handler without `rules` runs without loading permissions.
20
+ */
21
+ export declare class CaslBehavior implements IPipelineBehavior {
22
+ private readonly source;
23
+ static readonly [PIPELINE_BEHAVIOR_ID] = "@cqrs-ddd/pipeline-casl:CaslBehavior";
24
+ constructor(source: ICaslPermissionSource);
25
+ handle(context: IPipelineContext, next: NextDelegate): Promise<unknown>;
26
+ }
27
+ //# sourceMappingURL=casl.behavior.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"casl.behavior.d.ts","sourceRoot":"","sources":["../src/casl.behavior.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,oBAAoB,EACrB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,iFAAiF;AACjF,eAAO,MAAM,gBAAgB,yCAAyC,CAAC;AAEvE,kEAAkE;AAClE,MAAM,WAAW,mBAAmB;IAClC,uDAAuD;IACvD,KAAK,EAAE,SAAS,CAAC,kBAAkB,EAAE,GAAG,kBAAkB,EAAE,CAAC,CAAC;CAC/D;AAED;;;;;;;;;GASG;AACH,qBAAa,YAAa,YAAW,iBAAiB;IAGxC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,0CAAoB;IAE1D,YAA6B,MAAM,EAAE,qBAAqB,EAMzD;IAEK,MAAM,CACV,OAAO,EAAE,gBAAgB,EACzB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,OAAO,CAAC,CA+BlB;CACF"}
@@ -0,0 +1,57 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { PIPELINE_BEHAVIOR_ID, } from '@cqrs-ddd/pipeline';
3
+ import { CASL_ABILITY_KEY, CASL_PRINCIPAL_KEY } from './constants/tokens.js';
4
+ import { UnauthorizedActionException } from './errors/unauthorized-action.exception.js';
5
+ import { buildAbility } from './helpers/ability.js';
6
+ /** Stable behavior identity; cache and idempotency order themselves after it. */
7
+ export const CASL_BEHAVIOR_ID = '@cqrs-ddd/pipeline-casl:CaslBehavior';
8
+ /**
9
+ * Type-level authorization before the handler runs.
10
+ *
11
+ * Loads the caller through `ICaslPermissionSource`, builds the ability, stores
12
+ * it and the principal in `context.items` for `CaslAuthorizer` and
13
+ * security-scoped keys, and throws `UnauthorizedActionException` unless every
14
+ * requirement passes. An unauthenticated caller (`null` input) is denied.
15
+ * Errors from the source or from malformed rules propagate unchanged.
16
+ * A handler without `rules` runs without loading permissions.
17
+ */
18
+ export class CaslBehavior {
19
+ source;
20
+ static [PIPELINE_BEHAVIOR_ID] = CASL_BEHAVIOR_ID;
21
+ constructor(source) {
22
+ this.source = source;
23
+ if (typeof source?.load !== 'function') {
24
+ throw new TypeError('CaslBehavior requires a permission source (ICaslPermissionSource) with a load() method.');
25
+ }
26
+ }
27
+ async handle(context, next) {
28
+ const rules = context.getBehaviorOptions(CaslBehavior)?.rules;
29
+ if (!rules?.length)
30
+ return next();
31
+ const input = await this.source.load(context);
32
+ if (!input) {
33
+ throw new UnauthorizedActionException({
34
+ action: rules[0].action,
35
+ subject: rules[0].subject,
36
+ reason: 'Access denied — authentication required.',
37
+ });
38
+ }
39
+ const ability = buildAbility(input.rules, input.principal);
40
+ context.items.set(CASL_PRINCIPAL_KEY, input.principal);
41
+ context.items.set(CASL_ABILITY_KEY, ability);
42
+ for (const { action, subject, field } of rules) {
43
+ const allowed = field
44
+ ? ability.can(action, subject, field)
45
+ : ability.can(action, subject);
46
+ if (!allowed) {
47
+ throw new UnauthorizedActionException({
48
+ action,
49
+ subject,
50
+ ...(field ? { fields: [field] } : {}),
51
+ });
52
+ }
53
+ }
54
+ return next();
55
+ }
56
+ }
57
+ //# sourceMappingURL=casl.behavior.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"casl.behavior.js","sourceRoot":"","sources":["../src/casl.behavior.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAIL,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,2BAA2B,EAAE,MAAM,2CAA2C,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAIpD,iFAAiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,sCAAsC,CAAC;AAQvE;;;;;;;;;GASG;AACH,MAAM,OAAO,YAAY;IAGM,MAAM;IAFnC,MAAM,CAAU,CAAC,oBAAoB,CAAC,GAAG,gBAAgB,CAAC;IAE1D,YAA6B,MAA6B;sBAA7B,MAAM;QACjC,IAAI,OAAO,MAAM,EAAE,IAAI,KAAK,UAAU,EAAE,CAAC;YACvC,MAAM,IAAI,SAAS,CACjB,yFAAyF,CAC1F,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM,CACV,OAAyB,EACzB,IAAkB;QAElB,MAAM,KAAK,GACT,OAAO,CAAC,kBAAkB,CAAsB,YAAY,CAAC,EAAE,KAAK,CAAC;QACvE,IAAI,CAAC,KAAK,EAAE,MAAM;YAAE,OAAO,IAAI,EAAE,CAAC;QAElC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,2BAA2B,CAAC;gBACpC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM;gBACvB,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO;gBACzB,MAAM,EAAE,0CAA0C;aACnD,CAAC,CAAC;QACL,CAAC;QAED,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QAC3D,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACvD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QAE7C,KAAK,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE,CAAC;YAC/C,MAAM,OAAO,GAAG,KAAK;gBACnB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC;gBACrC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACjC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,2BAA2B,CAAC;oBACpC,MAAM;oBACN,OAAO;oBACP,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACtC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,IAAI,EAAE,CAAC;IAChB,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ /** `context.items` key holding the ability `CaslBehavior` built for the execution. */
2
+ export declare const CASL_ABILITY_KEY: unique symbol;
3
+ /** `context.items` key holding the principal that ability was built for. */
4
+ export declare const CASL_PRINCIPAL_KEY: unique symbol;
5
+ /**
6
+ * Built-in CASL keyword subjects.
7
+ */
8
+ export declare const CASL_SUBJECTS: {
9
+ /** Wildcard subject matching any entity type. */
10
+ readonly ALL: 'all';
11
+ };
12
+ export type CaslSubject = (typeof CASL_SUBJECTS)[keyof typeof CASL_SUBJECTS];
13
+ /**
14
+ * Standard CASL actions, including built-in wildcard and standard CRUD verbs.
15
+ */
16
+ export declare const CASL_ACTIONS: {
17
+ /** Built-in CASL wildcard action matching any operation. */
18
+ readonly MANAGE: 'manage';
19
+ /** Standard CRUD create action. */
20
+ readonly CREATE: 'create';
21
+ /** Standard CRUD read/retrieve action. */
22
+ readonly READ: 'read';
23
+ /** Standard CRUD update action. */
24
+ readonly UPDATE: 'update';
25
+ /** Standard CRUD delete action. */
26
+ readonly DELETE: 'delete';
27
+ };
28
+ export type CaslAction = (typeof CASL_ACTIONS)[keyof typeof CASL_ACTIONS];
29
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/constants/tokens.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,eAAgD,CAAC;AAE9E,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,eAE9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa;IACxB,iDAAiD;aACjD,GAAG,EAAE,KAAK;CACF,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE7E;;GAEG;AACH,eAAO,MAAM,YAAY;IACvB,4DAA4D;aAC5D,MAAM,EAAE,QAAQ;IAChB,mCAAmC;aACnC,MAAM,EAAE,QAAQ;IAChB,0CAA0C;aAC1C,IAAI,EAAE,MAAM;IACZ,mCAAmC;aACnC,MAAM,EAAE,QAAQ;IAChB,mCAAmC;aACnC,MAAM,EAAE,QAAQ;CACR,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC"}
@@ -0,0 +1,28 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ /** `context.items` key holding the ability `CaslBehavior` built for the execution. */
3
+ export const CASL_ABILITY_KEY = Symbol.for('@cqrs-ddd/pipeline-casl:ability');
4
+ /** `context.items` key holding the principal that ability was built for. */
5
+ export const CASL_PRINCIPAL_KEY = Symbol.for('@cqrs-ddd/pipeline-casl:principal');
6
+ /**
7
+ * Built-in CASL keyword subjects.
8
+ */
9
+ export const CASL_SUBJECTS = {
10
+ /** Wildcard subject matching any entity type. */
11
+ ALL: 'all',
12
+ };
13
+ /**
14
+ * Standard CASL actions, including built-in wildcard and standard CRUD verbs.
15
+ */
16
+ export const CASL_ACTIONS = {
17
+ /** Built-in CASL wildcard action matching any operation. */
18
+ MANAGE: 'manage',
19
+ /** Standard CRUD create action. */
20
+ CREATE: 'create',
21
+ /** Standard CRUD read/retrieve action. */
22
+ READ: 'read',
23
+ /** Standard CRUD update action. */
24
+ UPDATE: 'update',
25
+ /** Standard CRUD delete action. */
26
+ DELETE: 'delete',
27
+ };
28
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/constants/tokens.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,sFAAsF;AACtF,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;AAE9E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAC1C,mCAAmC,CACpC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,iDAAiD;IACjD,GAAG,EAAE,KAAK;CACF,CAAC;AAIX;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,4DAA4D;IAC5D,MAAM,EAAE,QAAQ;IAChB,mCAAmC;IACnC,MAAM,EAAE,QAAQ;IAChB,0CAA0C;IAC1C,IAAI,EAAE,MAAM;IACZ,mCAAmC;IACnC,MAAM,EAAE,QAAQ;IAChB,mCAAmC;IACnC,MAAM,EAAE,QAAQ;CACR,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Raised when code that needs the caller's authorization runs without an
3
+ * ability: `CaslBehavior` did not run for the request, or the code runs outside
4
+ * a pipeline. A configuration failure, not a denial — map it to a server error,
5
+ * never to 403.
6
+ */
7
+ export declare class MissingAbilityError extends Error {
8
+ readonly purpose: string;
9
+ /**
10
+ * @param purpose - What needed the ability, named in the message.
11
+ */
12
+ constructor(purpose: string);
13
+ }
14
+ //# sourceMappingURL=missing-ability.error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-ability.error.d.ts","sourceRoot":"","sources":["../../src/errors/missing-ability.error.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAIhC,QAAQ,CAAC,OAAO,EAAE,MAAM;IAHpC;;OAEG;IACH,YAAqB,OAAO,EAAE,MAAM,EAGnC;CACF"}
@@ -0,0 +1,19 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ /**
3
+ * Raised when code that needs the caller's authorization runs without an
4
+ * ability: `CaslBehavior` did not run for the request, or the code runs outside
5
+ * a pipeline. A configuration failure, not a denial — map it to a server error,
6
+ * never to 403.
7
+ */
8
+ export class MissingAbilityError extends Error {
9
+ purpose;
10
+ /**
11
+ * @param purpose - What needed the ability, named in the message.
12
+ */
13
+ constructor(purpose) {
14
+ super(`No authorization ability is present for ${purpose}.`);
15
+ this.purpose = purpose;
16
+ this.name = MissingAbilityError.name;
17
+ }
18
+ }
19
+ //# sourceMappingURL=missing-ability.error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-ability.error.js","sourceRoot":"","sources":["../../src/errors/missing-ability.error.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE;;;;;GAKG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAIvB,OAAO;IAH5B;;OAEG;IACH,YAAqB,OAAe;QAClC,KAAK,CAAC,2CAA2C,OAAO,GAAG,CAAC,CAAC;uBAD1C,OAAO;QAE1B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC;IACvC,CAAC;CACF"}
@@ -0,0 +1,19 @@
1
+ export interface UnauthorizedActionDetails {
2
+ action: string;
3
+ subject: string;
4
+ entityId?: string | number;
5
+ fields?: string[];
6
+ reason?: string;
7
+ }
8
+ /**
9
+ * Exception thrown when an actor attempts an action or field modification
10
+ * on a subject or entity without the required authorization permissions.
11
+ */
12
+ export declare class UnauthorizedActionException extends Error {
13
+ readonly action: string;
14
+ readonly subject: string;
15
+ readonly entityId?: string | number;
16
+ readonly fields?: string[];
17
+ constructor(details: UnauthorizedActionDetails);
18
+ }
19
+ //# sourceMappingURL=unauthorized-action.exception.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unauthorized-action.exception.d.ts","sourceRoot":"","sources":["../../src/errors/unauthorized-action.exception.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAE3B,YAAY,OAAO,EAAE,yBAAyB,EAgB7C;CACF"}
@@ -0,0 +1,26 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ /**
3
+ * Exception thrown when an actor attempts an action or field modification
4
+ * on a subject or entity without the required authorization permissions.
5
+ */
6
+ export class UnauthorizedActionException extends Error {
7
+ action;
8
+ subject;
9
+ entityId;
10
+ fields;
11
+ constructor(details) {
12
+ const fieldMsg = details.fields?.length
13
+ ? ` on fields: [${details.fields.join(', ')}]`
14
+ : '';
15
+ const idMsg = details.entityId !== undefined ? ` (id=${details.entityId})` : '';
16
+ const msg = details.reason ??
17
+ `Access denied: cannot execute "${details.action}" on "${details.subject}"${idMsg}${fieldMsg}.`;
18
+ super(msg);
19
+ this.name = 'UnauthorizedActionException';
20
+ this.action = details.action;
21
+ this.subject = details.subject;
22
+ this.entityId = details.entityId;
23
+ this.fields = details.fields;
24
+ }
25
+ }
26
+ //# sourceMappingURL=unauthorized-action.exception.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unauthorized-action.exception.js","sourceRoot":"","sources":["../../src/errors/unauthorized-action.exception.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAUtE;;;GAGG;AACH,MAAM,OAAO,2BAA4B,SAAQ,KAAK;IAC3C,MAAM,CAAS;IACf,OAAO,CAAS;IAChB,QAAQ,CAAmB;IAC3B,MAAM,CAAY;IAE3B,YAAY,OAAkC;QAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM;YACrC,CAAC,CAAC,gBAAgB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC9C,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,KAAK,GACT,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,MAAM,GAAG,GACP,OAAO,CAAC,MAAM;YACd,kCAAkC,OAAO,CAAC,MAAM,SAAS,OAAO,CAAC,OAAO,IAAI,KAAK,GAAG,QAAQ,GAAG,CAAC;QAElG,KAAK,CAAC,GAAG,CAAC,CAAC;QACX,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;QAC1C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC/B,CAAC;CACF"}
@@ -0,0 +1,47 @@
1
+ import type { IPipelineContext } from '@cqrs-ddd/pipeline';
2
+ /**
3
+ * A digest of the permissions the current ability grants: the SHA-256 (hex) of
4
+ * its rules in order, with every condition already resolved against the
5
+ * principal. Any change that can change a decision — a rule, its order,
6
+ * inversion, fields, or a principal value a condition interpolates — changes
7
+ * the digest.
8
+ *
9
+ * Use it as the permission-scope segment of a response cache key, or as an
10
+ * idempotency replay scope. It identifies a set of permissions, not a caller:
11
+ * two principals with identical effective rules share a digest, so combine it
12
+ * with the principal wherever the key must separate callers.
13
+ *
14
+ * @param context - The pipeline execution; omitted, the running one.
15
+ * @returns The 64-character hex digest, or `undefined` when no ability is
16
+ * present (`CaslBehavior` has not run, or runs outside a pipeline).
17
+ * @throws {TypeError} When a rule holds a value that is not plain JSON, such as
18
+ * a `RegExp` condition.
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const cacheKey = createPartitionedCacheKeyFactory({
23
+ * principal: (ctx) => getCaslPrincipal(ctx)?.id,
24
+ * scope: abilityDigest,
25
+ * });
26
+ * const replay = idempotent({ keyFactory, replayScopeFactory: abilityDigest });
27
+ * ```
28
+ */
29
+ export declare function abilityDigest(context?: IPipelineContext): string | undefined;
30
+ /**
31
+ * {@link abilityDigest}, failing closed: it throws instead of returning
32
+ * `undefined` when no ability is present. Use it where a missing ability must
33
+ * stop the operation, such as an idempotency replay scope, whose factory must
34
+ * throw rather than let an operation run without a scope.
35
+ *
36
+ * @param context - The pipeline execution; omitted, the running one.
37
+ * @returns The 64-character hex digest.
38
+ * @throws {MissingAbilityError} When no ability is present.
39
+ * @throws {TypeError} When a rule holds a value that is not plain JSON.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * idempotent({ keyFactory, replayScopeFactory: requireAbilityDigest });
44
+ * ```
45
+ */
46
+ export declare function requireAbilityDigest(context?: IPipelineContext): string;
47
+ //# sourceMappingURL=ability-digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ability-digest.d.ts","sourceRoot":"","sources":["../../src/helpers/ability-digest.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAM5E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAMvE"}
@@ -0,0 +1,64 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { createHash } from 'node:crypto';
3
+ import { stableStringify } from '@cqrs-ddd/safe-stringify';
4
+ import { MissingAbilityError } from '../errors/missing-ability.error.js';
5
+ import { getCaslAbility } from './authorizer.js';
6
+ /**
7
+ * A digest of the permissions the current ability grants: the SHA-256 (hex) of
8
+ * its rules in order, with every condition already resolved against the
9
+ * principal. Any change that can change a decision — a rule, its order,
10
+ * inversion, fields, or a principal value a condition interpolates — changes
11
+ * the digest.
12
+ *
13
+ * Use it as the permission-scope segment of a response cache key, or as an
14
+ * idempotency replay scope. It identifies a set of permissions, not a caller:
15
+ * two principals with identical effective rules share a digest, so combine it
16
+ * with the principal wherever the key must separate callers.
17
+ *
18
+ * @param context - The pipeline execution; omitted, the running one.
19
+ * @returns The 64-character hex digest, or `undefined` when no ability is
20
+ * present (`CaslBehavior` has not run, or runs outside a pipeline).
21
+ * @throws {TypeError} When a rule holds a value that is not plain JSON, such as
22
+ * a `RegExp` condition.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const cacheKey = createPartitionedCacheKeyFactory({
27
+ * principal: (ctx) => getCaslPrincipal(ctx)?.id,
28
+ * scope: abilityDigest,
29
+ * });
30
+ * const replay = idempotent({ keyFactory, replayScopeFactory: abilityDigest });
31
+ * ```
32
+ */
33
+ export function abilityDigest(context) {
34
+ const ability = getCaslAbility(context);
35
+ if (!ability)
36
+ return undefined;
37
+ return createHash('sha256')
38
+ .update(stableStringify(ability.rules))
39
+ .digest('hex');
40
+ }
41
+ /**
42
+ * {@link abilityDigest}, failing closed: it throws instead of returning
43
+ * `undefined` when no ability is present. Use it where a missing ability must
44
+ * stop the operation, such as an idempotency replay scope, whose factory must
45
+ * throw rather than let an operation run without a scope.
46
+ *
47
+ * @param context - The pipeline execution; omitted, the running one.
48
+ * @returns The 64-character hex digest.
49
+ * @throws {MissingAbilityError} When no ability is present.
50
+ * @throws {TypeError} When a rule holds a value that is not plain JSON.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * idempotent({ keyFactory, replayScopeFactory: requireAbilityDigest });
55
+ * ```
56
+ */
57
+ export function requireAbilityDigest(context) {
58
+ const digest = abilityDigest(context);
59
+ if (digest === undefined) {
60
+ throw new MissingAbilityError('an authorization digest');
61
+ }
62
+ return digest;
63
+ }
64
+ //# sourceMappingURL=ability-digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ability-digest.js","sourceRoot":"","sources":["../../src/helpers/ability-digest.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,aAAa,CAAC,OAA0B;IACtD,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;SACtC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA0B;IAC7D,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,IAAI,mBAAmB,CAAC,yBAAyB,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { CaslPrincipal } from '../interfaces/permission-source.interface.js';
2
+ import type { AppAbility, Capability, CapabilityString } from '../types/casl.types.js';
3
+ /**
4
+ * Builds the ability for one caller. Every direct rule precedes every inverted
5
+ * rule (stable within each group), so a deny from any source wins over an allow.
6
+ * Placeholders resolve against `principal`; a missing attribute throws.
7
+ *
8
+ * @throws {TypeError} For a malformed capability or an allow rule with an
9
+ * empty `fields` list.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const ability = buildAbility(
14
+ * ['User|read|{"department":"${user.department}"}', '!User|read|*|email'],
15
+ * { id: 'u-1', department: 'engineering' },
16
+ * );
17
+ * ```
18
+ */
19
+ export declare function buildAbility(rules: readonly (Capability | CapabilityString)[], principal?: CaslPrincipal): AppAbility;
20
+ /**
21
+ * Resolve template placeholders in a {@link Capability}'s `conditions` against
22
+ * the current {@link CaslPrincipal}.
23
+ *
24
+ * A capability's conditions, whether supplied as an object or parsed from a
25
+ * compact string, may contain placeholders like `${user.id}` or
26
+ * `${user.tenantId}` (or `{{ property }}` syntax). At runtime, before CASL can
27
+ * evaluate the rule, this function replaces each placeholder with the real
28
+ * value from the authenticated principal.
29
+ *
30
+ * Supports nested property access via dot notation (e.g. `${address.city}`).
31
+ *
32
+ * A placeholder that cannot be resolved against the principal, or that
33
+ * resolves to anything other than a string, number, boolean, `null` or an
34
+ * array of those, throws rather than collapsing to an empty string or
35
+ * injecting query operators. This fails closed: a condition
36
+ * such as `{ department: '${department}' }` can never silently become
37
+ * `{ department: '' }` and match unintended records.
38
+ *
39
+ * @throws {Error} When a placeholder references a property absent from the
40
+ * principal or resolves to an object.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * interpolateConditions({ authorId: '${user.id}' }, { id: 'u-1' })
45
+ * // → { authorId: 'u-1' }
46
+ *
47
+ * interpolateConditions({ level: '${level}' }, { id: 'u-1', level: 3 })
48
+ * // → { level: 3 }
49
+ * ```
50
+ */
51
+ export declare function interpolateConditions(conditions: Record<string, unknown>, principal: CaslPrincipal): Record<string, unknown>;
52
+ //# sourceMappingURL=ability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ability.d.ts","sourceRoot":"","sources":["../../src/helpers/ability.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAClF,OAAO,KAAK,EACV,UAAU,EAEV,UAAU,EACV,gBAAgB,EACjB,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,CAAC,UAAU,GAAG,gBAAgB,CAAC,EAAE,EACjD,SAAS,CAAC,EAAE,aAAa,GACxB,UAAU,CASZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,SAAS,EAAE,aAAa,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB"}
@@ -0,0 +1,148 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { createMongoAbility } from '@casl/ability';
3
+ import { isPlainObject, normalizeCapability } from './capability.js';
4
+ /**
5
+ * Builds the ability for one caller. Every direct rule precedes every inverted
6
+ * rule (stable within each group), so a deny from any source wins over an allow.
7
+ * Placeholders resolve against `principal`; a missing attribute throws.
8
+ *
9
+ * @throws {TypeError} For a malformed capability or an allow rule with an
10
+ * empty `fields` list.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const ability = buildAbility(
15
+ * ['User|read|{"department":"${user.department}"}', '!User|read|*|email'],
16
+ * { id: 'u-1', department: 'engineering' },
17
+ * );
18
+ * ```
19
+ */
20
+ export function buildAbility(rules, principal) {
21
+ const normalized = rules.map(normalizeCapability);
22
+ const ordered = [
23
+ ...normalized.filter((rule) => !rule.inverted),
24
+ ...normalized.filter((rule) => rule.inverted),
25
+ ];
26
+ return createMongoAbility(ordered.map((rule) => toRawRule(rule, principal)));
27
+ }
28
+ /**
29
+ * Resolve template placeholders in a {@link Capability}'s `conditions` against
30
+ * the current {@link CaslPrincipal}.
31
+ *
32
+ * A capability's conditions, whether supplied as an object or parsed from a
33
+ * compact string, may contain placeholders like `${user.id}` or
34
+ * `${user.tenantId}` (or `{{ property }}` syntax). At runtime, before CASL can
35
+ * evaluate the rule, this function replaces each placeholder with the real
36
+ * value from the authenticated principal.
37
+ *
38
+ * Supports nested property access via dot notation (e.g. `${address.city}`).
39
+ *
40
+ * A placeholder that cannot be resolved against the principal, or that
41
+ * resolves to anything other than a string, number, boolean, `null` or an
42
+ * array of those, throws rather than collapsing to an empty string or
43
+ * injecting query operators. This fails closed: a condition
44
+ * such as `{ department: '${department}' }` can never silently become
45
+ * `{ department: '' }` and match unintended records.
46
+ *
47
+ * @throws {Error} When a placeholder references a property absent from the
48
+ * principal or resolves to an object.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * interpolateConditions({ authorId: '${user.id}' }, { id: 'u-1' })
53
+ * // → { authorId: 'u-1' }
54
+ *
55
+ * interpolateConditions({ level: '${level}' }, { id: 'u-1', level: 3 })
56
+ * // → { level: 3 }
57
+ * ```
58
+ */
59
+ export function interpolateConditions(conditions, principal) {
60
+ return Object.fromEntries(Object.entries(conditions).map(([key, value]) => [
61
+ key,
62
+ interpolateValue(value, principal, key),
63
+ ]));
64
+ }
65
+ function interpolateValue(value, principal, conditionPath) {
66
+ if (typeof value === 'string') {
67
+ // If the entire string is a single placeholder, preserve the resolved
68
+ // value's original type (e.g. a numeric id stays a number).
69
+ if (/^(\$\{[^}]+\}|\{\{\s*[^}]+?\s*\}\})$/.test(value)) {
70
+ const prop = value.replace(/^\$\{|\}$|^\{\{\s*|\s*\}\}$/g, '').trim();
71
+ return resolvePlaceholder(principal, prop, conditionPath);
72
+ }
73
+ return value.replace(/\$\{([^}]+)\}|\{\{\s*([^}]+?)\s*\}\}/g, (_, p1, p2) => {
74
+ const prop = (p1 ?? p2).trim();
75
+ return String(resolvePlaceholder(principal, prop, conditionPath));
76
+ });
77
+ }
78
+ if (Array.isArray(value)) {
79
+ return value.map((item, index) => interpolateValue(item, principal, `${conditionPath}[${index}]`));
80
+ }
81
+ if (isPlainObject(value)) {
82
+ return Object.fromEntries(Object.entries(value).map(([key, nested]) => [
83
+ key,
84
+ interpolateValue(nested, principal, `${conditionPath}.${key}`),
85
+ ]));
86
+ }
87
+ return value;
88
+ }
89
+ function resolvePlaceholder(principal, path, conditionPath) {
90
+ const resolved = getNestedValue(principal, path);
91
+ if (typeof resolved === 'undefined') {
92
+ throw new Error(`Cannot interpolate capability condition "${conditionPath}": property "${path}" is missing from the principal.`);
93
+ }
94
+ // An object value would be read by CASL as query operators ({ $ne: ... }).
95
+ if (!isConditionScalar(resolved) &&
96
+ !(Array.isArray(resolved) && resolved.every(isConditionScalar))) {
97
+ throw new Error(`Cannot interpolate capability condition "${conditionPath}": property "${path}" must be a scalar or an array of scalars.`);
98
+ }
99
+ return resolved;
100
+ }
101
+ function isConditionScalar(value) {
102
+ return (value === null ||
103
+ typeof value === 'string' ||
104
+ typeof value === 'number' ||
105
+ typeof value === 'boolean');
106
+ }
107
+ function getNestedValue(obj, path) {
108
+ const normalizedPath = path.startsWith('user.') ? path.slice(5) : path;
109
+ const segments = normalizedPath.split('.').filter(Boolean);
110
+ let current = obj;
111
+ for (const seg of segments) {
112
+ if (current !== null &&
113
+ typeof current === 'object' &&
114
+ Object.getOwnPropertyDescriptor(current, seg) !== undefined) {
115
+ current = current[seg];
116
+ }
117
+ else {
118
+ return undefined;
119
+ }
120
+ }
121
+ return current;
122
+ }
123
+ function toRawRule(capability, principal) {
124
+ const rule = {
125
+ action: capability.action,
126
+ subject: capability.subject,
127
+ };
128
+ if (capability.inverted) {
129
+ rule.inverted = true;
130
+ }
131
+ if (capability.reason) {
132
+ rule.reason = capability.reason;
133
+ }
134
+ if (capability.fields?.length === 0 && !capability.inverted) {
135
+ throw new TypeError('Allow capability has an empty fields list, which would grant no fields. ' +
136
+ 'Omit fields for unrestricted access.');
137
+ }
138
+ if (capability.fields && capability.fields.length > 0) {
139
+ rule.fields = capability.fields;
140
+ }
141
+ if (capability.conditions) {
142
+ rule.conditions = principal
143
+ ? interpolateConditions(capability.conditions, principal)
144
+ : capability.conditions;
145
+ }
146
+ return rule;
147
+ }
148
+ //# sourceMappingURL=ability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ability.js","sourceRoot":"","sources":["../../src/helpers/ability.ts"],"names":[],"mappings":"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"}