@o3co/auth.policy-verifier.builtins 0.2.0 → 0.3.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 (64) hide show
  1. package/README.ja.md +101 -0
  2. package/README.md +101 -0
  3. package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts +2 -0
  4. package/dist/__tests__/rules/AttrLiteralCompare.test.d.mts.map +1 -0
  5. package/dist/__tests__/rules/AttrLiteralCompare.test.mjs +253 -0
  6. package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts +2 -0
  7. package/dist/__tests__/rules/AttrLiteralEqual.test.d.mts.map +1 -0
  8. package/dist/__tests__/rules/AttrLiteralEqual.test.mjs +138 -0
  9. package/dist/__tests__/rules/AttrLiteralIn.test.d.mts +2 -0
  10. package/dist/__tests__/rules/AttrLiteralIn.test.d.mts.map +1 -0
  11. package/dist/__tests__/rules/AttrLiteralIn.test.mjs +160 -0
  12. package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts +2 -0
  13. package/dist/__tests__/rules/AttrLiteralNotEqual.test.d.mts.map +1 -0
  14. package/dist/__tests__/rules/AttrLiteralNotEqual.test.mjs +157 -0
  15. package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts +2 -0
  16. package/dist/__tests__/rules/AttrLiteralNotIn.test.d.mts.map +1 -0
  17. package/dist/__tests__/rules/AttrLiteralNotIn.test.mjs +168 -0
  18. package/dist/__tests__/rules/AttrMatchRule.test.mjs +8 -0
  19. package/dist/__tests__/rules/AttrPairCompare.test.d.mts +2 -0
  20. package/dist/__tests__/rules/AttrPairCompare.test.d.mts.map +1 -0
  21. package/dist/__tests__/rules/AttrPairCompare.test.mjs +319 -0
  22. package/dist/__tests__/rules/AttrPairEqual.test.d.mts +2 -0
  23. package/dist/__tests__/rules/AttrPairEqual.test.d.mts.map +1 -0
  24. package/dist/__tests__/rules/AttrPairEqual.test.mjs +155 -0
  25. package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts +2 -0
  26. package/dist/__tests__/rules/AttrPairNotEqual.test.d.mts.map +1 -0
  27. package/dist/__tests__/rules/AttrPairNotEqual.test.mjs +155 -0
  28. package/dist/__tests__/rules/_sharedValidation.test.d.mts +2 -0
  29. package/dist/__tests__/rules/_sharedValidation.test.d.mts.map +1 -0
  30. package/dist/__tests__/rules/_sharedValidation.test.mjs +325 -0
  31. package/dist/index.d.mts +8 -0
  32. package/dist/index.d.mts.map +1 -1
  33. package/dist/index.mjs +8 -0
  34. package/dist/rules/AttrLiteralCompare.d.mts +31 -0
  35. package/dist/rules/AttrLiteralCompare.d.mts.map +1 -0
  36. package/dist/rules/AttrLiteralCompare.mjs +36 -0
  37. package/dist/rules/AttrLiteralEqual.d.mts +37 -0
  38. package/dist/rules/AttrLiteralEqual.d.mts.map +1 -0
  39. package/dist/rules/AttrLiteralEqual.mjs +43 -0
  40. package/dist/rules/AttrLiteralIn.d.mts +38 -0
  41. package/dist/rules/AttrLiteralIn.d.mts.map +1 -0
  42. package/dist/rules/AttrLiteralIn.mjs +46 -0
  43. package/dist/rules/AttrLiteralNotEqual.d.mts +36 -0
  44. package/dist/rules/AttrLiteralNotEqual.d.mts.map +1 -0
  45. package/dist/rules/AttrLiteralNotEqual.mjs +42 -0
  46. package/dist/rules/AttrLiteralNotIn.d.mts +33 -0
  47. package/dist/rules/AttrLiteralNotIn.d.mts.map +1 -0
  48. package/dist/rules/AttrLiteralNotIn.mjs +41 -0
  49. package/dist/rules/AttrMatchRule.d.mts +7 -34
  50. package/dist/rules/AttrMatchRule.d.mts.map +1 -1
  51. package/dist/rules/AttrMatchRule.mjs +7 -32
  52. package/dist/rules/AttrPairCompare.d.mts +35 -0
  53. package/dist/rules/AttrPairCompare.d.mts.map +1 -0
  54. package/dist/rules/AttrPairCompare.mjs +43 -0
  55. package/dist/rules/AttrPairEqual.d.mts +35 -0
  56. package/dist/rules/AttrPairEqual.d.mts.map +1 -0
  57. package/dist/rules/AttrPairEqual.mjs +44 -0
  58. package/dist/rules/AttrPairNotEqual.d.mts +34 -0
  59. package/dist/rules/AttrPairNotEqual.d.mts.map +1 -0
  60. package/dist/rules/AttrPairNotEqual.mjs +43 -0
  61. package/dist/rules/_sharedValidation.d.mts +87 -0
  62. package/dist/rules/_sharedValidation.d.mts.map +1 -0
  63. package/dist/rules/_sharedValidation.mjs +189 -0
  64. package/package.json +5 -2
