@authup/access 1.0.0-beta.28 → 1.0.0-beta.30

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 (104) hide show
  1. package/dist/index.mjs +693 -499
  2. package/dist/index.mjs.map +1 -1
  3. package/dist/permission/checker/index.d.ts +3 -0
  4. package/dist/permission/checker/index.d.ts.map +1 -0
  5. package/dist/permission/{module.d.ts → checker/module.d.ts} +8 -7
  6. package/dist/permission/checker/module.d.ts.map +1 -0
  7. package/dist/permission/checker/types.d.ts +26 -0
  8. package/dist/permission/checker/types.d.ts.map +1 -0
  9. package/dist/permission/error/index.d.ts +0 -1
  10. package/dist/permission/error/index.d.ts.map +1 -1
  11. package/dist/permission/error/module.d.ts +4 -5
  12. package/dist/permission/error/module.d.ts.map +1 -1
  13. package/dist/permission/index.d.ts +2 -2
  14. package/dist/permission/index.d.ts.map +1 -1
  15. package/dist/permission/repository/index.d.ts.map +1 -0
  16. package/dist/permission/repository/memory.d.ts +9 -0
  17. package/dist/permission/repository/memory.d.ts.map +1 -0
  18. package/dist/permission/{provider → repository}/types.d.ts +2 -2
  19. package/dist/permission/repository/types.d.ts.map +1 -0
  20. package/dist/permission/types.d.ts +1 -19
  21. package/dist/permission/types.d.ts.map +1 -1
  22. package/dist/policy/built-in/attribute-names/evaluator.d.ts +5 -8
  23. package/dist/policy/built-in/attribute-names/evaluator.d.ts.map +1 -1
  24. package/dist/policy/built-in/attributes/evaluator.d.ts +4 -8
  25. package/dist/policy/built-in/attributes/evaluator.d.ts.map +1 -1
  26. package/dist/policy/built-in/attributes/validator.d.ts.map +1 -1
  27. package/dist/policy/built-in/composite/evaluator.d.ts +3 -8
  28. package/dist/policy/built-in/composite/evaluator.d.ts.map +1 -1
  29. package/dist/policy/built-in/composite/validator.d.ts.map +1 -1
  30. package/dist/policy/built-in/date/evaluator.d.ts +3 -8
  31. package/dist/policy/built-in/date/evaluator.d.ts.map +1 -1
  32. package/dist/policy/built-in/identity/data/index.d.ts +2 -0
  33. package/dist/policy/built-in/identity/data/index.d.ts.map +1 -0
  34. package/dist/policy/built-in/identity/data/validator.d.ts +6 -0
  35. package/dist/policy/built-in/identity/data/validator.d.ts.map +1 -0
  36. package/dist/policy/built-in/identity/evaluator.d.ts +7 -8
  37. package/dist/policy/built-in/identity/evaluator.d.ts.map +1 -1
  38. package/dist/policy/built-in/identity/types.d.ts +22 -0
  39. package/dist/policy/built-in/identity/types.d.ts.map +1 -1
  40. package/dist/policy/built-in/permission-binding/module.d.ts +3 -8
  41. package/dist/policy/built-in/permission-binding/module.d.ts.map +1 -1
  42. package/dist/policy/built-in/realm-match/evaluator.d.ts +7 -8
  43. package/dist/policy/built-in/realm-match/evaluator.d.ts.map +1 -1
  44. package/dist/policy/built-in/time/evaluator.d.ts +3 -8
  45. package/dist/policy/built-in/time/evaluator.d.ts.map +1 -1
  46. package/dist/policy/constants.d.ts +12 -0
  47. package/dist/policy/constants.d.ts.map +1 -0
  48. package/dist/policy/data.d.ts +20 -0
  49. package/dist/policy/data.d.ts.map +1 -0
  50. package/dist/policy/engine/module.d.ts +9 -9
  51. package/dist/policy/engine/module.d.ts.map +1 -1
  52. package/dist/policy/engine/types.d.ts +6 -3
  53. package/dist/policy/engine/types.d.ts.map +1 -1
  54. package/dist/policy/error/module.d.ts +3 -0
  55. package/dist/policy/error/module.d.ts.map +1 -1
  56. package/dist/policy/evaluation/context/define.d.ts +3 -0
  57. package/dist/policy/evaluation/context/define.d.ts.map +1 -0
  58. package/dist/policy/evaluation/context/index.d.ts +3 -0
  59. package/dist/policy/evaluation/context/index.d.ts.map +1 -0
  60. package/dist/policy/evaluation/context/types.d.ts +11 -0
  61. package/dist/policy/evaluation/context/types.d.ts.map +1 -0
  62. package/dist/policy/evaluation/index.d.ts +3 -0
  63. package/dist/policy/evaluation/index.d.ts.map +1 -0
  64. package/dist/policy/evaluation/types.d.ts +14 -0
  65. package/dist/policy/evaluation/types.d.ts.map +1 -0
  66. package/dist/policy/index.d.ts +4 -1
  67. package/dist/policy/index.d.ts.map +1 -1
  68. package/dist/policy/issue/constants.d.ts +21 -0
  69. package/dist/policy/issue/constants.d.ts.map +1 -0
  70. package/dist/policy/issue/define.d.ts +4 -0
  71. package/dist/policy/issue/define.d.ts.map +1 -0
  72. package/dist/policy/issue/index.d.ts +4 -0
  73. package/dist/policy/issue/index.d.ts.map +1 -0
  74. package/dist/policy/issue/types.d.ts +5 -0
  75. package/dist/policy/issue/types.d.ts.map +1 -0
  76. package/dist/policy/types.d.ts +3 -41
  77. package/dist/policy/types.d.ts.map +1 -1
  78. package/package.json +17 -13
  79. package/dist/permission/error/types.d.ts +0 -11
  80. package/dist/permission/error/types.d.ts.map +0 -1
  81. package/dist/permission/module.d.ts.map +0 -1
  82. package/dist/permission/provider/index.d.ts.map +0 -1
  83. package/dist/permission/provider/memory.d.ts +0 -9
  84. package/dist/permission/provider/memory.d.ts.map +0 -1
  85. package/dist/permission/provider/types.d.ts.map +0 -1
  86. package/dist/policy/evaluator/data/identity/index.d.ts +0 -2
  87. package/dist/policy/evaluator/data/identity/index.d.ts.map +0 -1
  88. package/dist/policy/evaluator/data/identity/validator.d.ts +0 -6
  89. package/dist/policy/evaluator/data/identity/validator.d.ts.map +0 -1
  90. package/dist/policy/evaluator/data/index.d.ts +0 -4
  91. package/dist/policy/evaluator/data/index.d.ts.map +0 -1
  92. package/dist/policy/evaluator/data/permission/index.d.ts +0 -2
  93. package/dist/policy/evaluator/data/permission/index.d.ts.map +0 -1
  94. package/dist/policy/evaluator/data/permission/validator.d.ts +0 -6
  95. package/dist/policy/evaluator/data/permission/validator.d.ts.map +0 -1
  96. package/dist/policy/evaluator/data/validator.d.ts +0 -6
  97. package/dist/policy/evaluator/data/validator.d.ts.map +0 -1
  98. package/dist/policy/evaluator/evaluate.d.ts +0 -4
  99. package/dist/policy/evaluator/evaluate.d.ts.map +0 -1
  100. package/dist/policy/evaluator/index.d.ts +0 -4
  101. package/dist/policy/evaluator/index.d.ts.map +0 -1
  102. package/dist/policy/evaluator/types.d.ts +0 -44
  103. package/dist/policy/evaluator/types.d.ts.map +0 -1
  104. /package/dist/permission/{provider → repository}/index.d.ts +0 -0
package/dist/index.mjs CHANGED
@@ -1,10 +1,119 @@
1
1
  import { AuthupError, ErrorCode } from '@authup/errors';
2
+ import { defineIssueItem, defineIssueGroup, Container } from 'validup';
2
3
  import { isObject, flattenObject, hasOwnProperty } from '@authup/kit';
3
4
  import { createValidator } from '@validup/adapter-zod';
4
- import { Container } from 'validup';
5
5
  import { z } from 'zod';
6
6
  import { guard } from '@ucast/mongo2js';
7
7
 
