@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
@@ -0,0 +1,73 @@
1
+ import { type IPipelineContext } from '@cqrs-ddd/pipeline';
2
+ import type { CaslPrincipal } from '../interfaces/permission-source.interface.js';
3
+ import type { AppAbility, Projected } from '../types/casl.types.js';
4
+ /** The ability `CaslBehavior` built for the current pipeline execution. */
5
+ export declare function getCaslAbility(context?: IPipelineContext): AppAbility | undefined;
6
+ /** The principal that ability was built for; derive security-scoped keys from it. */
7
+ export declare function getCaslPrincipal(context?: IPipelineContext): CaslPrincipal | undefined;
8
+ /**
9
+ * Entity- and field-level authorization against the request ability.
10
+ *
11
+ * Uses the constructor ability, else the ambient ability stored by
12
+ * `CaslBehavior`. A missing ability denies.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const user = await this.users.findById(command.id);
17
+ * this.authorizer.authorize('update', user, ['username']);
18
+ * user.update(command.changes);
19
+ *
20
+ * return this.authorizer.project('read', user, { id: user.id, email: user.email });
21
+ * ```
22
+ */
23
+ export declare class CaslAuthorizer {
24
+ private readonly ability?;
25
+ constructor(ability?: AppAbility | undefined);
26
+ /**
27
+ * True when `action` is permitted on `subject` (and `field`, when given).
28
+ * Field checks use CASL field matching, unlike {@link project}: a grant of
29
+ * `fields: ['profile']` does not permit `'profile.secret'`; grant
30
+ * `'profile.*'` or `'profile.**'` to cover nested fields.
31
+ */
32
+ can(action: string, subject: object | string, field?: string): boolean;
33
+ /**
34
+ * Throws `UnauthorizedActionException` unless `action` is permitted on
35
+ * `subject` and every listed field. Fields use CASL field matching, as in
36
+ * {@link can}.
37
+ */
38
+ authorize(action: string, subject: object | string, fields?: readonly string[]): void;
39
+ /**
40
+ * Asserts `action` on `subject`, then returns the `candidate` fields the
41
+ * ability permits. Conditions use `subject`, never `candidate`.
42
+ */
43
+ project<TCandidate extends object>(action: string, subject: object | string, candidate: TCandidate): Projected<TCandidate>;
44
+ /**
45
+ * Whether the decision for `action` on `subject` depends on the entity's
46
+ * attributes: true when a rule for that subject, `all` or `manage` carries
47
+ * conditions, and when no ability is present. A handler that is about to
48
+ * decide against a cached or otherwise possibly stale entity reads a fresh
49
+ * one instead.
50
+ *
51
+ * @param action - The action about to be checked, such as `'read'`.
52
+ * @param subject - The subject type, such as `'User'`.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const refresh = this.authorizer.dependsOnEntity('read', 'User');
57
+ * const user = await this.users.findById(query.id, { refresh });
58
+ * return user && this.authorizer.project('read', user, user.toJSON());
59
+ * ```
60
+ */
61
+ dependsOnEntity(action: string, subject: string): boolean;
62
+ private permitted;
63
+ }
64
+ /**
65
+ * Checks whether an ability contains conditional rules for any of the given subjects.
66
+ *
67
+ * Cache policies use this to detect that entity attributes can alter an
68
+ * authorization decision, which requires bypassing a response cache. Pass
69
+ * `action` to ignore rules that cannot affect that action; rules for `manage`
70
+ * always count. A subject list containing `'all'` matches every rule.
71
+ */
72
+ export declare function hasEntityConditions(ability: AppAbility, subjects: readonly string[], action?: string): boolean;
73
+ //# sourceMappingURL=authorizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorizer.d.ts","sourceRoot":"","sources":["../../src/helpers/authorizer.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,gBAAgB,EAAiB,MAAM,oBAAoB,CAAC;AAG1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAC;AAClF,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGpE,2EAA2E;AAC3E,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,gBAAgB,GACzB,UAAU,GAAG,SAAS,CAGxB;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAC9B,OAAO,CAAC,EAAE,gBAAgB,GACzB,aAAa,GAAG,SAAS,CAG3B;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAArC,YAA6B,OAAO,CAAC,EAAE,UAAU,YAAA,EAAI;IAErD;;;;;OAKG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAOrE;IAED;;;;OAIG;IACH,SAAS,CACP,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GACzB,IAAI,CAYN;IAED;;;OAGG;IACH,OAAO,CAAC,UAAU,SAAS,MAAM,EAC/B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GAAG,MAAM,EACxB,SAAS,EAAE,UAAU,GACpB,SAAS,CAAC,UAAU,CAAC,CAavB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAGxD;IAED,OAAO,CAAC,SAAS;CAgBlB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CA2BT"}
@@ -0,0 +1,173 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ import { subject as caslSubject } from '@casl/ability';
3
+ import { pipelineStore } from '@cqrs-ddd/pipeline';
4
+ import { CASL_ABILITY_KEY, CASL_PRINCIPAL_KEY } from '../constants/tokens.js';
5
+ import { UnauthorizedActionException } from '../errors/unauthorized-action.exception.js';
6
+ import { projectPermittedFields } from './projection.js';
7
+ /** The ability `CaslBehavior` built for the current pipeline execution. */
8
+ export function getCaslAbility(context) {
9
+ const ctx = context ?? pipelineStore.getStore();
10
+ return ctx?.items.get(CASL_ABILITY_KEY);
11
+ }
12
+ /** The principal that ability was built for; derive security-scoped keys from it. */
13
+ export function getCaslPrincipal(context) {
14
+ const ctx = context ?? pipelineStore.getStore();
15
+ return ctx?.items.get(CASL_PRINCIPAL_KEY);
16
+ }
17
+ /**
18
+ * Entity- and field-level authorization against the request ability.
19
+ *
20
+ * Uses the constructor ability, else the ambient ability stored by
21
+ * `CaslBehavior`. A missing ability denies.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * const user = await this.users.findById(command.id);
26
+ * this.authorizer.authorize('update', user, ['username']);
27
+ * user.update(command.changes);
28
+ *
29
+ * return this.authorizer.project('read', user, { id: user.id, email: user.email });
30
+ * ```
31
+ */
32
+ export class CaslAuthorizer {
33
+ ability;
34
+ constructor(ability) {
35
+ this.ability = ability;
36
+ }
37
+ /**
38
+ * True when `action` is permitted on `subject` (and `field`, when given).
39
+ * Field checks use CASL field matching, unlike {@link project}: a grant of
40
+ * `fields: ['profile']` does not permit `'profile.secret'`; grant
41
+ * `'profile.*'` or `'profile.**'` to cover nested fields.
42
+ */
43
+ can(action, subject, field) {
44
+ const ability = this.ability ?? getCaslAbility();
45
+ if (!ability)
46
+ return false;
47
+ const { typedSubject } = resolveSubject(subject);
48
+ return field
49
+ ? ability.can(action, typedSubject, field)
50
+ : ability.can(action, typedSubject);
51
+ }
52
+ /**
53
+ * Throws `UnauthorizedActionException` unless `action` is permitted on
54
+ * `subject` and every listed field. Fields use CASL field matching, as in
55
+ * {@link can}.
56
+ */
57
+ authorize(action, subject, fields) {
58
+ const { ability, subjectType, entityId, typedSubject } = this.permitted(action, subject);
59
+ for (const field of fields ?? []) {
60
+ if (!ability.can(action, typedSubject, field)) {
61
+ throw denied(action, subjectType, entityId, ` field "${field}"`, [
62
+ field,
63
+ ]);
64
+ }
65
+ }
66
+ }
67
+ /**
68
+ * Asserts `action` on `subject`, then returns the `candidate` fields the
69
+ * ability permits. Conditions use `subject`, never `candidate`.
70
+ */
71
+ project(action, subject, candidate) {
72
+ if (!candidate || typeof candidate !== 'object') {
73
+ throw new TypeError('CaslAuthorizer.project() requires a candidate object.');
74
+ }
75
+ const { ability, typedSubject } = this.permitted(action, subject);
76
+ return projectPermittedFields(ability, action, typedSubject, toSnapshot(candidate));
77
+ }
78
+ /**
79
+ * Whether the decision for `action` on `subject` depends on the entity's
80
+ * attributes: true when a rule for that subject, `all` or `manage` carries
81
+ * conditions, and when no ability is present. A handler that is about to
82
+ * decide against a cached or otherwise possibly stale entity reads a fresh
83
+ * one instead.
84
+ *
85
+ * @param action - The action about to be checked, such as `'read'`.
86
+ * @param subject - The subject type, such as `'User'`.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const refresh = this.authorizer.dependsOnEntity('read', 'User');
91
+ * const user = await this.users.findById(query.id, { refresh });
92
+ * return user && this.authorizer.project('read', user, user.toJSON());
93
+ * ```
94
+ */
95
+ dependsOnEntity(action, subject) {
96
+ const ability = this.ability ?? getCaslAbility();
97
+ return !ability || hasEntityConditions(ability, [subject], action);
98
+ }
99
+ permitted(action, subject) {
100
+ const ability = this.ability ?? getCaslAbility();
101
+ const resolved = resolveSubject(subject);
102
+ if (!ability) {
103
+ throw denied(action, resolved.subjectType, resolved.entityId, ' (no authorization ability present in context)');
104
+ }
105
+ if (!ability.can(action, resolved.typedSubject)) {
106
+ throw denied(action, resolved.subjectType, resolved.entityId, '');
107
+ }
108
+ return { ability, ...resolved };
109
+ }
110
+ }
111
+ /**
112
+ * Checks whether an ability contains conditional rules for any of the given subjects.
113
+ *
114
+ * Cache policies use this to detect that entity attributes can alter an
115
+ * authorization decision, which requires bypassing a response cache. Pass
116
+ * `action` to ignore rules that cannot affect that action; rules for `manage`
117
+ * always count. A subject list containing `'all'` matches every rule.
118
+ */
119
+ export function hasEntityConditions(ability, subjects, action) {
120
+ const wanted = new Set(subjects);
121
+ const asList = (value) => Array.isArray(value) ? value : [value];
122
+ return ability.rules.some((rule) => {
123
+ const conditions = rule.conditions;
124
+ const conditional = typeof conditions === 'function' ||
125
+ (typeof conditions === 'object' &&
126
+ conditions !== null &&
127
+ Object.keys(conditions).length > 0);
128
+ if (!conditional)
129
+ return false;
130
+ if (action !== undefined &&
131
+ !asList(rule.action).some((a) => a === action || a === 'manage')) {
132
+ return false;
133
+ }
134
+ return asList(rule.subject).some((s) => typeof s === 'string' &&
135
+ (s === 'all' || wanted.has('all') || wanted.has(s)));
136
+ });
137
+ }
138
+ function toSnapshot(value) {
139
+ return typeof value?.toJSON === 'function'
140
+ ? value.toJSON()
141
+ : value;
142
+ }
143
+ function denied(action, subjectType, entityId, detail, fields) {
144
+ return new UnauthorizedActionException({
145
+ action,
146
+ subject: subjectType,
147
+ entityId,
148
+ ...(fields ? { fields } : {}),
149
+ reason: `Access denied: insufficient permissions to ${action} ${subjectType}${detail}.`,
150
+ });
151
+ }
152
+ function resolveSubject(subject) {
153
+ if (typeof subject === 'string') {
154
+ return { subjectType: subject, typedSubject: subject };
155
+ }
156
+ // A value tagged by CASL's `subject()` is a plain object whose constructor
157
+ // is `Object`; its tag is the only reliable subject type.
158
+ const tagged = subject
159
+ .__caslSubjectType__;
160
+ const subjectType = typeof tagged === 'string' && tagged.length > 0
161
+ ? tagged
162
+ : subject.constructor?.name && subject.constructor.name !== 'Object'
163
+ ? subject.constructor.name
164
+ : 'Object';
165
+ const record = toSnapshot(subject);
166
+ const entityId = subject.id ??
167
+ record?.id;
168
+ const typedSubject = caslSubject(subjectType, {
169
+ ...record,
170
+ });
171
+ return { subjectType, entityId, typedSubject };
172
+ }
173
+ //# sourceMappingURL=authorizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorizer.js","sourceRoot":"","sources":["../../src/helpers/authorizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import type { Capability, CapabilityString } from '../types/casl.types.js';
2
+ /**
3
+ * Expand a compact {@link CapabilityString} (e.g. from a JWT or cookie) back
4
+ * into a structured {@link Capability}.
5
+ *
6
+ * This is a **runtime-only deserialisation** step — the inverse of
7
+ * {@link serializeCapability}.
8
+ *
9
+ * Format: `[!]subject|action[|conditions[|fields[|reason]]]`
10
+ *
11
+ * - Prefix with `!` to create an inverted (deny) rule
12
+ * - Use CASL's `all` for any subject and `manage` for any action
13
+ * - `*` as conditions (or omitted) means no conditions
14
+ * - `*` as fields (or omitted) means all fields
15
+ * - Conditions remain readable JSON unless they contain `|`; those values are
16
+ * `~`-prefixed base64url JSON so segment parsing stays reversible
17
+ * - Safe fields are comma-separated: `title,body,status`
18
+ * - Delimiter-bearing subject/action/fields/reason values are `~`-prefixed
19
+ * base64url JSON so every valid capability can round-trip
20
+ *
21
+ * @throws {Error} When the subject segment is empty or missing.
22
+ * @throws {Error} When the action segment is empty or missing.
23
+ * @throws {Error} When the conditions segment is not a JSON object.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * parseCapabilityString('Post|read|*')
28
+ * // → { subject: 'Post', action: 'read' }
29
+ *
30
+ * parseCapabilityString('Post|manage|*')
31
+ * // → { subject: 'Post', action: 'manage' }
32
+ *
33
+ * parseCapabilityString('!Post|delete|*')
34
+ * // → { subject: 'Post', action: 'delete', inverted: true }
35
+ *
36
+ * parseCapabilityString('Post|update|{"authorId":"${user.id}"}|title,body')
37
+ * // → { subject: 'Post', action: 'update', conditions: { authorId: '${user.id}' }, fields: ['title', 'body'] }
38
+ *
39
+ * parseCapabilityString('Post|read|*|title,body,status')
40
+ * // → { subject: 'Post', action: 'read', fields: ['title', 'body', 'status'] }
41
+ * ```
42
+ */
43
+ export declare function parseCapabilityString(cap: CapabilityString): Capability;
44
+ /**
45
+ * Normalize a capability — whether it arrives as a {@link Capability} object
46
+ * or as a compact {@link CapabilityString} (for example from a JWT or cookie) —
47
+ * into a structured {@link Capability}.
48
+ */
49
+ export declare function normalizeCapability(cap: Capability | CapabilityString): Capability;
50
+ /**
51
+ * Collapse a {@link Capability} into a compact {@link CapabilityString}
52
+ * suitable for JWT claims, encrypted cookies, or session stores where payload
53
+ * size matters.
54
+ *
55
+ * This is a **runtime-only serialisation** step — the inverse of
56
+ * {@link parseCapabilityString}.
57
+ *
58
+ * The fields segment is appended when the capability restricts specific
59
+ * fields, or as `*` when a reason segment follows it; otherwise it is omitted,
60
+ * keeping the string compact for the common case.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * serializeCapability({ subject: 'Post', action: 'manage' })
65
+ * // → 'Post|manage|*'
66
+ *
67
+ * serializeCapability({ subject: 'all', action: 'manage' })
68
+ * // → 'all|manage|*'
69
+ *
70
+ * serializeCapability({ subject: 'Post', action: 'update', fields: ['title', 'body'] })
71
+ * // → 'Post|update|*|title,body'
72
+ *
73
+ * serializeCapability({ subject: 'Post', action: 'update', conditions: { authorId: 42 }, fields: ['title'] })
74
+ * // → 'Post|update|{"authorId":42}|title'
75
+ * ```
76
+ */
77
+ export declare function serializeCapability(cap: Capability): CapabilityString;
78
+ /** True for a non-null object whose prototype is `Object.prototype` or `null`. */
79
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
80
+ //# sourceMappingURL=capability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../../src/helpers/capability.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,gBAAgB,GAAG,UAAU,CA2DvE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,UAAU,GAAG,gBAAgB,GACjC,UAAU,CA4BZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,UAAU,GAAG,gBAAgB,CA8BrE;AAiID,kFAAkF;AAClF,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMlC"}
@@ -0,0 +1,287 @@
1
+ /* Copyright (C) 2026-present Aristotelis — see repository license. */
2
+ /**
3
+ * Expand a compact {@link CapabilityString} (e.g. from a JWT or cookie) back
4
+ * into a structured {@link Capability}.
5
+ *
6
+ * This is a **runtime-only deserialisation** step — the inverse of
7
+ * {@link serializeCapability}.
8
+ *
9
+ * Format: `[!]subject|action[|conditions[|fields[|reason]]]`
10
+ *
11
+ * - Prefix with `!` to create an inverted (deny) rule
12
+ * - Use CASL's `all` for any subject and `manage` for any action
13
+ * - `*` as conditions (or omitted) means no conditions
14
+ * - `*` as fields (or omitted) means all fields
15
+ * - Conditions remain readable JSON unless they contain `|`; those values are
16
+ * `~`-prefixed base64url JSON so segment parsing stays reversible
17
+ * - Safe fields are comma-separated: `title,body,status`
18
+ * - Delimiter-bearing subject/action/fields/reason values are `~`-prefixed
19
+ * base64url JSON so every valid capability can round-trip
20
+ *
21
+ * @throws {Error} When the subject segment is empty or missing.
22
+ * @throws {Error} When the action segment is empty or missing.
23
+ * @throws {Error} When the conditions segment is not a JSON object.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * parseCapabilityString('Post|read|*')
28
+ * // → { subject: 'Post', action: 'read' }
29
+ *
30
+ * parseCapabilityString('Post|manage|*')
31
+ * // → { subject: 'Post', action: 'manage' }
32
+ *
33
+ * parseCapabilityString('!Post|delete|*')
34
+ * // → { subject: 'Post', action: 'delete', inverted: true }
35
+ *
36
+ * parseCapabilityString('Post|update|{"authorId":"${user.id}"}|title,body')
37
+ * // → { subject: 'Post', action: 'update', conditions: { authorId: '${user.id}' }, fields: ['title', 'body'] }
38
+ *
39
+ * parseCapabilityString('Post|read|*|title,body,status')
40
+ * // → { subject: 'Post', action: 'read', fields: ['title', 'body', 'status'] }
41
+ * ```
42
+ */
43
+ export function parseCapabilityString(cap) {
44
+ let str = cap.trim();
45
+ let inverted = false;
46
+ if (str.startsWith('!')) {
47
+ inverted = true;
48
+ str = str.slice(1);
49
+ }
50
+ const parts = str.split('|');
51
+ if (parts.length > 5) {
52
+ throw new Error(`Invalid capability string: too many segments in "${cap}"`);
53
+ }
54
+ const subject = decodeTextSegment(parts[0], cap, 'subject');
55
+ const action = decodeTextSegment(parts[1], cap, 'action');
56
+ if (!subject) {
57
+ throw new Error(`Invalid capability string: missing subject in "${cap}"`);
58
+ }
59
+ if (!action) {
60
+ throw new Error(`Invalid capability string: missing action in "${cap}"`);
61
+ }
62
+ const rawConditions = parts[2];
63
+ const rawFields = parts[3];
64
+ const rawReason = parts[4];
65
+ let conditions;
66
+ if (rawConditions && rawConditions !== '*') {
67
+ let parsed;
68
+ try {
69
+ const json = rawConditions.startsWith('~')
70
+ ? Buffer.from(rawConditions.slice(1), 'base64url').toString('utf8')
71
+ : rawConditions;
72
+ parsed = JSON.parse(json);
73
+ }
74
+ catch {
75
+ throw new Error(`Invalid conditions JSON in capability string "${cap}": ${rawConditions}`);
76
+ }
77
+ if (!isPlainObject(parsed)) {
78
+ throw new TypeError(`Capability conditions must be a JSON object in "${cap}".`);
79
+ }
80
+ conditions = parsed;
81
+ }
82
+ const result = { subject, action };
83
+ if (inverted)
84
+ result.inverted = true;
85
+ if (conditions)
86
+ result.conditions = conditions;
87
+ if (rawFields && rawFields !== '*') {
88
+ result.fields = decodeFieldsSegment(rawFields, cap);
89
+ }
90
+ if (rawReason !== undefined && rawReason !== '*') {
91
+ result.reason = decodeTextSegment(rawReason, cap, 'reason');
92
+ }
93
+ return result;
94
+ }
95
+ /**
96
+ * Normalize a capability — whether it arrives as a {@link Capability} object
97
+ * or as a compact {@link CapabilityString} (for example from a JWT or cookie) —
98
+ * into a structured {@link Capability}.
99
+ */
100
+ export function normalizeCapability(cap) {
101
+ if (typeof cap === 'string')
102
+ return parseCapabilityString(cap);
103
+ if (!isPlainObject(cap)) {
104
+ throw new TypeError('Capability must be an object or compact string.');
105
+ }
106
+ if (typeof cap.subject !== 'string' || cap.subject.length === 0) {
107
+ throw new TypeError('Capability subject must be a non-empty string.');
108
+ }
109
+ if (typeof cap.action !== 'string' || cap.action.length === 0) {
110
+ throw new TypeError('Capability action must be a non-empty string.');
111
+ }
112
+ if (cap.conditions !== undefined && !isPlainObject(cap.conditions)) {
113
+ throw new TypeError('Capability conditions must be a JSON object.');
114
+ }
115
+ if (cap.fields !== undefined &&
116
+ (!Array.isArray(cap.fields) ||
117
+ cap.fields.some((field) => typeof field !== 'string'))) {
118
+ throw new TypeError('Capability fields must be an array of strings.');
119
+ }
120
+ if (cap.inverted !== undefined && typeof cap.inverted !== 'boolean') {
121
+ throw new TypeError('Capability inverted must be a boolean.');
122
+ }
123
+ if (cap.reason !== undefined && typeof cap.reason !== 'string') {
124
+ throw new TypeError('Capability reason must be a string.');
125
+ }
126
+ return cap;
127
+ }
128
+ /**
129
+ * Collapse a {@link Capability} into a compact {@link CapabilityString}
130
+ * suitable for JWT claims, encrypted cookies, or session stores where payload
131
+ * size matters.
132
+ *
133
+ * This is a **runtime-only serialisation** step — the inverse of
134
+ * {@link parseCapabilityString}.
135
+ *
136
+ * The fields segment is appended when the capability restricts specific
137
+ * fields, or as `*` when a reason segment follows it; otherwise it is omitted,
138
+ * keeping the string compact for the common case.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * serializeCapability({ subject: 'Post', action: 'manage' })
143
+ * // → 'Post|manage|*'
144
+ *
145
+ * serializeCapability({ subject: 'all', action: 'manage' })
146
+ * // → 'all|manage|*'
147
+ *
148
+ * serializeCapability({ subject: 'Post', action: 'update', fields: ['title', 'body'] })
149
+ * // → 'Post|update|*|title,body'
150
+ *
151
+ * serializeCapability({ subject: 'Post', action: 'update', conditions: { authorId: 42 }, fields: ['title'] })
152
+ * // → 'Post|update|{"authorId":42}|title'
153
+ * ```
154
+ */
155
+ export function serializeCapability(cap) {
156
+ const normalized = normalizeCapability(cap);
157
+ const prefix = normalized.inverted ? '!' : '';
158
+ const subject = encodeTextSegment(normalized.subject, true);
159
+ const action = encodeTextSegment(normalized.action);
160
+ let conditionsJson;
161
+ if (normalized.conditions !== undefined) {
162
+ try {
163
+ conditionsJson = JSON.stringify(copyJsonConditions(normalized.conditions));
164
+ }
165
+ catch {
166
+ throw new TypeError('Capability conditions must be a JSON object.');
167
+ }
168
+ }
169
+ const conditions = conditionsJson?.includes('|') === true
170
+ ? `~${Buffer.from(conditionsJson, 'utf8').toString('base64url')}`
171
+ : (conditionsJson ?? '*');
172
+ const fields = encodeFieldsSegment(normalized.fields);
173
+ const reason = normalized.reason !== undefined
174
+ ? encodeTextSegment(normalized.reason, false, true)
175
+ : undefined;
176
+ const base = `${prefix}${subject}|${action}|${conditions}`;
177
+ if (reason !== undefined)
178
+ return `${base}|${fields ?? '*'}|${reason}`;
179
+ return fields !== undefined ? `${base}|${fields}` : base;
180
+ }
181
+ /** Copy only lossless JSON data, without invoking getters or toJSON hooks. */
182
+ function copyJsonConditions(value, ancestors = new WeakSet()) {
183
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
184
+ return value;
185
+ if (typeof value === 'number' && Number.isFinite(value))
186
+ return value;
187
+ if (typeof value !== 'object' ||
188
+ value === null ||
189
+ (!Array.isArray(value) && !isPlainObject(value)) ||
190
+ ancestors.has(value)) {
191
+ throw new TypeError('Unsupported condition value.');
192
+ }
193
+ ancestors.add(value);
194
+ try {
195
+ const descriptors = Object.getOwnPropertyDescriptors(value);
196
+ const keys = Reflect.ownKeys(descriptors);
197
+ if (Array.isArray(value)) {
198
+ // Arrays must have exactly their length property and contiguous indices.
199
+ if (keys.length !== value.length + 1)
200
+ throw new TypeError('Lossy condition array.');
201
+ const result = [];
202
+ for (let index = 0; index < value.length; index += 1) {
203
+ const descriptor = descriptors[String(index)];
204
+ if (!descriptor?.enumerable || !('value' in descriptor)) {
205
+ throw new TypeError('Condition arrays must contain only JSON data.');
206
+ }
207
+ result.push(copyJsonConditions(descriptor.value, ancestors));
208
+ }
209
+ return result;
210
+ }
211
+ const result = Object.create(null);
212
+ for (const key of keys) {
213
+ if (typeof key !== 'string')
214
+ throw new TypeError('Symbol condition keys are unsupported.');
215
+ const descriptor = descriptors[key];
216
+ if (!descriptor.enumerable || !('value' in descriptor)) {
217
+ throw new TypeError('Conditions must contain only enumerable JSON data.');
218
+ }
219
+ result[key] = copyJsonConditions(descriptor.value, ancestors);
220
+ }
221
+ return result;
222
+ }
223
+ finally {
224
+ ancestors.delete(value);
225
+ }
226
+ }
227
+ function encodeTextSegment(value, escapeBang = false, escapeStar = false) {
228
+ const needsEncoding = value.startsWith('~') ||
229
+ value.includes('|') ||
230
+ value.trim() !== value ||
231
+ (escapeBang && value.startsWith('!')) ||
232
+ (escapeStar && value === '*');
233
+ return needsEncoding
234
+ ? `~${Buffer.from(JSON.stringify(value), 'utf8').toString('base64url')}`
235
+ : value;
236
+ }
237
+ function decodeTextSegment(value, capability, label) {
238
+ if (!value?.startsWith('~'))
239
+ return value ?? '';
240
+ try {
241
+ const decoded = JSON.parse(Buffer.from(value.slice(1), 'base64url').toString('utf8'));
242
+ if (typeof decoded !== 'string')
243
+ throw new TypeError();
244
+ return decoded;
245
+ }
246
+ catch {
247
+ throw new Error(`Invalid encoded ${label} in capability string "${capability}".`);
248
+ }
249
+ }
250
+ function encodeFieldsSegment(fields) {
251
+ if (fields === undefined)
252
+ return undefined;
253
+ const safe = fields.length > 0 &&
254
+ fields.every((field) => field.length > 0 &&
255
+ field !== '*' &&
256
+ !field.startsWith('~') &&
257
+ !field.includes(',') &&
258
+ !field.includes('|') &&
259
+ field.trim() === field);
260
+ return safe
261
+ ? fields.join(',')
262
+ : `~${Buffer.from(JSON.stringify(fields), 'utf8').toString('base64url')}`;
263
+ }
264
+ function decodeFieldsSegment(value, capability) {
265
+ if (!value.startsWith('~'))
266
+ return value.split(',');
267
+ try {
268
+ const decoded = JSON.parse(Buffer.from(value.slice(1), 'base64url').toString('utf8'));
269
+ if (!Array.isArray(decoded) ||
270
+ decoded.some((field) => typeof field !== 'string')) {
271
+ throw new TypeError();
272
+ }
273
+ return decoded;
274
+ }
275
+ catch {
276
+ throw new Error(`Invalid encoded fields in capability string "${capability}".`);
277
+ }
278
+ }
279
+ /** True for a non-null object whose prototype is `Object.prototype` or `null`. */
280
+ export function isPlainObject(value) {
281
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
282
+ return false;
283
+ }
284
+ const prototype = Object.getPrototypeOf(value);
285
+ return prototype === Object.prototype || prototype === null;
286
+ }
287
+ //# sourceMappingURL=capability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability.js","sourceRoot":"","sources":["../../src/helpers/capability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { AppAbility } from '../types/casl.types.js';
2
+ /**
3
+ * Projects a snapshot to the fields `ability` permits for `action`. Conditions
4
+ * are evaluated against the full subject. A granted parent path authorizes its
5
+ * descendants unless a descendant is explicitly denied; this differs from
6
+ * `ability.can(action, subject, 'parent.child')`, which needs
7
+ * `parent.*`/`parent.**`. Denied array elements become `null`. Cyclic input
8
+ * and more than 1024 path aliases throw.
9
+ */
10
+ export declare function projectPermittedFields(ability: AppAbility, action: string, typedSubject: string, record: Record<string, unknown> | unknown[]): Record<string, unknown> | unknown[];
11
+ //# sourceMappingURL=projection.d.ts.map