@@ -0,0 +1,325 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { applyCompare, COMPARE_OPS, computeValuesKey, requireAttrName, requireCompareOp, requireHomogeneousLiteralArray, requireLiteralValue, requireNumber, requireOptionalGroup, } from "#/rules/_sharedValidation.mjs";
3
+ // ---------------------------------------------------------------------------
4
+ // requireAttrName
5
+ // ---------------------------------------------------------------------------
6
+ describe("requireAttrName", () => {
7
+ it("returns the input string when value is a valid non-empty string", () => {
8
+ expect(requireAttrName("MyClass", "a", "role")).toBe("role");
9
+ });
10
+ it("returns the input string for fieldName 'b'", () => {
11
+ expect(requireAttrName("MyClass", "b", "score")).toBe("score");
12
+ });
13
+ it("throws with className and fieldName 'a' in message when value is undefined", () => {
14
+ expect(() => requireAttrName("MyClass", "a", undefined)).toThrow(/MyClass.*'a'.*non-empty string.*got undefined/);
15
+ });
16
+ it("throws with className and fieldName 'b' in message when value is undefined", () => {
17
+ expect(() => requireAttrName("MyClass", "b", undefined)).toThrow(/MyClass.*'b'.*non-empty string.*got undefined/);
18
+ });
19
+ it("throws with 'empty string' in message when value is ''", () => {
20
+ expect(() => requireAttrName("MyClass", "a", "")).toThrow(/MyClass.*'a'.*got empty string/);
21
+ });
22
+ it("throws with 'got number' in message when value is 42", () => {
23
+ expect(() => requireAttrName("MyClass", "a", 42)).toThrow(/MyClass.*'a'.*got number/);
24
+ });
25
+ it("throws with 'null' in message when value is null", () => {
26
+ expect(() => requireAttrName("MyClass", "a", null)).toThrow(/null/);
27
+ });
28
+ it("throws when value contains ':' (reserved as ruleType separator)", () => {
29
+ // Regression guard for an authorization-weakening bug: with ':' allowed,
30
+ // (a="x:y", b="z") and (a="x", b="y:z") would both produce the same
31
+ // `attr_pair_equal:x:y:z` default ruleType and be silently OR-combined
32
+ // by the evaluator. Fail-fast at construction instead.
33
+ expect(() => requireAttrName("MyClass", "a", "name:space")).toThrow(/must not contain ':'.*got 'name:space'/);
34
+ });
35
+ });
36
+ // ---------------------------------------------------------------------------
37
+ // requireLiteralValue
38
+ // ---------------------------------------------------------------------------
39
+ describe("requireLiteralValue", () => {
40
+ it("returns the value when it is a string", () => {
41
+ expect(requireLiteralValue("MyClass", "v", "hello")).toBe("hello");
42
+ });
43
+ it("returns the value when it is a number", () => {
44
+ expect(requireLiteralValue("MyClass", "v", 42)).toBe(42);
45
+ });
46
+ it("returns the value when it is a boolean", () => {
47
+ expect(requireLiteralValue("MyClass", "v", false)).toBe(false);
48
+ });
49
+ it("throws with 'null' in message when value is null", () => {
50
+ expect(() => requireLiteralValue("MyClass", "v", null)).toThrow(/null/);
51
+ });
52
+ it("throws with 'undefined' in message when value is undefined", () => {
53
+ expect(() => requireLiteralValue("MyClass", "v", undefined)).toThrow(/undefined/);
54
+ });
55
+ it("throws with 'object' in message when value is a plain object", () => {
56
+ expect(() => requireLiteralValue("MyClass", "v", {})).toThrow(/object/);
57
+ });
58
+ it("throws with 'object' in message when value is an array (typeof [] === 'object')", () => {
59
+ expect(() => requireLiteralValue("MyClass", "v", [])).toThrow(/object/);
60
+ });
61
+ it("throws on NaN (NaN === NaN is false; would invert Equal/NotEqual rules)", () => {
62
+ // Regression guard for an authorization-weakening bug: with NaN accepted,
63
+ // AttrLiteralNotEqual({ v: NaN }) would always pass for any numeric
64
+ // attribute because `x !== NaN` is always true. Must fail-fast in
65
+ // construction instead.
66
+ expect(() => requireLiteralValue("MyClass", "v", Number.NaN)).toThrow(/must not be NaN/);
67
+ });
68
+ });
69
+ // ---------------------------------------------------------------------------
70
+ // requireNumber
71
+ // ---------------------------------------------------------------------------
72
+ describe("requireNumber", () => {
73
+ it("returns 0 (zero)", () => {
74
+ expect(requireNumber("MyClass", "v", 0)).toBe(0);
75
+ });
76
+ it("returns -0 (negative zero)", () => {
77
+ expect(requireNumber("MyClass", "v", -0)).toBe(-0);
78
+ });
79
+ it("returns a large finite number", () => {
80
+ expect(requireNumber("MyClass", "v", 1e308)).toBe(1e308);
81
+ });
82
+ it("returns Infinity without throwing (Infinity is a valid number per spec)", () => {
83
+ expect(requireNumber("MyClass", "v", Infinity)).toBe(Infinity);
84
+ });
85
+ it("returns -Infinity without throwing", () => {
86
+ expect(requireNumber("MyClass", "v", -Infinity)).toBe(-Infinity);
87
+ });
88
+ it("throws on NaN with message matching /must not be NaN/", () => {
89
+ expect(() => requireNumber("MyClass", "v", NaN)).toThrow(/must not be NaN/);
90
+ });
91
+ it("throws on a string '42' with message containing 'got string'", () => {
92
+ expect(() => requireNumber("MyClass", "v", "42")).toThrow(/got string/);
93
+ });
94
+ it("throws on null with message containing 'null'", () => {
95
+ expect(() => requireNumber("MyClass", "v", null)).toThrow(/null/);
96
+ });
97
+ it("throws on undefined with message containing 'undefined'", () => {
98
+ expect(() => requireNumber("MyClass", "v", undefined)).toThrow(/undefined/);
99
+ });
100
+ });
101
+ // ---------------------------------------------------------------------------
102
+ // requireHomogeneousLiteralArray
103
+ // ---------------------------------------------------------------------------
104
+ describe("requireHomogeneousLiteralArray", () => {
105
+ it("returns a non-empty all-string array", () => {
106
+ const arr = ["a", "b", "c"];
107
+ expect(requireHomogeneousLiteralArray("MyClass", arr)).toEqual(arr);
108
+ });
109
+ it("returns a non-empty all-number array", () => {
110
+ const arr = [1, 2, 3];
111
+ expect(requireHomogeneousLiteralArray("MyClass", arr)).toEqual(arr);
112
+ });
113
+ it("returns a non-empty all-boolean array", () => {
114
+ const arr = [true, false];
115
+ expect(requireHomogeneousLiteralArray("MyClass", arr)).toEqual(arr);
116
+ });
117
+ it("throws with 'got string' when value is not an array", () => {
118
+ expect(() => requireHomogeneousLiteralArray("MyClass", "abc")).toThrow(/got string/);
119
+ });
120
+ it("throws with 'undefined' when value is undefined", () => {
121
+ expect(() => requireHomogeneousLiteralArray("MyClass", undefined)).toThrow(/undefined/);
122
+ });
123
+ it("throws with 'null' when value is null", () => {
124
+ expect(() => requireHomogeneousLiteralArray("MyClass", null)).toThrow(/null/);
125
+ });
126
+ it("throws with 'non-empty array' on empty array", () => {
127
+ expect(() => requireHomogeneousLiteralArray("MyClass", [])).toThrow(/non-empty array/);
128
+ });
129
+ it("throws with 'null' when an element is null", () => {
130
+ expect(() => requireHomogeneousLiteralArray("MyClass", [null])).toThrow(/null/);
131
+ });
132
+ it("throws with 'undefined' when an element is undefined", () => {
133
+ expect(() => requireHomogeneousLiteralArray("MyClass", [undefined])).toThrow(/undefined/);
134
+ });
135
+ it("throws with 'object' when an element is an object", () => {
136
+ expect(() => requireHomogeneousLiteralArray("MyClass", [{}])).toThrow(/object/);
137
+ });
138
+ it("throws with mixed types message for ['a', 1] (left-to-right Set insertion order)", () => {
139
+ expect(() => requireHomogeneousLiteralArray("MyClass", ["a", 1])).toThrow(/mixed types \[string, number\]/);
140
+ });
141
+ it("throws when an element is NaN (would silently mismatch AttrLiteralIn/NotIn)", () => {
142
+ // Regression guard: AttrLiteralIn uses strict equality against each element;
143
+ // a NaN element could never match any attribute value because NaN !== NaN.
144
+ // Must fail-fast at construction rather than ship an always-false element.
145
+ expect(() => requireHomogeneousLiteralArray("MyClass", [1, Number.NaN, 3])).toThrow(/must not contain NaN/);
146
+ });
147
+ });
148
+ // ---------------------------------------------------------------------------
149
+ // requireCompareOp
150
+ // ---------------------------------------------------------------------------
151
+ describe("requireCompareOp", () => {
152
+ it("returns 'lt' for valid op 'lt'", () => {
153
+ expect(requireCompareOp("MyClass", "lt")).toBe("lt");
154
+ });
155
+ it("returns 'le' for valid op 'le'", () => {
156
+ expect(requireCompareOp("MyClass", "le")).toBe("le");
157
+ });
158
+ it("returns 'gt' for valid op 'gt'", () => {
159
+ expect(requireCompareOp("MyClass", "gt")).toBe("gt");
160
+ });
161
+ it("returns 'ge' for valid op 'ge'", () => {
162
+ expect(requireCompareOp("MyClass", "ge")).toBe("ge");
163
+ });
164
+ it("throws on invalid string 'eq' with message listing valid ops joined by ' | '", () => {
165
+ expect(() => requireCompareOp("MyClass", "eq")).toThrow(/lt \| le \| gt \| ge/);
166
+ });
167
+ it("throws on empty string '' with message listing valid ops", () => {
168
+ expect(() => requireCompareOp("MyClass", "")).toThrow(/lt \| le \| gt \| ge/);
169
+ });
170
+ it("throws on symbolic string '<' with message listing valid ops", () => {
171
+ expect(() => requireCompareOp("MyClass", "<")).toThrow(/lt \| le \| gt \| ge/);
172
+ });
173
+ it("throws on number 42 with message containing the type label", () => {
174
+ expect(() => requireCompareOp("MyClass", 42)).toThrow(/got number/);
175
+ });
176
+ it("throws on null with message containing the type label", () => {
177
+ expect(() => requireCompareOp("MyClass", null)).toThrow(/null/);
178
+ });
179
+ it("throws on undefined with message containing the type label", () => {
180
+ expect(() => requireCompareOp("MyClass", undefined)).toThrow(/undefined/);
181
+ });
182
+ });
183
+ // ---------------------------------------------------------------------------
184
+ // requireOptionalGroup
185
+ // ---------------------------------------------------------------------------
186
+ describe("requireOptionalGroup", () => {
187
+ it("returns undefined when value is undefined", () => {
188
+ expect(requireOptionalGroup("MyClass", undefined)).toBeUndefined();
189
+ });
190
+ it("returns the input string when value is a non-empty string", () => {
191
+ expect(requireOptionalGroup("MyClass", "my-group")).toBe("my-group");
192
+ });
193
+ it("throws with 'empty string' message on ''", () => {
194
+ expect(() => requireOptionalGroup("MyClass", "")).toThrow(/empty string/);
195
+ });
196
+ it("throws with 'got number' message on 42", () => {
197
+ expect(() => requireOptionalGroup("MyClass", 42)).toThrow(/got number/);
198
+ });
199
+ it("throws with 'null' in message on null", () => {
200
+ expect(() => requireOptionalGroup("MyClass", null)).toThrow(/null/);
201
+ });
202
+ });
203
+ // ---------------------------------------------------------------------------
204
+ // computeValuesKey
205
+ // ---------------------------------------------------------------------------
206
+ describe("computeValuesKey", () => {
207
+ it("returns a string matching /{type}:{count}:{8-hex-chars}/ format", () => {
208
+ expect(computeValuesKey(["admin"])).toMatch(/^(string|number|boolean):[0-9]+:[0-9a-f]{8}$/);
209
+ });
210
+ it("sort determinism: same content in different order produces the same key", () => {
211
+ expect(computeValuesKey(["b", "a"])).toBe(computeValuesKey(["a", "b"]));
212
+ });
213
+ it("count discrimination: ['a'] !== ['a', 'b'] (distinct content → distinct key)", () => {
214
+ expect(computeValuesKey(["a"])).not.toBe(computeValuesKey(["a", "b"]));
215
+ });
216
+ it("dedup parity: ['a'] === ['a', 'a'] (duplicates do not affect the Set-based rule behavior, so they must not affect ruleType either)", () => {
217
+ // AttrLiteralIn / AttrLiteralNotIn use `new Set(values)` for verify(),
218
+ // so duplicate elements have no semantic effect. The ruleType must
219
+ // mirror that — otherwise two logically-equivalent rules would be
220
+ // placed in different evaluator groups and be AND-combined, when
221
+ // the caller intended them to be OR-combined (shared ruleType).
222
+ expect(computeValuesKey(["a"])).toBe(computeValuesKey(["a", "a"]));
223
+ });
224
+ it("content discrimination: ['a', 'b'] !== ['a', 'c'] (hash differs)", () => {
225
+ expect(computeValuesKey(["a", "b"])).not.toBe(computeValuesKey(["a", "c"]));
226
+ });
227
+ it("type segment correctness: string array starts with 'string:'", () => {
228
+ expect(computeValuesKey(["admin"])).toMatch(/^string:/);
229
+ });
230
+ it("type segment correctness: number array starts with 'number:'", () => {
231
+ expect(computeValuesKey([1])).toMatch(/^number:/);
232
+ });
233
+ it("type segment correctness: boolean array starts with 'boolean:'", () => {
234
+ expect(computeValuesKey([true])).toMatch(/^boolean:/);
235
+ });
236
+ it("type divergence: computeValuesKey(['1']) !== computeValuesKey([1]) (type segment differs)", () => {
237
+ expect(computeValuesKey(["1"])).not.toBe(computeValuesKey([1]));
238
+ });
239
+ it("separator collision guard: ['a,b', 'c'] !== ['a', 'b,c'] (values containing commas do not collide)", () => {
240
+ // Regression guard: a naive implementation of the form
241
+ // values.map(String).sort().join(",")
242
+ // would produce the same string "a,b,c" for both inputs and thus the
243
+ // same ruleType. That would cause the evaluator to OR what the caller
244
+ // meant as two independent set constraints, weakening authorization.
245
+ // The canonical form JSON.stringify-es each element before joining,
246
+ // so quotes/escapes disambiguate the element boundary.
247
+ expect(computeValuesKey(["a,b", "c"])).not.toBe(computeValuesKey(["a", "b,c"]));
248
+ });
249
+ it("pure / referentially transparent: two calls with the same input return the same string", () => {
250
+ const input = ["x", "y", "z"];
251
+ expect(computeValuesKey(input)).toBe(computeValuesKey(input));
252
+ });
253
+ });
254
+ // ---------------------------------------------------------------------------
255
+ // applyCompare
256
+ // ---------------------------------------------------------------------------
257
+ describe("applyCompare", () => {
258
+ describe("op=lt", () => {
259
+ it("returns true when left < right (1 < 2)", () => {
260
+ expect(applyCompare("lt", 1, 2)).toBe(true);
261
+ });
262
+ it("returns false when left === right (2 < 2 is false)", () => {
263
+ expect(applyCompare("lt", 2, 2)).toBe(false);
264
+ });
265
+ it("returns false when left > right (3 < 2 is false)", () => {
266
+ expect(applyCompare("lt", 3, 2)).toBe(false);
267
+ });
268
+ it("edge: -Infinity < 0 returns true", () => {
269
+ expect(applyCompare("lt", -Infinity, 0)).toBe(true);
270
+ });
271
+ });
272
+ describe("op=le", () => {
273
+ it("returns true when left < right (1 <= 2)", () => {
274
+ expect(applyCompare("le", 1, 2)).toBe(true);
275
+ });
276
+ it("returns true when left === right (2 <= 2)", () => {
277
+ expect(applyCompare("le", 2, 2)).toBe(true);
278
+ });
279
+ it("returns false when left > right (3 <= 2 is false)", () => {
280
+ expect(applyCompare("le", 3, 2)).toBe(false);
281
+ });
282
+ it("edge: 0 >= -0 returns true (ge symmetry check; 0 <= -0 also true)", () => {
283
+ expect(applyCompare("le", 0, -0)).toBe(true);
284
+ });
285
+ });
286
+ describe("op=gt", () => {
287
+ it("returns true when left > right (3 > 2)", () => {
288
+ expect(applyCompare("gt", 3, 2)).toBe(true);
289
+ });
290
+ it("returns false when left === right (2 > 2 is false)", () => {
291
+ expect(applyCompare("gt", 2, 2)).toBe(false);
292
+ });
293
+ it("returns false when left < right (1 > 2 is false)", () => {
294
+ expect(applyCompare("gt", 1, 2)).toBe(false);
295
+ });
296
+ it("edge: Infinity > 1e308 returns true", () => {
297
+ expect(applyCompare("gt", Infinity, 1e308)).toBe(true);
298
+ });
299
+ });
300
+ describe("op=ge", () => {
301
+ it("returns true when left > right (3 >= 2)", () => {
302
+ expect(applyCompare("ge", 3, 2)).toBe(true);
303
+ });
304
+ it("returns true when left === right (2 >= 2)", () => {
305
+ expect(applyCompare("ge", 2, 2)).toBe(true);
306
+ });
307
+ it("returns false when left < right (1 >= 2 is false)", () => {
308
+ expect(applyCompare("ge", 1, 2)).toBe(false);
309
+ });
310
+ it("edge: 0 >= -0 returns true", () => {
311
+ expect(applyCompare("ge", 0, -0)).toBe(true);
312
+ });
313
+ });
314
+ });
315
+ // ---------------------------------------------------------------------------
316
+ // COMPARE_OPS
317
+ // ---------------------------------------------------------------------------
318
+ describe("COMPARE_OPS", () => {
319
+ it("exposes exactly 4 values", () => {
320
+ expect(COMPARE_OPS).toHaveLength(4);
321
+ });
322
+ it("contains 'lt', 'le', 'gt', 'ge' in order", () => {
323
+ expect(COMPARE_OPS).toEqual(["lt", "le", "gt", "ge"]);
324
+ });
325
+ });
package/dist/index.d.mts CHANGED
@@ -4,7 +4,15 @@ export { StaticPermissionCollector } from "./collectors/StaticPermissionCollecto
4
4
  export { StaticRoleCollector } from "./collectors/StaticRoleCollector.mjs";