8
+ class PermissionError extends AuthupError {
9
+ constructor(options = {}){
10
+ super({
11
+ ...options,
12
+ message: options.message || 'A permission error occurred.',
13
+ statusCode: options.statusCode || 403
14
+ });
15
+ }
16
+ addIssue(data) {
17
+ this.issues.push(data);
18
+ }
19
+ addIssues(data) {
20
+ this.issues.push(...data);
21
+ }
22
+ static notFound(name) {
23
+ return new PermissionError({
24
+ message: `The permission ${name} was not found.`,
25
+ code: ErrorCode.PERMISSION_NOT_FOUND
26
+ });
27
+ }
28
+ static denied(name) {
29
+ return new PermissionError({
30
+ message: `The permission ${name} has not been granted.`,
31
+ code: ErrorCode.PERMISSION_DENIED
32
+ });
33
+ }
34
+ static deniedAll(names) {
35
+ return new PermissionError({
36
+ message: `None of the permissions ${names.join(', ')} has been granted.`,
37
+ code: ErrorCode.PERMISSION_DENIED
38
+ });
39
+ }
40
+ static evaluationFailed(name) {
41
+ if (Array.isArray(name)) {
42
+ return new PermissionError({
43
+ message: `The evaluation of permissions ${name.join(', ')} failed`,
44
+ code: ErrorCode.PERMISSION_EVALUATION_FAILED
45
+ });
46
+ }
47
+ return new PermissionError({
48
+ message: `The evaluation of permission ${name} failed`,
49
+ code: ErrorCode.PERMISSION_EVALUATION_FAILED
50
+ });
51
+ }
52
+ }
53
+
54
+ /*
55
+ * Copyright (c) 2025.
56
+ * Author Peter Placzek (tada5hi)
57
+ * For the full copyright and license information,
58
+ * view the LICENSE file that was distributed with this source code.
59
+ */ function buildPermissionItemKey(input) {
60
+ return `${input.realmId || '_'}/${input.clientId || '_'}/${input.name}`;
61
+ }
62
+
63
+ /*
64
+ * Copyright (c) 2024-2024.
65
+ * Author Peter Placzek (tada5hi)
66
+ * For the full copyright and license information,
67
+ * view the LICENSE file that was distributed with this source code.
68
+ */ function isPermissionItemEqual(a, b) {
69
+ if (a.name !== b.name) {
70
+ return false;
71
+ }
72
+ if (typeof a.realmId === 'string' || typeof b.realmId === 'string') {
73
+ return a.realmId === b.realmId;
74
+ }
75
+ if (typeof a.clientId === 'string' || typeof b.clientId === 'string') {
76
+ return a.clientId === b.clientId;
77
+ }
78
+ return !!a.realmId === !!b.realmId && !!a.clientId === !!b.clientId;
79
+ }
80
+
81
+ /*
82
+ * Copyright (c) 2024-2024.
83
+ * Author Peter Placzek (tada5hi)
84
+ * For the full copyright and license information,
85
+ * view the LICENSE file that was distributed with this source code.
86
+ */ var DecisionStrategy = /*#__PURE__*/ function(DecisionStrategy) {
87
+ /**
88
+ * One or more positive
89
+ */ DecisionStrategy["AFFIRMATIVE"] = "affirmative";
90
+ /**
91
+ * All positive
92
+ */ DecisionStrategy["UNANIMOUS"] = "unanimous";
93
+ /**
94
+ * More positive than negative
95
+ */ DecisionStrategy["CONSENSUS"] = "consensus";
96
+ return DecisionStrategy;
97
+ }({});
98
+
99
+ /*
100
+ * Copyright (c) 2024-2024.
101
+ * Author Peter Placzek (tada5hi)
102
+ * For the full copyright and license information,
103
+ * view the LICENSE file that was distributed with this source code.
104
+ */ function maybeInvertPolicyOutcome(outcome, invert) {
105
+ if (typeof invert === 'undefined') {
106
+ return outcome;
107
+ }
108
+ if (typeof invert === 'function') {
109
+ return invert(outcome);
110
+ }
111
+ if (!invert) {
112
+ return outcome;
113
+ }
114
+ return !outcome;
115
+ }
116
+
8
117
  class PolicyError extends AuthupError {
9
118
  constructor(message, code){
10
119
  super({
@@ -12,6 +121,12 @@ class PolicyError extends AuthupError {
12
121
  code
13
122
  });
14
123
  }
124
+ addIssue(data) {
125
+ this.issues.push(data);
126
+ }
127
+ addIssues(data) {
128
+ this.issues.push(...data);
129
+ }
15
130
  static evaluatorNotFound(type) {
16
131
  return new PolicyError(`No evaluator is registered to handle the policy: ${type}`, ErrorCode.POLICY_EVALUATOR_NOT_FOUND);
17
132
  }
@@ -23,22 +138,200 @@ class PolicyError extends AuthupError {
23
138
  }
24
139
  }
25
140
 
141
+ /**
142
+ * The policy engine is a component that interprets defined policies and makes decisions
143
+ * on whether to allow or deny a particular access.
144
+ */ class PolicyEngine {
145
+ evaluators;
146
+ constructor(evaluators = {}){
147
+ this.evaluators = evaluators;
148
+ }
149
+ registerEvaluator(type, evaluator) {
150
+ this.evaluators[type] = evaluator;
151
+ }
152
+ registerEvaluators(evaluators) {
153
+ const keys = Object.keys(evaluators);
154
+ for(let i = 0; i < keys.length; i++){
155
+ this.registerEvaluator(keys[i], evaluators[keys[i]]);
156
+ }
157
+ }
158
+ /**
159
+ * @param policy
160
+ * @param ctx
161
+ */ async evaluate(policy, ctx) {
162
+ if (ctx.exclude && ctx.exclude.length > 0 && ctx.exclude.indexOf(policy.type) !== -1) {
163
+ return {
164
+ success: maybeInvertPolicyOutcome(true, policy.invert)
165
+ };
166
+ }
167
+ if (ctx.include && ctx.include.length > 0 && ctx.include.indexOf(policy.type) === -1) {
168
+ return {
169
+ success: maybeInvertPolicyOutcome(true, policy.invert)
170
+ };
171
+ }
172
+ const issues = [];
173
+ const evaluator = this.evaluators[policy.type];
174
+ if (!evaluator) {
175
+ // todo: add issue here instead + return false ?
176
+ issues.push(defineIssueItem({
177
+ path: [
178
+ policy.type
179
+ ],
180
+ message: `The policy ${policy.type} can not be handled by any evaluator.`,
181
+ code: ErrorCode.POLICY_EVALUATOR_NOT_FOUND
182
+ }));
183
+ return {
184
+ success: maybeInvertPolicyOutcome(false, policy.invert),
185
+ issues
186
+ };
187
+ }
188
+ try {
189
+ return await evaluator.evaluate(policy, {
190
+ ...ctx,
191
+ evaluators: {
192
+ ...this.evaluators,
193
+ ...ctx.evaluators || {}
194
+ }
195
+ });
196
+ } catch (e) {
197
+ issues.push(defineIssueItem({
198
+ path: [
199
+ policy.type
200
+ ],
201
+ message: `The ${policy.type} evaluator can not process the policy specification.`,
202
+ code: ErrorCode.POLICY_EVALUATOR_NOT_PROCESSABLE
203
+ }));
204
+ return {
205
+ success: maybeInvertPolicyOutcome(false, policy.invert),
206
+ issues
207
+ };
208
+ }
209
+ }
210
+ async evaluateOrFail(policy, ctx) {
211
+ const issues = [];
212
+ try {
213
+ const outcome = await this.evaluate(policy, ctx);
214
+ if (outcome.success) {
215
+ return;
216
+ }
217
+ if (outcome.issues) {
218
+ issues.push(...outcome.issues);
219
+ }
220
+ } catch (e) {
221
+ if (e instanceof PolicyError) {
222
+ throw e;
223
+ }
224
+ }
225
+ const error = new PolicyError(`The policy ${policy.type} evaluation failed.`);
226
+ error.addIssues(issues);
227
+ throw error;
228
+ }
229
+ }
230
+
26
231
  /*
27
- * Copyright (c) 2024-2024.
232
+ * Copyright (c) 2026.
28
233
  * Author Peter Placzek (tada5hi)
29
234
  * For the full copyright and license information,
30
235
  * view the LICENSE file that was distributed with this source code.
31
- */ function maybeInvertPolicyOutcome(outcome, invert) {
32
- if (typeof invert === 'undefined') {
33
- return outcome;
236
+ */ class PolicyData {
237
+ data;
238
+ validated;
239
+ constructor(data = {}, validated = new Set()){
240
+ this.data = data;
241
+ this.validated = validated;
242
+ }
243
+ set(key, value) {
244
+ this.data[key] = value;
245
+ this.validated.delete(key);
246
+ }
247
+ get(key) {
248
+ if (key in this.data) {
249
+ return this.data[key];
250
+ }
251
+ throw new Error(`Policy data item ${key} does not exist.`);
34
252
  }
35
- if (typeof invert === 'function') {
36
- return invert(outcome);
253
+ has(key) {
254
+ return key in this.data;
37
255
  }
38
- if (!invert) {
39
- return outcome;
256
+ setValidated(key) {
257
+ this.validated.add(key);
258
+ }
259
+ isValidated(key) {
260
+ return this.validated.has(key);
261
+ }
262
+ clone() {
263
+ return new PolicyData({
264
+ ...this.data
265
+ }, new Set(this.validated));
40
266
  }
41
- return !outcome;
267
+ }
268
+
269
+ function definePolicyEvaluationContext(input = {}) {
270
+ return {
271
+ evaluators: input.evaluators || {},
272
+ path: input.path || [],
273
+ data: input.data || new PolicyData(),
274
+ include: input.include || [],
275
+ exclude: input.exclude || []
276
+ };
277
+ }
278
+
279
+ /*
280
+ * Copyright (c) 2026-2026.
281
+ * Author Peter Placzek (tada5hi)
282
+ * For the full copyright and license information,
283
+ * view the LICENSE file that was distributed with this source code.
284
+ */ var PolicyIssueSeverity = /*#__PURE__*/ function(PolicyIssueSeverity) {
285
+ PolicyIssueSeverity["WARNING"] = "warning";
286
+ PolicyIssueSeverity["ERROR"] = "error";
287
+ return PolicyIssueSeverity;
288
+ }({});
289
+ var PolicyIssueCode = /*#__PURE__*/ function(PolicyIssueCode) {
290
+ // Data/Input
291
+ PolicyIssueCode["DATA_MISSING"] = "DATA_MISSING";
292
+ PolicyIssueCode["DATA_INVALID"] = "DATA_INVALID";
293
+ // Policy Spec / Config
294
+ PolicyIssueCode["INVALID"] = "INVALID";
295
+ PolicyIssueCode["TYPE_UNKNOWN"] = "TYPE_UNKNOWN";
296
+ PolicyIssueCode["FIELD_MISSING"] = "FIELD_MISSING";
297
+ PolicyIssueCode["FIELD_INVALID"] = "FIELD_INVALID";
298
+ // Composite / Tree evaluation
299
+ PolicyIssueCode["CHILD_INVALID"] = "CHILD_INVALID";
300
+ PolicyIssueCode["CHILD_EVALUATION_FAILED"] = "CHILD_EVALUATION_FAILED";
301
+ // Engine / Evaluator registry
302
+ PolicyIssueCode["EVALUATION_DENIED"] = "EVALUATION_DENIED";
303
+ PolicyIssueCode["EVALUATOR_NOT_FOUND"] = "EVALUATOR_NOT_FOUND";
304
+ PolicyIssueCode["EVALUATOR_ERROR"] = "EVALUATOR_ERROR";
305
+ // Execution control
306
+ PolicyIssueCode["SKIPPED_EXCLUDED"] = "SKIPPED_EXCLUDED";
307
+ PolicyIssueCode["SKIPPED_NOT_INCLUDED"] = "SKIPPED_NOT_INCLUDED";
308
+ // Internal / unexpected
309
+ PolicyIssueCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
310
+ return PolicyIssueCode;
311
+ }({});
312
+
313
+ function definePolicyIssueItem(input) {
314
+ return defineIssueItem(input);
315
+ }
316
+ function definePolicyIssueGroup(input) {
317
+ return defineIssueGroup(input);
318
+ }
319
+
320
+ class AttributeNamesPolicyValidator extends Container {
321
+ initialize() {
322
+ super.initialize();
323
+ this.mount('names', createValidator(z.array(z.string().min(3).max(128))));
324
+ this.mount('invert', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
325
+ }
326
+ }
327
+
328
+ /*
329
+ * Copyright (c) 2025.
330
+ * Author Peter Placzek (tada5hi)
331
+ * For the full copyright and license information,
332
+ * view the LICENSE file that was distributed with this source code.
333
+ */ function defineAttributesPolicy(data) {
334
+ return data;
42
335
  }
43
336
 
44
337
  /*
@@ -74,60 +367,10 @@ class PolicyError extends AuthupError {
74
367
  return BuiltInPolicyType;
75
368
  }({});
76
369
 
77
- class AttributeNamesPolicyValidator extends Container {
78
- initialize() {
79
- super.initialize();
80
- this.mount('names', createValidator(z.array(z.string().min(3).max(128))));
81
- this.mount('invert', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
82
- }
83
- }
84
-
85
- class AttributeNamesPolicyEvaluator {
86
- validator;
87
- constructor(){
88
- this.validator = new AttributeNamesPolicyValidator();
89
- }
90
- async can(ctx) {
91
- return ctx.config.type === BuiltInPolicyType.ATTRIBUTE_NAMES;
92
- }
93
- async validateConfig(ctx) {
94
- return this.validator.run(ctx.config);
95
- }
96
- async validateInput(ctx) {
97
- if (!isObject(ctx.input.attributes)) {
98
- throw PolicyError.evaluatorContextInvalid();
99
- }
100
- return ctx.input;
101
- }
102
- async evaluate(ctx) {
103
- if (!ctx.input.attributes) {
104
- throw PolicyError.evaluatorContextInvalid();
105
- }
106
- const attributes = flattenObject(ctx.input.attributes);
107
- const keys = Object.keys(attributes);
108
- for(let i = 0; i < keys.length; i++){
109
- const index = ctx.config.names.indexOf(keys[i]);
110
- if (index === -1) {
111
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
112
- }
113
- }
114
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
115
- }
116
- }
117
-
118
- /*
119
- * Copyright (c) 2025.
120
- * Author Peter Placzek (tada5hi)
121
- * For the full copyright and license information,
122
- * view the LICENSE file that was distributed with this source code.
123
- */ function defineAttributesPolicy(data) {
124
- return data;
125
- }
126
-
127
370
  class AttributesPolicyValidator extends Container {
128
371
  initialize() {
129
372
  super.initialize();
130
- this.mount('query', createValidator(z.object({}).passthrough()));
373
+ this.mount('query', createValidator(z.looseObject({})));
131
374
  this.mount('invert', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
132
375
  }
133
376
  }
@@ -137,25 +380,40 @@ class AttributesPolicyEvaluator {
137
380
  constructor(){
138
381
  this.validator = new AttributesPolicyValidator();
139
382
  }
140
- async can(ctx) {
141
- return ctx.config.type === BuiltInPolicyType.ATTRIBUTES;
142
- }
143
- async validateConfig(ctx) {
144
- return this.validator.run(ctx.config);
145
- }
146
- async validateInput(ctx) {
147
- if (!isObject(ctx.input.attributes)) {
148
- throw PolicyError.evaluatorContextInvalid();
383
+ async accessData(ctx) {
384
+ if (!ctx.data.has(BuiltInPolicyType.ATTRIBUTES)) {
385
+ return null;
149
386
  }
150
- return ctx.input;
151
- }
152
- async evaluate(ctx) {
153
- if (!ctx.input.attributes) {
154
- throw PolicyError.evaluatorContextInvalid();
387
+ if (ctx.data.isValidated(BuiltInPolicyType.ATTRIBUTES)) {
388
+ return ctx.data.get(BuiltInPolicyType.ATTRIBUTES);
389
+ }
390
+ // todo: run validator on attributes (isObject ...)
391
+ const data = ctx.data.get(BuiltInPolicyType.ATTRIBUTES);
392
+ ctx.data.set(BuiltInPolicyType.ATTRIBUTES, data);
393
+ ctx.data.setValidated(BuiltInPolicyType.ATTRIBUTES);
394
+ return data;
395
+ }
396
+ async evaluate(value, ctx) {
397
+ // todo: catch errors + transform to issue(s)
398
+ const policy = await this.validator.run(value);
399
+ const data = await this.accessData(ctx);
400
+ if (!data) {
401
+ return {
402
+ success: false,
403
+ issues: [
404
+ definePolicyIssueItem({
405
+ code: PolicyIssueCode.DATA_MISSING,
406
+ message: 'The data property attributes is missing',
407
+ path: ctx.path
408
+ })
409
+ ]
410
+ };
155
411
  }
156
- this.fixQuery(ctx.config.query);
157
- const testIt = guard(ctx.config.query);
158
- return maybeInvertPolicyOutcome(testIt(ctx.input.attributes), ctx.config.invert);
412
+ this.fixQuery(policy.query);
413
+ const testIt = guard(policy.query);
414
+ return {
415
+ success: maybeInvertPolicyOutcome(testIt(data), policy.invert)
416
+ };
159
417
  }
160
418
  fixQuery(query) {
161
419
  if (Array.isArray(query)) {
@@ -184,122 +442,59 @@ class AttributesPolicyEvaluator {
184
442
  }
185
443
  }
186
444
 
187
- /*
188
- * Copyright (c) 2024-2024.
189
- * Author Peter Placzek (tada5hi)
190
- * For the full copyright and license information,
191
- * view the LICENSE file that was distributed with this source code.
192
- */ var DecisionStrategy = /*#__PURE__*/ function(DecisionStrategy) {
193
- /**
194
- * One or more positive
195
- */ DecisionStrategy["AFFIRMATIVE"] = "affirmative";
196
- /**
197
- * All positive
198
- */ DecisionStrategy["UNANIMOUS"] = "unanimous";
199
- /**
200
- * More positive than negative
201
- */ DecisionStrategy["CONSENSUS"] = "consensus";
202
- return DecisionStrategy;
203
- }({});
204
-
205
- class PolicyDataIdentityValidator extends Container {
206
- initialize() {
207
- super.initialize();
208
- this.mount('type', createValidator(z.string().min(3).max(128)));
209
- this.mount('id', createValidator(z.string().uuid()));
210
- this.mount('clientId', createValidator(z.string().uuid().or(z.null()).or(z.undefined()).optional()));
211
- this.mount('realmId', createValidator(z.string().uuid().or(z.null()).or(z.undefined()).optional()));
212
- this.mount('realmName', createValidator(z.string().min(3).max(128).or(z.null()).or(z.undefined()).optional()));
213
- }
214
- }
215
-
216
- class PolicyDataPermissionValidator extends Container {
217
- initialize() {
218
- super.initialize();
219
- this.mount('name', createValidator(z.string().min(3).max(256)));
220
- this.mount('clientId', createValidator(z.string().uuid().or(z.null()).or(z.undefined()).optional()));
221
- this.mount('realmId', createValidator(z.string().uuid().or(z.null()).or(z.undefined()).optional()));
222
- this.mount('policy', createValidator(z.object({
223
- type: z.string()
224
- }).optional()));
225
- }
226
- }
227
-
228
- class PolicyDataValidator extends Container {
229
- initialize() {
230
- super.initialize();
231
- // todo: optionalValue should be null
232
- const identity = new PolicyDataIdentityValidator();
233
- this.mount('identity', {
234
- optional: true,
235
- optionalValue: 'undefined',
236
- optionalInclude: true
237
- }, identity);
238
- // todo: optionalValue should be null
239
- const permission = new PolicyDataPermissionValidator();
240
- this.mount('permission', {
241
- optional: true,
242
- optionalValue: 'undefined',
243
- optionalInclude: true
244
- }, permission);
245
- this.mount('attributes', createValidator(z.object({}).optional()));
246
- this.mount('dateTime', createValidator(z.date().or(z.string()).or(z.number()).optional()));
247
- }
248
- }
249
-
250
- async function evaluatePolicy(ctx) {
251
- if (ctx.options.exclude && ctx.options.exclude.length > 0 && ctx.options.exclude.indexOf(ctx.config.type) !== -1) {
252
- if (typeof ctx.config.invert === 'boolean') {
253
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
254
- }
255
- return true;
256
- }
257
- if (ctx.options.include && ctx.options.include.length > 0 && ctx.options.include.indexOf(ctx.config.type) === -1) {
258
- if (typeof ctx.config.invert === 'boolean') {
259
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
260
- }
261
- return true;
262
- }
263
- const evaluator = ctx.evaluators[ctx.config.type];
264
- if (!evaluator) {
265
- throw PolicyError.evaluatorNotFound(ctx.config.type);
266
- }
267
- try {
268
- const canEvaluate = await evaluator.can(ctx);
269
- if (canEvaluate) {
270
- const config = await evaluator.validateConfig(ctx);
271
- const input = await evaluator.validateInput(ctx);
272
- return await evaluator.evaluate({
273
- ...ctx,
274
- input,
275
- config
276
- });
277
- }
278
- } catch (e) {
279
- if (e instanceof PolicyError) {
280
- throw e;
445
+ class AttributeNamesPolicyEvaluator {
446
+ validator;
447
+ attributesEvaluator;
448
+ constructor(){
449
+ this.validator = new AttributeNamesPolicyValidator();
450
+ this.attributesEvaluator = new AttributesPolicyEvaluator();
451
+ }
452
+ async evaluate(value, ctx) {
453
+ // todo: catch errors + transform to issue(s)
454
+ const policy = await this.validator.run(value);
455
+ const data = await this.attributesEvaluator.accessData(ctx);
456
+ if (!data) {
457
+ return {
458
+ success: false,
459
+ issues: [
460
+ definePolicyIssueItem({
461
+ code: PolicyIssueCode.DATA_MISSING,
462
+ message: 'The data property attributes is missing',
463
+ path: ctx.path
464
+ })
465
+ ]
466
+ };
281
467
  }
282
- if (e instanceof Error) {
283
- const error = new PolicyError(e.message);
284
- error.stack = e.stack;
285
- throw error;
468
+ const attributes = flattenObject(data);
469
+ const keys = Object.keys(attributes);
470
+ const issues = [];
471
+ for(let i = 0; i < keys.length; i++){
472
+ const index = policy.names.indexOf(keys[i]);
473
+ if (index === -1) {
474
+ issues.push(definePolicyIssueItem({
475
+ code: PolicyIssueCode.EVALUATION_DENIED,
476
+ message: `The attribute ${keys[i]} is not included`,
477
+ path: [
478
+ ...ctx.path,
479
+ keys[i]
480
+ ]
481
+ }));
482
+ }
286
483
  }
287
- throw new PolicyError();
484
+ return {
485
+ success: maybeInvertPolicyOutcome(issues.length === 0, policy.invert),
486
+ issues
487
+ };
288
488
  }
289
- throw PolicyError.evaluatorNotProcessable();
290
- }
291
-
292
- function isCompositePolicy(input) {
293
- return input.type === BuiltInPolicyType.COMPOSITE;
294
489
  }
295
490
 
296
491
  class CompositePolicyValidator extends Container {
297
492
  initialize() {
298
493
  super.initialize();
299
- this.mount('decisionStrategy', createValidator(z.nativeEnum(DecisionStrategy).or(z.null()).or(z.undefined()).optional()));
300
- this.mount('children', createValidator(z.array(z.object({
494
+ this.mount('decisionStrategy', createValidator(z.enum(DecisionStrategy).or(z.null()).or(z.undefined()).optional()));
495
+ this.mount('children', createValidator(z.array(z.looseObject({
301
496
  type: z.string().min(3)
302
- }).passthrough())));
497
+ }))));
303
498
  this.mount('invert', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
304
499
  }
305
500
  }
@@ -309,53 +504,81 @@ class CompositePolicyEvaluator {
309
504
  constructor(){
310
505
  this.validator = new CompositePolicyValidator();
311
506
  }
312
- async can(ctx) {
313
- return isCompositePolicy(ctx.config);
314
- }
315
- async validateConfig(ctx) {
316
- return this.validator.run(ctx.config);
317
- }
318
- async validateInput(ctx) {
319
- return ctx.input;
320
- }
321
- async evaluate(ctx) {
507
+ async evaluate(value, ctx) {
508
+ // todo: catch errors + transform to issue(s)
509
+ const policy = await this.validator.run(value);
322
510
  let count = 0;
323
- const decisionStrategy = ctx.config.decisionStrategy ?? DecisionStrategy.UNANIMOUS;
324
- for(let i = 0; i < ctx.config.children.length; i++){
325
- const childPolicy = ctx.config.children[i];
326
- let outcome;
327
- if (isCompositePolicy(childPolicy)) {
328
- outcome = await this.evaluate({
329
- ...ctx,
330
- config: childPolicy
331
- });
332
- } else {
333
- outcome = await evaluatePolicy({
334
- ...ctx,
335
- config: childPolicy
336
- });
337
- }
338
- if (outcome) {
511
+ const decisionStrategy = policy.decisionStrategy ?? DecisionStrategy.UNANIMOUS;
512
+ const engine = new PolicyEngine(ctx.evaluators);
513
+ const issues = [];
514
+ for(let i = 0; i < policy.children.length; i++){
515
+ const childPolicy = policy.children[i];
516
+ const path = [
517
+ ...ctx.path || [],
518
+ childPolicy.type
519
+ ];
520
+ const outcome = await engine.evaluate(childPolicy, {
521
+ ...ctx,
522
+ path
523
+ });
524
+ if (outcome.success) {
339
525
  if (decisionStrategy === DecisionStrategy.AFFIRMATIVE) {
340
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
526
+ return {
527
+ success: maybeInvertPolicyOutcome(true, policy.invert),
528
+ issues: []
529
+ };
341
530
  }
342
531
  count++;
343
532
  } else {
533
+ if (outcome.issues) {
534
+ issues.push(defineIssueGroup({
535
+ message: `The evaluation of child policy ${childPolicy.type} failed`,
536
+ issues: outcome.issues || [],
537
+ path
538
+ }));
539
+ }
344
540
  if (decisionStrategy === DecisionStrategy.UNANIMOUS) {
345
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
541
+ return {
542
+ success: maybeInvertPolicyOutcome(false, policy.invert),
543
+ issues: [
544
+ defineIssueGroup({
545
+ message: `The evaluation of composite policy failed (${DecisionStrategy.UNANIMOUS})`,
546
+ issues,
547
+ path: ctx.path
548
+ })
549
+ ]
550
+ };
346
551
  }
347
552
  count--;
348
553
  }
349
554
  }
350
- return maybeInvertPolicyOutcome(count > 0, ctx.config.invert);
555
+ if (count > 0) {
556
+ return {
557
+ success: maybeInvertPolicyOutcome(true, policy.invert)
558
+ };
559
+ }
560
+ return {
561
+ success: maybeInvertPolicyOutcome(false, policy.invert),
562
+ issues: [
563
+ defineIssueGroup({
564
+ message: `The evaluation of composite policy failed (${DecisionStrategy.CONSENSUS})`,
565
+ issues: issues || [],
566
+ path: ctx.path
567
+ })
568
+ ]
569
+ };
351
570
  }
352
571
  }
353
572
 
573
+ function isCompositePolicy(input) {
574
+ return input.type === BuiltInPolicyType.COMPOSITE;
575
+ }
576
+
354
577
  class DatePolicyValidator extends Container {
355
578
  initialize() {
356
579
  super.initialize();
357
- this.mount('start', createValidator(z.date().or(z.string().date()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
358
- this.mount('end', createValidator(z.date().or(z.string().date()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
580
+ this.mount('start', createValidator(z.date().or(z.iso.date()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
581
+ this.mount('end', createValidator(z.date().or(z.iso.date()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
359
582
  this.mount('invert', createValidator(z.boolean().or(z.undefined()).or(z.null()).optional()));
360
583
  }
361
584
  }
@@ -377,35 +600,52 @@ class DatePolicyEvaluator {
377
600
  constructor(){
378
601
  this.validator = new DatePolicyValidator();
379
602
  }
380
- async can(ctx) {
381
- return ctx.config.type === BuiltInPolicyType.DATE;
382
- }
383
- async validateConfig(ctx) {
384
- return this.validator.run(ctx.config);
385
- }
386
- async validateInput(ctx) {
387
- return ctx.input;
388
- }
389
- async evaluate(ctx) {
603
+ async evaluate(value, ctx) {
604
+ // todo: catch errors + transform to issue(s)
605
+ const policy = await this.validator.run(value);
390
606
  let now;
391
- if (ctx.input.dateTime) {
392
- now = normalizeDate$1(toDate$1(ctx.input.dateTime));
607
+ if (ctx.data.has(BuiltInPolicyType.DATE)) {
608
+ if (ctx.data.isValidated(BuiltInPolicyType.DATE)) {
609
+ now = ctx.data.get(BuiltInPolicyType.DATE);
610
+ } else {
611
+ // todo: run validator on attributes (isObject ...)
612
+ now = normalizeDate$1(toDate$1(ctx.data.get(BuiltInPolicyType.DATE)));
613
+ ctx.data.set(BuiltInPolicyType.DATE, now);
614
+ ctx.data.setValidated(BuiltInPolicyType.DATE);
615
+ }
393
616
  } else {
394
617
  now = normalizeDate$1(new Date());
395
618
  }
396
- if (ctx.config.start) {
397
- const start = normalizeDate$1(toDate$1(ctx.config.start));
619
+ if (policy.start) {
620
+ const start = normalizeDate$1(toDate$1(policy.start));
398
621
  if (now < start) {
399
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
622
+ return {
623
+ success: maybeInvertPolicyOutcome(false, policy.invert)
624
+ };
400
625
  }
401
626
  }
402
- if (ctx.config.end) {
403
- const end = normalizeDate$1(toDate$1(ctx.config.end));
627
+ if (policy.end) {
628
+ const end = normalizeDate$1(toDate$1(policy.end));
404
629
  if (now > end) {
405
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
630
+ return {
631
+ success: maybeInvertPolicyOutcome(false, policy.invert)
632
+ };
406
633
  }
407
634
  }
408
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
635
+ return {
636
+ success: maybeInvertPolicyOutcome(true, policy.invert)
637
+ };
638
+ }
639
+ }
640
+
641
+ class PolicyIdentityDataValidator extends Container {
642
+ initialize() {
643
+ super.initialize();
644
+ this.mount('type', createValidator(z.string().min(3).max(128)));
645
+ this.mount('id', createValidator(z.uuid()));
646
+ this.mount('clientId', createValidator(z.uuid().or(z.null()).or(z.undefined()).optional()));
647
+ this.mount('realmId', createValidator(z.uuid().or(z.null()).or(z.undefined()).optional()));
648
+ this.mount('realmName', createValidator(z.string().min(3).max(128).or(z.null()).or(z.undefined()).optional()));
409
649
  }
410
650
  }
411
651
 
@@ -418,28 +658,60 @@ class IdentityPolicyValidator extends Container {
418
658
 
419
659
  class IdentityPolicyEvaluator {
420
660
  validator;
661
+ dataValidator;
421
662
  constructor(){
422
663
  this.validator = new IdentityPolicyValidator();
664
+ this.dataValidator = new PolicyIdentityDataValidator();
423
665
  }
424
- async can(ctx) {
425
- return ctx.config.type === BuiltInPolicyType.IDENTITY;
426
- }
427
- async validateConfig(ctx) {
428
- return this.validator.run(ctx.config);
429
- }
430
- async validateInput(ctx) {
431
- return ctx.input;
432
- }
433
- async evaluate(ctx) {
434
- if (!isObject(ctx.input.identity)) {
435
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
666
+ async accessData(ctx) {
667
+ if (!ctx.data.has('identity')) {
668
+ return null;
436
669
  }
437
- const types = ctx.config.types || [];
438
- if (types.length === 0) {
439
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
670
+ if (ctx.data.isValidated('identity')) {
671
+ return ctx.data.get('identity');
440
672
  }
441
- const typeAllowed = types.indexOf(ctx.input.identity.type) !== -1;
442
- return maybeInvertPolicyOutcome(typeAllowed, ctx.config.invert);
673
+ const data = await this.dataValidator.run(ctx.data.get('identity'));
674
+ ctx.data.set('identity', data);
675
+ ctx.data.setValidated('identity');
676
+ return data;
677
+ }
678
+ async evaluate(value, ctx) {
679
+ // todo: catch errors + transform to issue(s)
680
+ const policy = await this.validator.run(value);
681
+ const data = await this.accessData(ctx);
682
+ if (!data) {
683
+ return {
684
+ success: false,
685
+ issues: [
686
+ definePolicyIssueItem({
687
+ code: PolicyIssueCode.DATA_MISSING,
688
+ message: 'The data property identity is missing',
689
+ path: ctx.path
690
+ })
691
+ ]
692
+ };
693
+ }
694
+ if (!policy.types || policy.types.length === 0) {
695
+ return {
696
+ success: maybeInvertPolicyOutcome(true, policy.invert)
697
+ };
698
+ }
699
+ const typeAllowed = policy.types.indexOf(data.type) !== -1;
700
+ if (typeAllowed) {
701
+ return {
702
+ success: maybeInvertPolicyOutcome(typeAllowed, policy.invert)
703
+ };
704
+ }
705
+ return {
706
+ success: maybeInvertPolicyOutcome(typeAllowed, policy.invert),
707
+ issues: [
708
+ definePolicyIssueItem({
709
+ code: PolicyIssueCode.EVALUATION_DENIED,
710
+ message: `The type ${data.type} is not covered by the policy configuration`,
711
+ path: ctx.path
712
+ })
713
+ ]
714
+ };
443
715
  }
444
716
  }
445
717
 
@@ -452,28 +724,19 @@ class PermissionBindingPolicyEvaluator {
452
724
  constructor(){
453
725
  this.validator = new PermissionBindingPolicyValidator();
454
726
  }
455
- async can(ctx) {
456
- return ctx.config.type === BuiltInPolicyType.PERMISSION_BINDING;
457
- }
458
- async validateConfig(ctx) {
459
- return this.validator.run(ctx.config);
460
- }
461
- async validateInput(ctx) {
462
- if (!isObject(ctx.input.identity) && !isObject(ctx.input.permission)) {
463
- throw PolicyError.evaluatorContextInvalid();
464
- }
465
- return ctx.input;
466
- }
467
- async evaluate(ctx) {
727
+ async evaluate(value) {
728
+ const policy = await this.validator.run(value);
468
729
  // todo: this must be changed when the permission-checker not only checks owned permissions.
469
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
730
+ return {
731
+ success: maybeInvertPolicyOutcome(true, policy.invert)
732
+ };
470
733
  }
471
734
  }
472
735
 
473
736
  class RealmMatchPolicyValidator extends Container {
474
737
  initialize() {
475
738
  super.initialize();
476
- this.mount('decisionStrategy', createValidator(z.nativeEnum(DecisionStrategy).or(z.null()).or(z.undefined()).optional()));
739
+ this.mount('decisionStrategy', createValidator(z.enum(DecisionStrategy).or(z.null()).or(z.undefined()).optional()));
477
740
  this.mount('attributeName', createValidator(z.string().min(3).or(z.array(z.string().min(3))).or(z.null()).or(z.undefined()).optional()));
478
741
  this.mount('attributeNameStrict', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
479
742
  this.mount('attributeNullMatchAll', createValidator(z.boolean().or(z.null()).or(z.undefined()).optional()));
@@ -484,36 +747,55 @@ class RealmMatchPolicyValidator extends Container {
484
747
 
485
748
  class RealmMatchPolicyEvaluator {
486
749
  validator;
750
+ identityEvaluator;
751
+ attributesEvaluator;
487
752
  constructor(){
488
753
  this.validator = new RealmMatchPolicyValidator();
489
- }
490
- async can(ctx) {
491
- return ctx.config.type === BuiltInPolicyType.REALM_MATCH;
492
- }
493
- async validateConfig(ctx) {
494
- return this.validator.run(ctx.config);
495
- }
496
- async validateInput(ctx) {
497
- if (!isObject(ctx.input.attributes) || !isObject(ctx.input.identity)) {
498
- throw PolicyError.evaluatorContextInvalid();
754
+ this.identityEvaluator = new IdentityPolicyEvaluator();
755
+ this.attributesEvaluator = new AttributesPolicyEvaluator();
756
+ }
757
+ async evaluate(value, ctx) {
758
+ // todo: catch errors + transform to issue(s)
759
+ const policy = await this.validator.run(value);
760
+ const identity = await this.identityEvaluator.accessData(ctx);
761
+ if (!identity) {
762
+ return {
763
+ success: false,
764
+ issues: [
765
+ definePolicyIssueItem({
766
+ code: PolicyIssueCode.DATA_MISSING,
767
+ message: 'The data property identity is missing',
768
+ path: ctx.path
769
+ })
770
+ ]
771
+ };
499
772
  }
500
- return ctx.input;
501
- }
502
- async evaluate(ctx) {
503
- if (!ctx.input.attributes || !ctx.input.identity) {
504
- throw PolicyError.evaluatorContextInvalid();
773
+ const attributes = await this.attributesEvaluator.accessData(ctx);
774
+ if (!attributes) {
775
+ return {
776
+ success: false,
777
+ issues: [
778
+ definePolicyIssueItem({
779
+ code: PolicyIssueCode.DATA_MISSING,
780
+ message: 'The data property attributes is missing',
781
+ path: ctx.path
782
+ })
783
+ ]
784
+ };
505
785
  }
506
- const identityMasterMatchAll = ctx.config.identityMasterMatchAll ?? true;
507
- if (identityMasterMatchAll && ctx.input.identity.realmName && ctx.input.identity.realmName === 'master') {
508
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
786
+ const identityMasterMatchAll = policy.identityMasterMatchAll ?? true;
787
+ if (identityMasterMatchAll && identity.realmName && identity.realmName === 'master') {
788
+ return {
789
+ success: maybeInvertPolicyOutcome(true, policy.invert)
790
+ };
509
791
  }
510
792
  let keys;
511
- if (ctx.config.attributeName) {
512
- if (Array.isArray(ctx.config.attributeName)) {
513
- keys = ctx.config.attributeName;
793
+ if (policy.attributeName) {
794
+ if (Array.isArray(policy.attributeName)) {
795
+ keys = policy.attributeName;
514
796
  } else {
515
797
  keys = [
516
- ctx.config.attributeName
798
+ policy.attributeName
517
799
  ];
518
800
  }
519
801
  } else {
@@ -523,11 +805,11 @@ class RealmMatchPolicyEvaluator {
523
805
  'realmId',
524
806
  'realmName'
525
807
  ];
526
- ctx.config.decisionStrategy = DecisionStrategy.CONSENSUS;
808
+ policy.decisionStrategy = DecisionStrategy.CONSENSUS;
527
809
  }
528
- const attributeNameStrict = ctx.config.attributeNameStrict ?? true;
810
+ const attributeNameStrict = policy.attributeNameStrict ?? true;
529
811
  if (!attributeNameStrict) {
530
- const resourceKeys = Object.keys(ctx.input.attributes);
812
+ const resourceKeys = Object.keys(attributes);
531
813
  const keysToAdd = [];
532
814
  for(let i = 0; i < resourceKeys.length; i++){
533
815
  let contains = false;
@@ -547,29 +829,35 @@ class RealmMatchPolicyEvaluator {
547
829
  }
548
830
  let count = 0;
549
831
  for(let i = 0; i < keys.length; i++){
550
- if (!hasOwnProperty(ctx.input.attributes, keys[i])) {
832
+ if (!hasOwnProperty(attributes, keys[i])) {
551
833
  continue;
552
834
  }
553
835
  let outcome = false;
554
- const attributeValue = ctx.input.attributes[keys[i]];
555
- if (attributeValue === null && ctx.config.attributeNullMatchAll) {
836
+ const attributeValue = attributes[keys[i]];
837
+ if (attributeValue === null && policy.attributeNullMatchAll) {
556
838
  outcome = true;
557
- } else if (attributeValue === ctx.input.identity.realmId || attributeValue === ctx.input.identity.realmName) {
839
+ } else if (attributeValue === identity.realmId || attributeValue === identity.realmName) {
558
840
  outcome = true;
559
841
  }
560
842
  if (outcome) {
561
- if (ctx.config.decisionStrategy === DecisionStrategy.AFFIRMATIVE) {
562
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
843
+ if (policy.decisionStrategy === DecisionStrategy.AFFIRMATIVE) {
844
+ return {
845
+ success: maybeInvertPolicyOutcome(true, policy.invert)
846
+ };
563
847
  }
564
848
  count++;
565
849
  } else {
566
- if (ctx.config.decisionStrategy === DecisionStrategy.UNANIMOUS) {
567
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
850
+ if (policy.decisionStrategy === DecisionStrategy.UNANIMOUS) {
851
+ return {
852
+ success: maybeInvertPolicyOutcome(false, policy.invert)
853
+ };
568
854
  }
569
855
  count--;
570
856
  }
571
857
  }
572
- return maybeInvertPolicyOutcome(count > 0, ctx.config.invert);
858
+ return {
859
+ success: maybeInvertPolicyOutcome(count > 0, policy.invert)
860
+ };
573
861
  }
574
862
  }
575
863
 
@@ -602,9 +890,9 @@ class TimePolicyValidator extends Container {
602
890
  this.mount('dayOfWeek', createValidator(z.number().min(0).max(6).or(z.null()).or(z.undefined()).optional()));
603
891
  this.mount('dayOfMonth', createValidator(z.number().min(1).max(31).or(z.null()).or(z.undefined()).optional()));
604
892
  this.mount('dayOfYear', createValidator(z.number().min(1).max(365).or(z.null()).or(z.undefined()).optional()));
605
- this.mount('start', createValidator(z.date().or(z.string().datetime()).or(z.string().time()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
606
- this.mount('end', createValidator(z.date().or(z.string().datetime()).or(z.string().time()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
607
- this.mount('interval', createValidator(z.nativeEnum(TimePolicyInterval).or(z.null()).or(z.undefined()).optional()));
893
+ this.mount('start', createValidator(z.date().or(z.iso.datetime()).or(z.iso.time()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
894
+ this.mount('end', createValidator(z.date().or(z.iso.datetime()).or(z.iso.time()).or(z.number()).or(z.null()).or(z.undefined()).optional()));
895
+ this.mount('interval', createValidator(z.enum(TimePolicyInterval).or(z.null()).or(z.undefined()).optional()));
608
896
  this.mount('invert', createValidator(z.boolean().or(z.undefined()).or(z.null()).optional()));
609
897
  }
610
898
  }
@@ -635,56 +923,68 @@ class TimePolicyEvaluator {
635
923
  constructor(){
636
924
  this.validator = new TimePolicyValidator();
637
925
  }
638
- async can(ctx) {
639
- return ctx.config.type === BuiltInPolicyType.TIME;
640
- }
641
- async validateConfig(ctx) {
642
- return this.validator.run(ctx.config);
643
- }
644
- async validateInput(ctx) {
645
- return ctx.input;
646
- }
647
- async evaluate(ctx) {
926
+ async evaluate(value, ctx) {
927
+ // todo: catch errors + transform to issue(s)
928
+ const policy = await this.validator.run(value);
648
929
  let now;
649
- if (ctx.input.dateTime) {
650
- now = toDate(ctx.input.dateTime);
930
+ if (ctx.data.has(BuiltInPolicyType.TIME)) {
931
+ if (ctx.data.isValidated(BuiltInPolicyType.TIME)) {
932
+ now = ctx.data.get(BuiltInPolicyType.TIME);
933
+ } else {
934
+ // todo: run validator on attributes (isObject ...)
935
+ now = toDate(ctx.data.get(BuiltInPolicyType.TIME));
936
+ ctx.data.set(BuiltInPolicyType.TIME, now);
937
+ ctx.data.setValidated(BuiltInPolicyType.TIME);
938
+ }
651
939
  } else {
652
940
  now = new Date();
653
941
  }
654
- if (ctx.config.start) {
655
- const start = normalizeDate(toDate(ctx.config.start, now), now);
942
+ if (policy.start) {
943
+ const start = normalizeDate(toDate(policy.start, now), now);
656
944
  if (now < start) {
657
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
945
+ return {
946
+ success: maybeInvertPolicyOutcome(false, policy.invert)
947
+ };
658
948
  }
659
949
  }
660
- if (ctx.config.end) {
661
- const end = normalizeDate(toDate(ctx.config.end, now), now);
950
+ if (policy.end) {
951
+ const end = normalizeDate(toDate(policy.end, now), now);
662
952
  if (now > end) {
663
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
953
+ return {
954
+ success: maybeInvertPolicyOutcome(false, policy.invert)
955
+ };
664
956
  }
665
957
  }
666
- if (ctx.config.interval) {
667
- if (isIntervalForDayOfWeek(ctx.config.interval) && ctx.config.dayOfWeek) {
668
- if (now.getDay() !== ctx.config.dayOfWeek) {
669
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
958
+ if (policy.interval) {
959
+ if (isIntervalForDayOfWeek(policy.interval) && policy.dayOfWeek) {
960
+ if (now.getDay() !== policy.dayOfWeek) {
961
+ return {
962
+ success: maybeInvertPolicyOutcome(false, policy.invert)
963
+ };
670
964
  }
671
965
  }
672
- if (isIntervalForDayOfMonth(ctx.config.interval) && ctx.config.dayOfMonth) {
673
- if (now.getDate() !== ctx.config.dayOfMonth) {
674
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
966
+ if (isIntervalForDayOfMonth(policy.interval) && policy.dayOfMonth) {
967
+ if (now.getDate() !== policy.dayOfMonth) {
968
+ return {
969
+ success: maybeInvertPolicyOutcome(false, policy.invert)
970
+ };
675
971
  }
676
972
  }
677
- if (isIntervalForDayOfYear(ctx.config.interval) && ctx.config.dayOfYear) {
973
+ if (isIntervalForDayOfYear(policy.interval) && policy.dayOfYear) {
678
974
  const start = new Date(now.getFullYear(), 0, 0);
679
975
  const diff = now.getTime() - start.getTime() + (start.getTimezoneOffset() - now.getTimezoneOffset()) * 60 * 1000;
680
976
  const oneDay = 1000 * 60 * 60 * 24;
681
977
  const dayOfYear = Math.floor(diff / oneDay);
682
- if (dayOfYear !== ctx.config.dayOfYear) {
683
- return maybeInvertPolicyOutcome(false, ctx.config.invert);
978
+ if (dayOfYear !== policy.dayOfYear) {
979
+ return {
980
+ success: maybeInvertPolicyOutcome(false, policy.invert)
981
+ };
684
982
  }
685
983
  }
686
984
  }
687
- return maybeInvertPolicyOutcome(true, ctx.config.invert);
985
+ return {
986
+ success: maybeInvertPolicyOutcome(true, policy.invert)
987
+ };
688
988
  }
689
989
  }
690
990
 
@@ -700,120 +1000,16 @@ class TimePolicyEvaluator {
700
1000
  };
701
1001
  }
702
1002
 
703
- /**
704
- * The policy engine is a component that interprets defined policies and makes decisions
705
- * on whether to allow or deny a particular access.
706
- */ class PolicyEngine {
707
- evaluators;
708
- constructor(evaluators = {}){
709
- this.evaluators = {};
710
- this.registerDefaultEvaluators();
711
- this.registerEvaluators(evaluators);
712
- }
713
- registerEvaluator(type, evaluator) {
714
- this.evaluators[type] = evaluator;
715
- }
716
- registerEvaluators(evaluators) {
717
- const keys = Object.keys(evaluators);
718
- for(let i = 0; i < keys.length; i++){
719
- this.registerEvaluator(keys[i], evaluators[keys[i]]);
720
- }
721
- }
722
- registerDefaultEvaluators() {
723
- this.registerEvaluator(BuiltInPolicyType.COMPOSITE, new CompositePolicyEvaluator());
724
- this.registerEvaluator(BuiltInPolicyType.ATTRIBUTES, new AttributesPolicyEvaluator());
725
- this.registerEvaluator(BuiltInPolicyType.ATTRIBUTE_NAMES, new AttributeNamesPolicyEvaluator());
726
- this.registerEvaluator(BuiltInPolicyType.DATE, new DatePolicyEvaluator());
727
- this.registerEvaluator(BuiltInPolicyType.IDENTITY, new IdentityPolicyEvaluator());
728
- this.registerEvaluator(BuiltInPolicyType.PERMISSION_BINDING, new PermissionBindingPolicyEvaluator());
729
- this.registerEvaluator(BuiltInPolicyType.REALM_MATCH, new RealmMatchPolicyEvaluator());
730
- this.registerEvaluator(BuiltInPolicyType.TIME, new TimePolicyEvaluator());
731
- }
732
- /**
733
- * @throws PolicyError
734
- *
735
- * @param ctx
736
- */ async evaluate(ctx) {
737
- return evaluatePolicy({
738
- ...ctx,
739
- options: ctx.options || {},
740
- evaluators: this.evaluators
741
- });
742
- }
743
- }
744
-
745
- class PermissionError extends AuthupError {
746
- policy;
747
- policyError;
748
- constructor(options = {}){
749
- super({
750
- ...options,
751
- message: options.message || 'A permission error occurred.',
752
- statusCode: options.statusCode || 403
753
- });
754
- }
755
- static notFound(name) {
756
- return new PermissionError({
757
- message: `The permission ${name} was not found.`,
758
- code: ErrorCode.PERMISSION_NOT_FOUND
759
- });
760
- }
761
- static denied(name) {
762
- return new PermissionError({
763
- message: `The permission ${name} has not been granted.`,
764
- code: ErrorCode.PERMISSION_DENIED
765
- });
766
- }
767
- static deniedAll(names) {
768
- return new PermissionError({
769
- message: `None of the permissions ${names.join(', ')} has been granted.`,
770
- code: ErrorCode.PERMISSION_DENIED
771
- });
772
- }
773
- static evaluationFailed(options) {
774
- const error = new PermissionError({
775
- message: `The evaluation of permission ${options.name} failed.`,
776
- code: ErrorCode.PERMISSION_EVALUATION_FAILED
777
- });
778
- if (options.policy) {
779
- error.policy = options.policy;
780
- if (options.policy.type === BuiltInPolicyType.IDENTITY) {
781
- error.statusCode = 401;
782
- }
783
- }
784
- if (options.policyError) {
785
- error.policyError = options.policyError;
786
- }
787
- return error;
788
- }
789
- }
790
-
791
- /*
792
- * Copyright (c) 2025.
793
- * Author Peter Placzek (tada5hi)
794
- * For the full copyright and license information,
795
- * view the LICENSE file that was distributed with this source code.
796
- */ function buildPermissionItemKey(input) {
797
- return `${input.realmId || '_'}/${input.clientId || '_'}/${input.name}`;
798
- }
799
-
800
- /*
801
- * Copyright (c) 2024-2024.
802
- * Author Peter Placzek (tada5hi)
803
- * For the full copyright and license information,
804
- * view the LICENSE file that was distributed with this source code.
805
- */ function isPermissionItemEqual(a, b) {
806
- if (a.name !== b.name) {
807
- return false;
808
- }
809
- if (typeof a.realmId === 'string' || typeof b.realmId === 'string') {
810
- return a.realmId === b.realmId;
811
- }
812
- if (typeof a.clientId === 'string' || typeof b.clientId === 'string') {
813
- return a.clientId === b.clientId;
814
- }
815
- return !!a.realmId === !!b.realmId && !!a.clientId === !!b.clientId;
816
- }
1003
+ const PolicyDefaultEvaluators = {
1004
+ [BuiltInPolicyType.COMPOSITE]: new CompositePolicyEvaluator(),
1005
+ [BuiltInPolicyType.ATTRIBUTES]: new AttributesPolicyEvaluator(),
1006
+ [BuiltInPolicyType.ATTRIBUTE_NAMES]: new AttributeNamesPolicyEvaluator(),
1007
+ [BuiltInPolicyType.DATE]: new DatePolicyEvaluator(),
1008
+ [BuiltInPolicyType.IDENTITY]: new IdentityPolicyEvaluator(),
1009
+ [BuiltInPolicyType.PERMISSION_BINDING]: new PermissionBindingPolicyEvaluator(),
1010
+ [BuiltInPolicyType.REALM_MATCH]: new RealmMatchPolicyEvaluator(),
1011
+ [BuiltInPolicyType.TIME]: new TimePolicyEvaluator()
1012
+ };
817
1013
 
818
1014
  function mergePermissionItems(input) {
819
1015
  const grouped = input.reduce((previous, current)=>{
@@ -846,13 +1042,17 @@ function mergePermissionItems(input) {
846
1042
  return output;
847
1043
  }
848
1044
 
849
- class PermissionMemoryProvider {
1045
+ class PermissionMemoryRepository {
850
1046
  items = {};
851
1047
  constructor(items = []){
852
1048
  this.setMany(items);
853
1049
  }
854
- async get(options) {
855
- return this.items[buildPermissionItemKey(options)];
1050
+ async findOne(options) {
1051
+ const entry = this.items[buildPermissionItemKey(options)];
1052
+ if (entry) {
1053
+ return entry;
1054
+ }
1055
+ return null;
856
1056
  }
857
1057
  setMany(input) {
858
1058
  this.items = mergePermissionItems(input).reduce((prev, current)=>{
@@ -869,10 +1069,10 @@ class PermissionChecker {
869
1069
  realmId;
870
1070
  // ----------------------------------------------
871
1071
  constructor(options = {}){
872
- if (options.provider) {
873
- this.provider = options.provider;
1072
+ if (options.repository) {
1073
+ this.provider = options.repository;
874
1074
  } else {
875
- this.provider = new PermissionMemoryProvider();
1075
+ this.provider = new PermissionMemoryRepository();
876
1076
  }
877
1077
  if (options.clientId) {
878
1078
  this.clientId = options.clientId;
@@ -883,7 +1083,7 @@ class PermissionChecker {
883
1083
  if (options.policyEngine) {
884
1084
  this.policyEngine = options.policyEngine;
885
1085
  } else {
886
- this.policyEngine = new PolicyEngine();
1086
+ this.policyEngine = new PolicyEngine(PolicyDefaultEvaluators);
887
1087
  }
888
1088
  }
889
1089
  // ----------------------------------------------
@@ -891,7 +1091,7 @@ class PermissionChecker {
891
1091
  * Get a permission.
892
1092
  *
893
1093
  * @param input
894
- */ async get(input) {
1094
+ */ async findOne(input) {
895
1095
  const options = {
896
1096
  name: input
897
1097
  };
@@ -901,7 +1101,7 @@ class PermissionChecker {
901
1101
  if (this.realmId) {
902
1102
  options.realmId = this.realmId;
903
1103
  }
904
- return this.provider.get(options);
1104
+ return this.provider.findOne(options);
905
1105
  }
906
1106
  // ----------------------------------------------
907
1107
  /**
@@ -922,14 +1122,23 @@ class PermissionChecker {
922
1122
  }
923
1123
  const { options = {} } = ctx;
924
1124
  const decisionStrategy = options.decisionStrategy ?? DecisionStrategy.UNANIMOUS;
925
- let lastError;
1125
+ const issues = [];
926
1126
  let count = 0;
1127
+ const dataBase = ctx.input || new PolicyData();
927
1128
  for(let i = 0; i < ctx.name.length; i++){
928
- const entity = await this.get(ctx.name[i]);
1129
+ const entity = await this.findOne(ctx.name[i]);
929
1130
  if (!entity) {
930
- lastError = PermissionError.notFound(ctx.name[i]);
1131
+ issues.push(defineIssueItem({
1132
+ code: ErrorCode.PERMISSION_NOT_FOUND,
1133
+ message: `The ${ctx.name[i]} permission could not be resolved`,
1134
+ path: [
1135
+ ctx.name[i]
1136
+ ]
1137
+ }));
931
1138
  if (decisionStrategy === DecisionStrategy.UNANIMOUS) {
932
- throw lastError;
1139
+ const error = PermissionError.evaluationFailed(ctx.name);
1140
+ error.addIssues(issues);
1141
+ throw error;
933
1142
  }
934
1143
  continue;
935
1144
  }
@@ -940,48 +1149,31 @@ class PermissionChecker {
940
1149
  count++;
941
1150
  continue;
942
1151
  }
943
- let outcome;
944
- try {
945
- outcome = await this.policyEngine.evaluate({
946
- input: {
947
- ...ctx.input || {},
948
- permission: entity
949
- },
950
- config: entity.policy,
951
- options: {
952
- include: options.policiesIncluded,
953
- exclude: options.policiesExcluded
954
- }
955
- });
956
- } catch (e) {
957
- if (e instanceof PolicyError) {
958
- lastError = PermissionError.evaluationFailed({
959
- name: entity.name,
960
- policy: entity.policy,
961
- policyError: e
962
- });
963
- } else {
964
- lastError = PermissionError.evaluationFailed({
965
- name: entity.name,
966
- policy: entity.policy
967
- });
968
- }
969
- outcome = false;
970
- }
971
- if (outcome) {
1152
+ const data = dataBase.clone();
1153
+ data.set(BuiltInPolicyType.PERMISSION_BINDING, entity);
1154
+ const evaluationResult = await this.policyEngine.evaluate(entity.policy, definePolicyEvaluationContext({
1155
+ include: options.policiesIncluded,
1156
+ exclude: options.policiesExcluded,
1157
+ data
1158
+ }));
1159
+ if (evaluationResult.success) {
972
1160
  if (decisionStrategy === DecisionStrategy.AFFIRMATIVE) {
973
1161
  return;
974
1162
  }
975
1163
  count++;
976
1164
  } else {
977
- if (!lastError) {
978
- lastError = PermissionError.evaluationFailed({
979
- name: entity.name,
980
- policy: entity.policy
981
- });
982
- }
1165
+ issues.push(definePolicyIssueGroup({
1166
+ code: ErrorCode.PERMISSION_EVALUATION_FAILED,
1167
+ issues: evaluationResult.issues || [],
1168
+ message: `The ${entity.name} permissions policy evaluation failed`,
1169
+ path: [
1170
+ entity.name
1171
+ ]
1172
+ }));
983
1173
  if (decisionStrategy === DecisionStrategy.UNANIMOUS) {
984
- throw lastError;
1174
+ const error = PermissionError.evaluationFailed(entity.name);
1175
+ error.addIssues(issues);
1176
+ throw error;
985
1177
  }
986
1178
  count--;
987
1179
  }
@@ -989,10 +1181,12 @@ class PermissionChecker {
989
1181
  if (count > 0) {
990
1182
  return;
991
1183
  }
992
- if (count > 1 || !lastError) {
1184
+ if (issues.length === 0) {
993
1185
  throw PermissionError.deniedAll(ctx.name);
994
1186
  } else {
995
- throw lastError;
1187
+ const error = PermissionError.evaluationFailed(ctx.name);
1188
+ error.addIssues(issues);
1189
+ throw error;
996
1190
  }
997
1191
  }
998
1192
  /**
@@ -1035,5 +1229,5 @@ class PermissionChecker {
1035
1229
  }
1036
1230
  }
1037
1231
 
1038
- export { AttributeNamesPolicyEvaluator, AttributeNamesPolicyValidator, AttributesPolicyEvaluator, AttributesPolicyValidator, BuiltInPolicyType, CompositePolicyEvaluator, CompositePolicyValidator, DatePolicyEvaluator, DatePolicyValidator, DecisionStrategy, IdentityPolicyEvaluator, IdentityPolicyValidator, PermissionBindingPolicyEvaluator, PermissionBindingPolicyValidator, PermissionChecker, PermissionError, PermissionMemoryProvider, PolicyDataIdentityValidator, PolicyDataPermissionValidator, PolicyDataValidator, PolicyEngine, PolicyError, RealmMatchPolicyEvaluator, RealmMatchPolicyValidator, TimePolicyEvaluator, TimePolicyInterval, TimePolicyValidator, buildPermissionItemKey, defineAttributesPolicy, definePolicyWithType, evaluatePolicy, isCompositePolicy, isIntervalForDayOfMonth, isIntervalForDayOfWeek, isIntervalForDayOfYear, isPermissionItemEqual, maybeInvertPolicyOutcome, mergePermissionItems };
1232
+ export { AttributeNamesPolicyEvaluator, AttributeNamesPolicyValidator, AttributesPolicyEvaluator, AttributesPolicyValidator, BuiltInPolicyType, CompositePolicyEvaluator, CompositePolicyValidator, DatePolicyEvaluator, DatePolicyValidator, DecisionStrategy, IdentityPolicyEvaluator, IdentityPolicyValidator, PermissionBindingPolicyEvaluator, PermissionBindingPolicyValidator, PermissionChecker, PermissionError, PermissionMemoryRepository, PolicyData, PolicyDefaultEvaluators, PolicyEngine, PolicyError, PolicyIssueCode, PolicyIssueSeverity, RealmMatchPolicyEvaluator, RealmMatchPolicyValidator, TimePolicyEvaluator, TimePolicyInterval, TimePolicyValidator, buildPermissionItemKey, defineAttributesPolicy, definePolicyEvaluationContext, definePolicyIssueGroup, definePolicyIssueItem, definePolicyWithType, isCompositePolicy, isIntervalForDayOfMonth, isIntervalForDayOfWeek, isIntervalForDayOfYear, isPermissionItemEqual, maybeInvertPolicyOutcome, mergePermissionItems };
1039
1233
  //# sourceMappingURL=index.mjs.map