5
5
  export { builtinCollectorsModule } from "./module.mjs";
6
6
  export { DotNotationResourceParser } from "./resource/DotNotationResourceParser.mjs";
7
+ export { AttrLiteralCompare, type AttrLiteralCompareConfig } from "./rules/AttrLiteralCompare.mjs";
8
+ export { AttrLiteralEqual, type AttrLiteralEqualConfig } from "./rules/AttrLiteralEqual.mjs";
9
+ export { AttrLiteralIn, type AttrLiteralInConfig } from "./rules/AttrLiteralIn.mjs";
10
+ export { AttrLiteralNotEqual, type AttrLiteralNotEqualConfig, } from "./rules/AttrLiteralNotEqual.mjs";
11
+ export { AttrLiteralNotIn, type AttrLiteralNotInConfig } from "./rules/AttrLiteralNotIn.mjs";
7
12
  export { AttrMatchRule, type AttrMatchRuleConfig } from "./rules/AttrMatchRule.mjs";
13
+ export { AttrPairCompare, type AttrPairCompareConfig } from "./rules/AttrPairCompare.mjs";
14
+ export { AttrPairEqual, type AttrPairEqualConfig } from "./rules/AttrPairEqual.mjs";
15
+ export { AttrPairNotEqual, type AttrPairNotEqualConfig } from "./rules/AttrPairNotEqual.mjs";
8
16
  export { HasPermission } from "./rules/HasPermission.mjs";
9
17
  export { HasScope } from "./rules/HasScope.mjs";
10
18
  export { ResourceActionPermissionRuleCollector } from "./rules/ResourceActionPermissionRuleCollector.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAC/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAE3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAErF,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,qCAAqC,EAAE,MAAM,mDAAmD,CAAC;AAC1G,OAAO,EAAE,gCAAgC,EAAE,MAAM,8CAA8C,CAAC"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,wCAAwC,CAAC;AAC/E,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AACvF,OAAO,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAE3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AAErF,OAAO,EAAE,kBAAkB,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAE,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EACN,mBAAmB,EACnB,KAAK,yBAAyB,GAC9B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAC1F,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAE,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC7F,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,qCAAqC,EAAE,MAAM,mDAAmD,CAAC;AAC1G,OAAO,EAAE,gCAAgC,EAAE,MAAM,8CAA8C,CAAC"}
package/dist/index.mjs CHANGED
@@ -8,7 +8,15 @@ export { builtinCollectorsModule } from "./module.mjs";
8
8
  // Resource
9
9
  export { DotNotationResourceParser } from "./resource/DotNotationResourceParser.mjs";
10
10
  // Rules
11
+ export { AttrLiteralCompare } from "./rules/AttrLiteralCompare.mjs";
12
+ export { AttrLiteralEqual } from "./rules/AttrLiteralEqual.mjs";
13
+ export { AttrLiteralIn } from "./rules/AttrLiteralIn.mjs";
14
+ export { AttrLiteralNotEqual, } from "./rules/AttrLiteralNotEqual.mjs";
15
+ export { AttrLiteralNotIn } from "./rules/AttrLiteralNotIn.mjs";
11
16
  export { AttrMatchRule } from "./rules/AttrMatchRule.mjs";
17
+ export { AttrPairCompare } from "./rules/AttrPairCompare.mjs";
18
+ export { AttrPairEqual } from "./rules/AttrPairEqual.mjs";
19
+ export { AttrPairNotEqual } from "./rules/AttrPairNotEqual.mjs";
12
20
  export { HasPermission } from "./rules/HasPermission.mjs";
13
21
  export { HasScope } from "./rules/HasScope.mjs";
14
22
  export { ResourceActionPermissionRuleCollector } from "./rules/ResourceActionPermissionRuleCollector.mjs";
@@ -0,0 +1,31 @@
1
+ import type { Attributes, Rule } from "@o3co/auth.policy-verifier.core";
2
+ import { type CompareOp } from "./_sharedValidation.mjs";
3
+ export interface AttrLiteralCompareConfig {
4
+ a: string;
5
+ op: CompareOp;
6
+ v: number;
7
+ group?: string;
8
+ }
9
+ /**
10
+ * Rule that passes when a named attribute is present, is a number, and
11
+ * satisfies the configured comparison operator against the configured literal
12
+ * number `v`. NaN attributes always return false. Missing, null, or non-number
13
+ * attributes return false (safe-deny). NaN is rejected at construction time.
14
+ *
15
+ * ## Grouping and the default ruleType
16
+ *
17
+ * The evaluator groups rules by `ruleType`, ORs within a group, and ANDs
18
+ * across groups. The default `ruleType` is derived from `a`, `op`, and `v`:
19
+ * `attr_literal_compare:{a}:{op}:{String(v)}`
20
+ *
21
+ * Pass an explicit `group` string to override the default ruleType entirely.
22
+ */
23
+ export declare class AttrLiteralCompare implements Rule {
24
+ private readonly config;
25
+ readonly ruleType: string;
26
+ readonly code = "attr_compare_violated";
27
+ readonly message: string;
28
+ constructor(config: AttrLiteralCompareConfig);
29
+ verify(attrs: Attributes): boolean;
30
+ }
31
+ //# sourceMappingURL=AttrLiteralCompare.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttrLiteralCompare.d.mts","sourceRoot":"","sources":["../../src/rules/AttrLiteralCompare.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAEN,KAAK,SAAS,EAKd,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,wBAAwB;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,SAAS,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,kBAAmB,YAAW,IAAI;IAKlC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,2BAA2B;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEI,MAAM,EAAE,wBAAwB;IAU7D,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;CAKlC"}
@@ -0,0 +1,36 @@
1
+ import { applyCompare, requireAttrName, requireCompareOp, requireNumber, requireOptionalGroup, } from "./_sharedValidation.mjs";
2
+ /**
3
+ * Rule that passes when a named attribute is present, is a number, and
4
+ * satisfies the configured comparison operator against the configured literal
5
+ * number `v`. NaN attributes always return false. Missing, null, or non-number
6
+ * attributes return false (safe-deny). NaN is rejected at construction time.
7
+ *
8
+ * ## Grouping and the default ruleType
9
+ *
10
+ * The evaluator groups rules by `ruleType`, ORs within a group, and ANDs
11
+ * across groups. The default `ruleType` is derived from `a`, `op`, and `v`:
12
+ * `attr_literal_compare:{a}:{op}:{String(v)}`
13
+ *
14
+ * Pass an explicit `group` string to override the default ruleType entirely.
15
+ */
16
+ export class AttrLiteralCompare {
17
+ config;
18
+ ruleType;
19
+ code = "attr_compare_violated";
20
+ message;
21
+ constructor(config) {
22
+ this.config = config;
23
+ requireAttrName("AttrLiteralCompare", "a", config.a);
24
+ requireNumber("AttrLiteralCompare", "v", config.v);
25
+ requireCompareOp("AttrLiteralCompare", config.op);
26
+ const group = requireOptionalGroup("AttrLiteralCompare", config.group);
27
+ this.ruleType = group ?? `attr_literal_compare:${config.a}:${config.op}:${String(config.v)}`;
28
+ this.message = `Attribute constraint not satisfied: ${config.a} must be ${config.op} ${String(config.v)}.`;
29
+ }
30
+ verify(attrs) {
31
+ const x = attrs.get(this.config.a);
32
+ if (typeof x !== "number")
33
+ return false;
34
+ return applyCompare(this.config.op, x, this.config.v);
35
+ }
36
+ }
@@ -0,0 +1,37 @@
1
+ import type { Attributes, Rule } from "@o3co/auth.policy-verifier.core";
2
+ import { type LiteralValue } from "./_sharedValidation.mjs";
3
+ export interface AttrLiteralEqualConfig {
4
+ a: string;
5
+ v: LiteralValue;
6
+ group?: string;
7
+ }
8
+ /**
9
+ * Rule that passes when a named attribute is present and strictly equal to a
10
+ * configured literal value (string, number, or boolean). No type coercion is
11
+ * performed: the attribute value must be the same type and value as `v`.
12
+ *
13
+ * ## Grouping and the default ruleType
14
+ *
15
+ * The evaluator groups rules by `ruleType`, ORs within a group, and ANDs
16
+ * across groups. The default `ruleType` is derived from `a`, `typeof v`, and
17
+ * `String(v)` so that distinct literal requirements are AND-combined by default:
18
+ * `attr_literal_equal:{a}:{typeof v}:{String(v)}`
19
+ *
20
+ * The `typeof v` segment is required because `String(v)` collapses distinct-
21
+ * type literals that stringify the same way (e.g. `true` vs `"true"`, `1` vs
22
+ * `"1"`). Without it, two rules on the same attribute with different-type
23
+ * literals would share a `ruleType` and be OR-combined by the evaluator,
24
+ * silently weakening authorization.
25
+ *
26
+ * Pass a shared `group` string to two instances to opt into OR semantics
27
+ * (e.g. "role is admin" OR "role is superuser" under one group).
28
+ */
29
+ export declare class AttrLiteralEqual implements Rule {
30
+ private readonly config;
31
+ readonly ruleType: string;
32
+ readonly code = "attr_not_equal";
33
+ readonly message: string;
34
+ constructor(config: AttrLiteralEqualConfig);
35
+ verify(attrs: Attributes): boolean;
36
+ }
37
+ //# sourceMappingURL=AttrLiteralEqual.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttrLiteralEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrLiteralEqual.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EACN,KAAK,YAAY,EAIjB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,sBAAsB;IACtC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,YAAY,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,gBAAiB,YAAW,IAAI;IAKhC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEI,MAAM,EAAE,sBAAsB;IAU3D,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;CAKlC"}
@@ -0,0 +1,43 @@
1
+ import { requireAttrName, requireLiteralValue, requireOptionalGroup, } from "./_sharedValidation.mjs";
2
+ /**
3
+ * Rule that passes when a named attribute is present and strictly equal to a
4
+ * configured literal value (string, number, or boolean). No type coercion is
5
+ * performed: the attribute value must be the same type and value as `v`.
6
+ *
7
+ * ## Grouping and the default ruleType
8
+ *
9
+ * The evaluator groups rules by `ruleType`, ORs within a group, and ANDs
10
+ * across groups. The default `ruleType` is derived from `a`, `typeof v`, and
11
+ * `String(v)` so that distinct literal requirements are AND-combined by default:
12
+ * `attr_literal_equal:{a}:{typeof v}:{String(v)}`
13
+ *
14
+ * The `typeof v` segment is required because `String(v)` collapses distinct-
15
+ * type literals that stringify the same way (e.g. `true` vs `"true"`, `1` vs
16
+ * `"1"`). Without it, two rules on the same attribute with different-type
17
+ * literals would share a `ruleType` and be OR-combined by the evaluator,
18
+ * silently weakening authorization.
19
+ *
20
+ * Pass a shared `group` string to two instances to opt into OR semantics
21
+ * (e.g. "role is admin" OR "role is superuser" under one group).
22
+ */
23
+ export class AttrLiteralEqual {
24
+ config;
25
+ ruleType;
26
+ code = "attr_not_equal";
27
+ message;
28
+ constructor(config) {
29
+ this.config = config;
30
+ requireAttrName("AttrLiteralEqual", "a", config.a);
31
+ requireLiteralValue("AttrLiteralEqual", "v", config.v);
32
+ const group = requireOptionalGroup("AttrLiteralEqual", config.group);
33
+ this.ruleType =
34
+ group ?? `attr_literal_equal:${config.a}:${typeof config.v}:${String(config.v)}`;
35
+ this.message = `Attribute constraint not satisfied: ${config.a} must equal ${String(config.v)}.`;
36
+ }
37
+ verify(attrs) {
38
+ const x = attrs.get(this.config.a);
39
+ if (typeof x !== typeof this.config.v)
40
+ return false;
41
+ return x === this.config.v;
42
+ }
43
+ }
@@ -0,0 +1,38 @@
1
+ import type { Attributes, Rule } from "@o3co/auth.policy-verifier.core";
2
+ import { type LiteralValue } from "./_sharedValidation.mjs";
3
+ export interface AttrLiteralInConfig {
4
+ a: string;
5
+ values: LiteralValue[];
6
+ group?: string;
7
+ }
8
+ /**
9
+ * Rule that passes when a named attribute is present, matches the element type
10
+ * of the configured values array, and is included in that array. No type
11
+ * coercion is performed. If the attribute is missing, null, or of the wrong
12
+ * type the rule returns false (safe-deny).
13
+ *
14
+ * ## Grouping and the default ruleType
15
+ *
16
+ * The evaluator groups rules by `ruleType`, ORs within a group, and ANDs
17
+ * across groups. The default `ruleType` is derived from `a` and a stable
18
+ * content-hash key over the values array:
19
+ * `attr_literal_in:{a}:{valuesKey}`
20
+ *
21
+ * Two instances with the same `a` but different `values` will produce distinct
22
+ * ruleTypes and thus be AND-combined by the evaluator. Two instances with the
23
+ * same `a` and logically equivalent `values` (regardless of insertion order)
24
+ * will share a ruleType and be OR-combined.
25
+ *
26
+ * Pass an explicit `group` string to override the default ruleType entirely.
27
+ */
28
+ export declare class AttrLiteralIn implements Rule {
29
+ private readonly config;
30
+ readonly ruleType: string;
31
+ readonly code = "attr_not_in_set";
32
+ readonly message: string;
33
+ private readonly elementType;
34
+ private readonly valuesSet;
35
+ constructor(config: AttrLiteralInConfig);
36
+ verify(attrs: Attributes): boolean;
37
+ }
38
+ //# sourceMappingURL=AttrLiteralIn.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AttrLiteralIn.d.mts","sourceRoot":"","sources":["../../src/rules/AttrLiteralIn.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAEN,KAAK,YAAY,EAIjB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,mBAAmB;IACnC,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,aAAc,YAAW,IAAI;IAQ7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAPnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,qBAAqB;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoB;gBAEjB,MAAM,EAAE,mBAAmB;IAaxD,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO;CAKlC"}
@@ -0,0 +1,46 @@
1
+ import { computeValuesKey, requireAttrName, requireHomogeneousLiteralArray, requireOptionalGroup, } from "./_sharedValidation.mjs";
2
+ /**
3
+ * Rule that passes when a named attribute is present, matches the element type
4
+ * of the configured values array, and is included in that array. No type
5
+ * coercion is performed. If the attribute is missing, null, or of the wrong
6
+ * type the rule returns false (safe-deny).
7
+ *
8
+ * ## Grouping and the default ruleType
9
+ *
10
+ * The evaluator groups rules by `ruleType`, ORs within a group, and ANDs
11
+ * across groups. The default `ruleType` is derived from `a` and a stable
12
+ * content-hash key over the values array:
13
+ * `attr_literal_in:{a}:{valuesKey}`
14
+ *
15
+ * Two instances with the same `a` but different `values` will produce distinct
16
+ * ruleTypes and thus be AND-combined by the evaluator. Two instances with the
17
+ * same `a` and logically equivalent `values` (regardless of insertion order)
18
+ * will share a ruleType and be OR-combined.
19
+ *
20
+ * Pass an explicit `group` string to override the default ruleType entirely.
21
+ */
22
+ export class AttrLiteralIn {
23
+ config;
24
+ ruleType;
25
+ code = "attr_not_in_set";
26
+ message;
27
+ elementType;
28
+ valuesSet;
29
+ constructor(config) {
30
+ this.config = config;
31
+ requireAttrName("AttrLiteralIn", "a", config.a);
32
+ const values = requireHomogeneousLiteralArray("AttrLiteralIn", config.values);
33
+ const group = requireOptionalGroup("AttrLiteralIn", config.group);
34
+ this.elementType = typeof values[0];
35
+ this.valuesSet = new Set(values);
36
+ const valuesKey = computeValuesKey(values);
37
+ this.ruleType = group ?? `attr_literal_in:${config.a}:${valuesKey}`;
38
+ this.message = `Attribute constraint not satisfied: ${config.a} must be one of [${values.map(String).join(", ")}].`;
39
+ }
40
+ verify(attrs) {
41
+ const x = attrs.get(this.config.a);
42
+ if (typeof x !== this.elementType)
43
+ return false;
44
+ return this.valuesSet.has(x);
45
+ }
46
+ }
@@ -0,0 +1,36 @@
1
+ import type { Attributes, Rule } from "@o3co/auth.policy-verifier.core";
2
+ import { type LiteralValue } from "./_sharedValidation.mjs";
3
+ export interface AttrLiteralNotEqualConfig {
4
+ a: string;
5
+ v: LiteralValue;
6
+ group?: string;
7
+ }
8
+ /**
9
+ * Rule that passes when a named attribute is present, matches the type of the
10
+ * configured literal value, and is strictly NOT equal to it. No type coercion
11
+ * is performed. If the attribute is missing, null, or of the wrong type the
12
+ * rule returns false (safe-deny).
13
+ *
14
+ * ## Grouping and the default ruleType
15
+ *
16
+ * The evaluator groups rules by `ruleType`, ORs within a group, and ANDs
17
+ * across groups. The default `ruleType` is derived from `a`, `typeof v`, and
18
+ * `String(v)`:
19
+ * `attr_literal_not_equal:{a}:{typeof v}:{String(v)}`
20
+ *
21
+ * The `typeof v` segment prevents silent `ruleType` collisions between
22
+ * different-type literals that stringify the same way (e.g. `1` vs `"1"`),
23
+ * which would otherwise be OR-combined by the evaluator against the intent
24
+ * of two independent constraints. See AttrLiteralEqual for the same rationale.
25
+ *
26
+ * Pass a shared `group` string to two instances to opt into OR semantics.
27
+ */
28
+ export declare class AttrLiteralNotEqual implements Rule {
29
+ private readonly config;
30
+ readonly ruleType: string;
31
+ readonly code = "attr_equal";
32
+ readonly message: string;
33
+ constructor(config: AttrLiteralNotEqualConfig);
34
+ verify(attrs: Attributes): boolean;
35
+ }
36
+ //# sourceMappingURL=AttrLiteralNotEqual.d.mts.map