@o3co/auth.policy-verifier.builtins 0.15.0 → 0.16.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 (85) hide show
  1. package/README.ja.md +45 -7
  2. package/README.md +47 -8
  3. package/dist/collectors/DelegationRangeCollector.d.mts +29 -0
  4. package/dist/collectors/DelegationRangeCollector.d.mts.map +1 -0
  5. package/dist/collectors/DelegationRangeCollector.mjs +29 -0
  6. package/dist/collectors/PayloadClaimAttributeCollector.d.mts +24 -39
  7. package/dist/collectors/PayloadClaimAttributeCollector.d.mts.map +1 -1
  8. package/dist/collectors/PayloadClaimAttributeCollector.mjs +26 -41
  9. package/dist/collectors/PayloadScopeCollector.d.mts +1 -1
  10. package/dist/collectors/PayloadSubjectIdCollector.d.mts +1 -1
  11. package/dist/collectors/PayloadSubjectIdCollector.mjs +1 -1
  12. package/dist/collectors/RequestContextAttributeCollector.d.mts +24 -62
  13. package/dist/collectors/RequestContextAttributeCollector.d.mts.map +1 -1
  14. package/dist/collectors/RequestContextAttributeCollector.mjs +33 -74
  15. package/dist/collectors/StaticPermissionCollector.d.mts +7 -9
  16. package/dist/collectors/StaticPermissionCollector.d.mts.map +1 -1
  17. package/dist/collectors/StaticPermissionCollector.mjs +7 -9
  18. package/dist/collectors/StaticRoleCollector.d.mts +9 -10
  19. package/dist/collectors/StaticRoleCollector.d.mts.map +1 -1
  20. package/dist/collectors/StaticRoleCollector.mjs +10 -11
  21. package/dist/collectors/_attributeMapping.d.mts +1 -0
  22. package/dist/collectors/_attributeMapping.d.mts.map +1 -1
  23. package/dist/collectors/_attributeMapping.mjs +3 -0
  24. package/dist/collectors/_claims.d.mts +2 -3
  25. package/dist/collectors/_claims.d.mts.map +1 -1
  26. package/dist/collectors/_claims.mjs +3 -4
  27. package/dist/delegation/range.d.mts +48 -0
  28. package/dist/delegation/range.d.mts.map +1 -0
  29. package/dist/delegation/range.mjs +108 -0
  30. package/dist/index.d.mts +5 -0
  31. package/dist/index.d.mts.map +1 -1
  32. package/dist/index.mjs +7 -0
  33. package/dist/keys.d.mts +9 -0
  34. package/dist/keys.d.mts.map +1 -0
  35. package/dist/keys.mjs +31 -0
  36. package/dist/module.d.mts.map +1 -1
  37. package/dist/module.mjs +4 -0
  38. package/dist/resource/DotNotationResourceParser.d.mts +16 -19
  39. package/dist/resource/DotNotationResourceParser.d.mts.map +1 -1
  40. package/dist/resource/DotNotationResourceParser.mjs +20 -25
  41. package/dist/rules/AttrLiteralCompare.d.mts +3 -5
  42. package/dist/rules/AttrLiteralCompare.d.mts.map +1 -1
  43. package/dist/rules/AttrLiteralCompare.mjs +3 -5
  44. package/dist/rules/AttrLiteralEqual.d.mts +3 -5
  45. package/dist/rules/AttrLiteralEqual.d.mts.map +1 -1
  46. package/dist/rules/AttrLiteralEqual.mjs +3 -5
  47. package/dist/rules/AttrLiteralIn.d.mts +3 -5
  48. package/dist/rules/AttrLiteralIn.d.mts.map +1 -1
  49. package/dist/rules/AttrLiteralIn.mjs +3 -5
  50. package/dist/rules/AttrLiteralNotEqual.d.mts +6 -8
  51. package/dist/rules/AttrLiteralNotEqual.d.mts.map +1 -1
  52. package/dist/rules/AttrLiteralNotEqual.mjs +6 -8
  53. package/dist/rules/AttrLiteralNotIn.d.mts +4 -6
  54. package/dist/rules/AttrLiteralNotIn.d.mts.map +1 -1
  55. package/dist/rules/AttrLiteralNotIn.mjs +4 -6
  56. package/dist/rules/AttrMatchRule.d.mts +1 -1
  57. package/dist/rules/AttrMatchRule.mjs +1 -1
  58. package/dist/rules/AttrPairCompare.d.mts +3 -5
  59. package/dist/rules/AttrPairCompare.d.mts.map +1 -1
  60. package/dist/rules/AttrPairCompare.mjs +3 -5
  61. package/dist/rules/AttrPairEqual.d.mts +6 -9
  62. package/dist/rules/AttrPairEqual.d.mts.map +1 -1
  63. package/dist/rules/AttrPairEqual.mjs +6 -9
  64. package/dist/rules/AttrPairNotEqual.d.mts +3 -5
  65. package/dist/rules/AttrPairNotEqual.d.mts.map +1 -1
  66. package/dist/rules/AttrPairNotEqual.mjs +3 -5
  67. package/dist/rules/HasPermission.d.mts +9 -10
  68. package/dist/rules/HasPermission.d.mts.map +1 -1
  69. package/dist/rules/HasPermission.mjs +16 -20
  70. package/dist/rules/HasScope.d.mts +11 -14
  71. package/dist/rules/HasScope.d.mts.map +1 -1
  72. package/dist/rules/HasScope.mjs +8 -11
  73. package/dist/rules/WithinDelegationRange.d.mts +22 -0
  74. package/dist/rules/WithinDelegationRange.d.mts.map +1 -0
  75. package/dist/rules/WithinDelegationRange.mjs +38 -0
  76. package/dist/rules/_sharedValidation.d.mts +12 -19
  77. package/dist/rules/_sharedValidation.d.mts.map +1 -1
  78. package/dist/rules/_sharedValidation.mjs +17 -25
  79. package/dist/rules/collectors/DelegationRangeRuleCollector.d.mts +36 -0
  80. package/dist/rules/collectors/DelegationRangeRuleCollector.d.mts.map +1 -0
  81. package/dist/rules/collectors/DelegationRangeRuleCollector.mjs +36 -0
  82. package/dist/rules/collectors/ResourceActionScopeRuleCollector.d.mts +19 -28
  83. package/dist/rules/collectors/ResourceActionScopeRuleCollector.d.mts.map +1 -1
  84. package/dist/rules/collectors/ResourceActionScopeRuleCollector.mjs +21 -30
  85. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"AttrPairEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrPairEqual.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,MAAM,WAAW,mBAAmB;IACnC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,aAAc,YAAW,IAAI;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;gBAEf,MAAM,EAAE,mBAAmB;IAWvC,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;CAO1C"}
1
+ {"version":3,"file":"AttrPairEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrPairEqual.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,MAAM,WAAW,mBAAmB;IACnC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,aAAc,YAAW,IAAI;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;gBAEf,MAAM,EAAE,mBAAmB;IAWvC,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;CAO1C"}
@@ -6,11 +6,10 @@ import { requireAttrName, requireOptionalGroup } from "./_sharedValidation.mjs";
6
6
  * strings, and are strictly equal. No type coercion is performed: both
7
7
  * attribute values must be non-empty strings.
8
8
  *
9
- * ## Backward compatibility
9
+ * ## String-only
10
10
  *
11
- * AttrPairEqual deliberately retains the string-only semantic of the legacy
12
- * AttrMatchRule. Do not widen verify() to accept numbers or booleans — the
13
- * Phase 4 AttrMatchRule alias depends on this invariant.
11
+ * The deprecated `AttrMatchRule` inherits verify() from this rule and must stay
12
+ * string-only: do not widen verify() to accept numbers or booleans.
14
13
  *
15
14
  * ## Grouping and the default ruleType
16
15
  *
@@ -23,11 +22,9 @@ import { requireAttrName, requireOptionalGroup } from "./_sharedValidation.mjs";
23
22
  *
24
23
  * ## Configuration is copied at construction
25
24
  *
26
- * The constructor reads each field of `config` once, validates it, and keeps
27
- * the validated value in a field of its own; the object is not retained (#255).
28
- * A caller that mutates the config afterwards changes nothing: the rule answers
29
- * from the values it validated, and `ruleType` and `message` keep describing
30
- * them.
25
+ * The constructor validates each field of `config` once and keeps the
26
+ * validated value; the object is not retained, so mutating it afterwards
27
+ * changes neither the rule's answers nor its `ruleType` and `message`.
31
28
  */
32
29
  export class AttrPairEqual {
33
30
  ruleType;
@@ -25,11 +25,9 @@ export interface AttrPairNotEqualConfig {
25
25
  *
26
26
  * ## Configuration is copied at construction
27
27
  *
28
- * The constructor reads each field of `config` once, validates it, and keeps
29
- * the validated value in a field of its own; the object is not retained (#255).
30
- * A caller that mutates the config afterwards changes nothing: the rule answers
31
- * from the values it validated, and `ruleType` and `message` keep describing
32
- * them.
28
+ * The constructor validates each field of `config` once and keeps the
29
+ * validated value; the object is not retained, so mutating it afterwards
30
+ * changes neither the rule's answers nor its `ruleType` and `message`.
33
31
  */
34
32
  export declare class AttrPairNotEqual implements Rule {
35
33
  readonly ruleType: string;
@@ -1 +1 @@
1
- {"version":3,"file":"AttrPairNotEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrPairNotEqual.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,MAAM,WAAW,sBAAsB;IACtC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,gBAAiB,YAAW,IAAI;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,gBAAgB;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;gBAEf,MAAM,EAAE,sBAAsB;IAW1C,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;CAO1C"}
1
+ {"version":3,"file":"AttrPairNotEqual.d.mts","sourceRoot":"","sources":["../../src/rules/AttrPairNotEqual.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,MAAM,WAAW,sBAAsB;IACtC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,gBAAiB,YAAW,IAAI;IAC5C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,gBAAgB;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAS;gBAEf,MAAM,EAAE,sBAAsB;IAW1C,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;CAO1C"}
@@ -22,11 +22,9 @@ import { requireAttrName, requireOptionalGroup } from "./_sharedValidation.mjs";
22
22
  *
23
23
  * ## Configuration is copied at construction
24
24
  *
25
- * The constructor reads each field of `config` once, validates it, and keeps
26
- * the validated value in a field of its own; the object is not retained (#255).
27
- * A caller that mutates the config afterwards changes nothing: the rule answers
28
- * from the values it validated, and `ruleType` and `message` keep describing
29
- * them.
25
+ * The constructor validates each field of `config` once and keeps the
26
+ * validated value; the object is not retained, so mutating it afterwards
27
+ * changes neither the rule's answers nor its `ruleType` and `message`.
30
28
  */
31
29
  export class AttrPairNotEqual {
32
30
  ruleType;
@@ -8,13 +8,11 @@ import type { ReadonlyAttributes, Rule } from "@o3co/auth.policy-verifier.core";
8
8
  *
9
9
  * Comparison is **exact and case-sensitive**, the same philosophy `HasScope`
10
10
  * applies to scope values and `DotNotationResourceParser` applies to resource
11
- * identifiers (#116, #117): compare what was written, never a normalized guess
12
- * at what was meant. This rule matched case-insensitively until #155, which
13
- * put permissions on the wrong side of the line those two argued —
14
- * `ResourceActionPermissionRuleCollector` builds `{resource.raw}.perm:{action}`
15
- * from the case-preserving parser, so `Project:1` and `project:1` were two
16
- * namespaces to a scope rule and one to a permission rule. One vocabulary, one
17
- * matching discipline.
11
+ * identifiers: compare what was written, never a normalized guess at what was
12
+ * meant. `ResourceActionPermissionRuleCollector` builds
13
+ * `{resource.raw}.perm:{action}` from the case-preserving parser, so
14
+ * `Project:1` and `project:1` are two namespaces to a permission rule, as they
15
+ * are to a scope rule.
18
16
  *
19
17
  * ## Wildcards
20
18
  *
@@ -24,10 +22,11 @@ import type { ReadonlyAttributes, Rule } from "@o3co/auth.policy-verifier.core";
24
22
  * no-normalization rule: a wildcard is match structure the policy author
25
23
  * **wrote into the grant**, not a rewrite of both sides behind their back. The
26
24
  * literal halves around the `*` still compare exactly and case-sensitively.
27
- * Multiple wildcards are rejected outright because the two-part split would
28
- * silently drop segments and over-grant.
25
+ * A grant with more than one `*` is not read as a pattern (only an exact
26
+ * match passes it), because the two-part split would silently drop segments
27
+ * and over-grant.
29
28
  *
30
- * The two halves must not overlap in the required permission (#180): a grant
29
+ * The two halves must not overlap in the required permission: a grant
31
30
  * of `"posts.*.read"` does not match `"posts.read"`, where the single `.`
32
31
  * would satisfy `startsWith` and `endsWith` at once. The wildcard may match
33
32
  * the empty string, but each character of the required permission counts
@@ -1 +1 @@
1
- {"version":3,"file":"HasPermission.d.mts","sourceRoot":"","sources":["../../src/rules/HasPermission.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAQ,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,aAAc,YAAW,IAAI;IAK7B,OAAO,CAAC,UAAU;IAJ9B,QAAQ,CAAC,QAAQ,gBAAgB;IACjC,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEL,UAAU,EAAE,MAAM;IAItC,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAoB1C,OAAO,CAAC,KAAK;CA0Bb"}
1
+ {"version":3,"file":"HasPermission.d.mts","sourceRoot":"","sources":["../../src/rules/HasPermission.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAQ,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,qBAAa,aAAc,YAAW,IAAI;IAK7B,OAAO,CAAC,UAAU;IAJ9B,QAAQ,CAAC,QAAQ,gBAAgB;IACjC,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEL,UAAU,EAAE,MAAM;IAItC,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAoB1C,OAAO,CAAC,KAAK;CAuBb"}
@@ -10,13 +10,11 @@ import { ATTR_PERMISSIONS, ATTR_ROLES } from "@o3co/auth.policy-verifier.core";
10
10
  *
11
11
  * Comparison is **exact and case-sensitive**, the same philosophy `HasScope`
12
12
  * applies to scope values and `DotNotationResourceParser` applies to resource
13
- * identifiers (#116, #117): compare what was written, never a normalized guess
14
- * at what was meant. This rule matched case-insensitively until #155, which
15
- * put permissions on the wrong side of the line those two argued —
16
- * `ResourceActionPermissionRuleCollector` builds `{resource.raw}.perm:{action}`
17
- * from the case-preserving parser, so `Project:1` and `project:1` were two
18
- * namespaces to a scope rule and one to a permission rule. One vocabulary, one
19
- * matching discipline.
13
+ * identifiers: compare what was written, never a normalized guess at what was
14
+ * meant. `ResourceActionPermissionRuleCollector` builds
15
+ * `{resource.raw}.perm:{action}` from the case-preserving parser, so
16
+ * `Project:1` and `project:1` are two namespaces to a permission rule, as they
17
+ * are to a scope rule.
20
18
  *
21
19
  * ## Wildcards
22
20
  *
@@ -26,10 +24,11 @@ import { ATTR_PERMISSIONS, ATTR_ROLES } from "@o3co/auth.policy-verifier.core";
26
24
  * no-normalization rule: a wildcard is match structure the policy author
27
25
  * **wrote into the grant**, not a rewrite of both sides behind their back. The
28
26
  * literal halves around the `*` still compare exactly and case-sensitively.
29
- * Multiple wildcards are rejected outright because the two-part split would
30
- * silently drop segments and over-grant.
27
+ * A grant with more than one `*` is not read as a pattern (only an exact
28
+ * match passes it), because the two-part split would silently drop segments
29
+ * and over-grant.
31
30
  *
32
- * The two halves must not overlap in the required permission (#180): a grant
31
+ * The two halves must not overlap in the required permission: a grant
33
32
  * of `"posts.*.read"` does not match `"posts.read"`, where the single `.`
34
33
  * would satisfy `startsWith` and `endsWith` at once. The wildcard may match
35
34
  * the empty string, but each character of the required permission counts
@@ -45,7 +44,7 @@ export class HasPermission {
45
44
  this.message = `User does not have required permission: ${permission}`;
46
45
  }
47
46
  verify(attrs) {
48
- // Nothing about these values is taken on the cast's word (#180): both
47
+ // Nothing about these values is taken on the cast's word: both
49
48
  // arrive from collectors, and a store-backed one can hand back anything
50
49
  // — a non-array under either key, a role whose `permissions` is missing
51
50
  // or not an array, non-string entries. Every malformed shape is ignored
@@ -57,7 +56,7 @@ export class HasPermission {
57
56
  const roles = attrs.get(ATTR_ROLES);
58
57
  const fromRoles = (Array.isArray(roles) ? roles : []).flatMap((role) => Array.isArray(role?.permissions) ? role.permissions : []);
59
58
  const all = [...(Array.isArray(direct) ? direct : []), ...fromRoles];
60
- // Non-string entries never match and never throw, as in HasScope (#116).
59
+ // Non-string entries never match and never throw, as in HasScope.
61
60
  return all.some((p) => typeof p === "string" && this.match(p, this.permission));
62
61
  }
63
62
  match(permission, required) {
@@ -66,17 +65,14 @@ export class HasPermission {
66
65
  if (permission === required)
67
66
  return true;
68
67
  if (permission.includes("*")) {
69
- // Reject any granted permission that contains more than one wildcard.
70
- // With 2+ wildcards, split("*") produces 3+ parts, but the destructuring
71
- // `const [prefix, suffix] = ...` silently drops everything after the second
72
- // segment. For example, "a*c*b" is treated as "a*c" (endsWith "c" instead of
73
- // "b"), which can grant broader access than intended. Returning false is the
74
- // safe default: deny rather than over-grant. Single-wildcard permissions
75
- // (e.g. "posts.*" or "*.read") continue to work as before.
68
+ // A grant with more than one wildcard is not read as a pattern; only
69
+ // the exact match above passes it. The two-part split below would drop
70
+ // everything after the second segment ("a*c*b" read as "a*c") and
71
+ // over-grant.
76
72
  if ((permission.match(/\*/g) ?? []).length > 1)
77
73
  return false;
78
74
  const [prefix, suffix] = permission.split("*");
79
- // The halves must not overlap in `required` (#180): "posts.*.read"
75
+ // The halves must not overlap in `required`: "posts.*.read"
80
76
  // must not match "posts.read", where the single "." satisfies both
81
77
  // startsWith and endsWith. Length is the whole check — the wildcard
82
78
  // may match the empty string, but a character may only count toward
@@ -5,10 +5,10 @@ export interface HasScopeOptions {
5
5
  * Opt in to treating a bare granted scope (one containing no `:`) as
6
6
  * `read:<scope>` in addition to its literal value. Defaults to `false`.
7
7
  *
8
- * This exists only for deployments whose issuer emits bare resource names
9
- * and that relied on the rewrite before it became opt-in. It is off by
10
- * default because the rewrite invents an action the issuer never wrote:
11
- * a token granted `project` is silently promoted to `read:project`.
8
+ * This exists only for deployments whose issuer emits bare resource names.
9
+ * It is off by default because the rewrite invents an action the issuer
10
+ * never wrote: a token granted `project` is silently promoted to
11
+ * `read:project`.
12
12
  */
13
13
  allowBareScopeRewrite?: boolean;
14
14
  }
@@ -20,21 +20,18 @@ export interface HasScopeOptions {
20
20
  * Comparison is an **exact, case-sensitive string equality** against each value
21
21
  * in `ATTR_SCOPES`. OAuth 2.0 scope values are case-sensitive opaque strings
22
22
  * (RFC 6749 §3.3), so the verifier compares what the issuer wrote rather than a
23
- * normalized form of it:
24
- *
25
- * - `read:PROJECT` does **not** satisfy a `read:project` requirement.
26
- * - `read:project:restricted` is a value in its own right. It does not satisfy
27
- * `read:project`, and `read:project` does not satisfy it. Nothing is split
28
- * off at the second `:`; a scope the issuer deliberately narrowed must not
29
- * collapse into the broader one.
23
+ * normalized form of it: `read:PROJECT` does **not** satisfy `read:project`,
24
+ * and `read:project:restricted` neither satisfies `read:project` nor is
25
+ * satisfied by it — a scope the issuer deliberately narrowed must not collapse
26
+ * into the broader one.
30
27
  *
31
28
  * ## Bare-scope rewrite
32
29
  *
33
30
  * A granted scope carrying no `:` is compared literally unless
34
31
  * `{ allowBareScopeRewrite: true }` is passed, in which case it also matches
35
- * `read:<scope>`. Even then, only a scope with **no** `:` is ever rewritten —
36
- * a value such as `project:restricted` is left alone, because which of its
37
- * segments is the action is unknowable and guessing would over-grant.
32
+ * `read:<scope>`. A scope containing `:`, such as `project:restricted`, is
33
+ * never rewritten: which of its segments is the action is unknowable, and
34
+ * guessing would over-grant.
38
35
  *
39
36
  * Non-string entries in `ATTR_SCOPES` never match and never throw: the map is
40
37
  * untyped, and a malformed value must produce a denial, not a crash.
@@ -1 +1 @@
1
- {"version":3,"file":"HasScope.d.mts","sourceRoot":"","sources":["../../src/rules/HasScope.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC/B;;;;;;;;OAQG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,QAAS,YAAW,IAAI;IAQnC,OAAO,CAAC,KAAK;IAPd,QAAQ,CAAC,QAAQ,WAAW;IAC5B,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;gBAGvC,KAAK,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,eAAe;IAM1B,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAM1C,OAAO,CAAC,UAAU;CAYlB"}
1
+ {"version":3,"file":"HasScope.d.mts","sourceRoot":"","sources":["../../src/rules/HasScope.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAGhF,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC/B;;;;;;;;OAQG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,QAAS,YAAW,IAAI;IAQnC,OAAO,CAAC,KAAK;IAPd,QAAQ,CAAC,QAAQ,WAAW;IAC5B,QAAQ,CAAC,IAAI,mBAAmB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAU;gBAGvC,KAAK,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,eAAe;IAM1B,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;IAM1C,OAAO,CAAC,UAAU;CAYlB"}
@@ -9,21 +9,18 @@ import { ATTR_SCOPES } from "@o3co/auth.policy-verifier.core";
9
9
  * Comparison is an **exact, case-sensitive string equality** against each value
10
10
  * in `ATTR_SCOPES`. OAuth 2.0 scope values are case-sensitive opaque strings
11
11
  * (RFC 6749 §3.3), so the verifier compares what the issuer wrote rather than a
12
- * normalized form of it:
13
- *
14
- * - `read:PROJECT` does **not** satisfy a `read:project` requirement.
15
- * - `read:project:restricted` is a value in its own right. It does not satisfy
16
- * `read:project`, and `read:project` does not satisfy it. Nothing is split
17
- * off at the second `:`; a scope the issuer deliberately narrowed must not
18
- * collapse into the broader one.
12
+ * normalized form of it: `read:PROJECT` does **not** satisfy `read:project`,
13
+ * and `read:project:restricted` neither satisfies `read:project` nor is
14
+ * satisfied by it — a scope the issuer deliberately narrowed must not collapse
15
+ * into the broader one.
19
16
  *
20
17
  * ## Bare-scope rewrite
21
18
  *
22
19
  * A granted scope carrying no `:` is compared literally unless
23
20
  * `{ allowBareScopeRewrite: true }` is passed, in which case it also matches
24
- * `read:<scope>`. Even then, only a scope with **no** `:` is ever rewritten —
25
- * a value such as `project:restricted` is left alone, because which of its
26
- * segments is the action is unknowable and guessing would over-grant.
21
+ * `read:<scope>`. A scope containing `:`, such as `project:restricted`, is
22
+ * never rewritten: which of its segments is the action is unknowable, and
23
+ * guessing would over-grant.
27
24
  *
28
25
  * Non-string entries in `ATTR_SCOPES` never match and never throw: the map is
29
26
  * untyped, and a malformed value must produce a denial, not a crash.
@@ -50,7 +47,7 @@ export class HasScope {
50
47
  return true;
51
48
  // The rewrite applies to bare scopes only. A granted scope that already
52
49
  // contains ":" is never re-interpreted: splitting it to guess an action
53
- // is what let "read:project:restricted" pass as "read:project".
50
+ // would let "read:project:restricted" pass as "read:project".
54
51
  if (this.allowBareScopeRewrite && !granted.includes(":")) {
55
52
  return `read:${granted}` === required;
56
53
  }
@@ -0,0 +1,22 @@
1
+ import type { ReadonlyAttributes, Rule } from "@o3co/auth.policy-verifier.core";
2
+ /**
3
+ * Rule that passes when an entry of `ATTR_DELEGATION_RANGE` contains the
4
+ * requested path, by the claims contract's containment rule (`rangeContains`).
5
+ * A range that is absent, not a list, or holds no string that contains the
6
+ * path fails, and so does a requested path outside the grammar, or none at
7
+ * all — `null`, a request whose action is not one action of the grammar
8
+ * (`requestedRangePath`). The rule restricts: it narrows what the policies
9
+ * allow and is no reason to allow, so a request no policy rule applies to goes
10
+ * to `onEmptyRuleSet` and is never allowed by the range alone (under
11
+ * `"allow"` it is still held to it).
12
+ */
13
+ export declare class WithinDelegationRange implements Rule {
14
+ private readonly path;
15
+ readonly ruleType = "delegation_range";
16
+ readonly code = "outside_delegation_range";
17
+ readonly restricts = true;
18
+ readonly message: string;
19
+ constructor(path: string | null);
20
+ verify(attrs: ReadonlyAttributes): boolean;
21
+ }
22
+ //# sourceMappingURL=WithinDelegationRange.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WithinDelegationRange.d.mts","sourceRoot":"","sources":["../../src/rules/WithinDelegationRange.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AAIhF;;;;;;;;;;GAUG;AACH,qBAAa,qBAAsB,YAAW,IAAI;IAMrC,OAAO,CAAC,QAAQ,CAAC,IAAI;IALjC,QAAQ,CAAC,QAAQ,sBAAsB;IACvC,QAAQ,CAAC,IAAI,8BAA8B;IAC3C,QAAQ,CAAC,SAAS,QAAQ;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEI,IAAI,EAAE,MAAM,GAAG,IAAI;IAOhD,MAAM,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO;CAO1C"}
@@ -0,0 +1,38 @@
1
+ // SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { rangeContains } from "../delegation/range.mjs";
4
+ import { ATTR_DELEGATION_RANGE } from "../keys.mjs";
5
+ /**
6
+ * Rule that passes when an entry of `ATTR_DELEGATION_RANGE` contains the
7
+ * requested path, by the claims contract's containment rule (`rangeContains`).
8
+ * A range that is absent, not a list, or holds no string that contains the
9
+ * path fails, and so does a requested path outside the grammar, or none at
10
+ * all — `null`, a request whose action is not one action of the grammar
11
+ * (`requestedRangePath`). The rule restricts: it narrows what the policies
12
+ * allow and is no reason to allow, so a request no policy rule applies to goes
13
+ * to `onEmptyRuleSet` and is never allowed by the range alone (under
14
+ * `"allow"` it is still held to it).
15
+ */
16
+ export class WithinDelegationRange {
17
+ path;
18
+ ruleType = "delegation_range";
19
+ code = "outside_delegation_range";
20
+ restricts = true;
21
+ message;
22
+ constructor(path) {
23
+ this.path = path;
24
+ this.message =
25
+ path === null
26
+ ? "Request is outside the token's delegation range: its action is not one action of the range grammar"
27
+ : `Request is outside the token's delegation range: ${path}`;
28
+ }
29
+ verify(attrs) {
30
+ const path = this.path;
31
+ if (path === null)
32
+ return false;
33
+ const range = attrs.get(ATTR_DELEGATION_RANGE);
34
+ if (!Array.isArray(range))
35
+ return false;
36
+ return range.some((entry) => typeof entry === "string" && rangeContains(entry, path));
37
+ }
38
+ }
@@ -4,11 +4,9 @@ export type CompareOp = "lt" | "le" | "gt" | "ge";
4
4
  export declare const COMPARE_OPS: readonly CompareOp[];
5
5
  /**
6
6
  * Validates that a config field used as an attribute name is a non-empty string
7
- * that does not contain the `:` separator character used in derived `ruleType`
8
- * strings. Allowing `:` would let distinct configs collide on the same
9
- * `ruleType` (e.g. `(a="x:y", b="z")` and `(a="x", b="y:z")` both produce
10
- * `attr_pair_equal:x:y:z`), which the evaluator would silently OR together
11
- * and weaken authorization.
7
+ * without `:`, the separator of derived `ruleType` strings. With `:` allowed,
8
+ * `(a="x:y", b="z")` and `(a="x", b="y:z")` would both produce
9
+ * `attr_pair_equal:x:y:z`, which the evaluator would silently OR together.
12
10
  * @param className - The rule class name, used in error messages.
13
11
  * @param fieldName - The config field name ("a" or "b"), used in error messages.
14
12
  * @param value - The raw value to validate.
@@ -18,10 +16,9 @@ export declare const COMPARE_OPS: readonly CompareOp[];
18
16
  export declare function requireAttrName(className: string, fieldName: "a" | "b", value: unknown): string;
19
17
  /**
20
18
  * Validates that a config field used as a literal comparison value is a
21
- * string, number, or boolean (i.e. a LiteralValue). NaN is rejected because
22
- * its comparison semantics (`NaN !== NaN` is always true, `NaN === NaN` is
23
- * always false) would silently invert Equal/NotEqual rules — a NotEqual(v=NaN)
24
- * would always pass against any numeric attribute, weakening authorization.
19
+ * string, number, or boolean (i.e. a LiteralValue). NaN is rejected: `x !== NaN`
20
+ * is always true and `x === NaN` always false, so a NotEqual(v=NaN) would pass
21
+ * against any numeric attribute and an Equal(v=NaN) against none.
25
22
  * @param className - The rule class name, used in error messages.
26
23
  * @param fieldName - The config field name ("v"), used in error messages.
27
24
  * @param value - The raw value to validate.
@@ -67,20 +64,16 @@ export declare function requireCompareOp(className: string, value: unknown): Com
67
64
  export declare function requireOptionalGroup(className: string, value: unknown): string | undefined;
68
65
  /**
69
66
  * Computes a stable cache/dedup key for an array of LiteralValues.
70
- * Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is a 16-hex-character
71
- * FNV-1a 64-bit hash over a canonical serialization of `values`. The canonical
72
- * form first deduplicates `values` (to mirror the Set-based semantics of
73
- * AttrLiteralIn / AttrLiteralNotIn — duplicates do not change the rule's
74
- * behavior, so they must not change its `ruleType`), then applies
67
+ * Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is `fnv1a64` over a
68
+ * canonical serialization of `values`. The canonical form first deduplicates
69
+ * `values` (AttrLiteralIn / AttrLiteralNotIn test membership through a `Set`,
70
+ * so duplicates must not change the `ruleType`), then applies
75
71
  * `JSON.stringify(String(v))` to each remaining element before sorting and
76
72
  * joining with `,`. The per-element quoting prevents separator collisions
77
73
  * across different arrays (e.g. `["a,b", "c"]` vs `["a", "b,c"]`).
78
- * The `{count}` segment reflects the post-dedup element count.
74
+ * `{count}` is the post-dedup element count.
79
75
  * Caller must guarantee values is a non-empty homogeneous LiteralValue[].
80
- *
81
- * The hash is non-cryptographic by design, but uses 64-bit output to keep
82
- * the birthday-collision bound (~2^32) far above any realistic policy size
83
- * and to resist deliberate collision construction.
76
+ * The hash is non-cryptographic by design (see `fnv1a64`).
84
77
  */
85
78
  export declare function computeValuesKey(values: LiteralValue[]): string;
86
79
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"_sharedValidation.d.mts","sourceRoot":"","sources":["../../src/rules/_sharedValidation.mts"],"names":[],"mappings":"AA4BA,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOzC;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAarD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAA6B,CAAC;AAE1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAe/F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,OAAO,GACZ,YAAY,CAad;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAQvF;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,CAkChG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAU7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAe1F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAQ/D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAWxF"}
1
+ {"version":3,"file":"_sharedValidation.d.mts","sourceRoot":"","sources":["../../src/rules/_sharedValidation.mts"],"names":[],"mappings":"AA2BA,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOzC;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAarD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAElD,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAA6B,CAAC;AAE1E;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAe/F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,GAAG,EACd,KAAK,EAAE,OAAO,GACZ,YAAY,CAad;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAQvF;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,YAAY,EAAE,CAkChG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,CAU7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAe1F;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,MAAM,CAQ/D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAWxF"}
@@ -9,12 +9,11 @@
9
9
  * lowercase hex characters, zero-padded.
10
10
  *
11
11
  * Used by `computeValuesKey` to build a stable grouping suffix for `ruleType`.
12
- * The 64-bit output gives ~2^32 birthday-collision bound, which is
13
- * effectively non-colliding for any realistic policy size and resists
14
- * deliberate collision construction by a hostile policy author or supply
15
- * chain. (A 32-bit variant was rejected during review because random short
16
- * strings can be made to collide within seconds, weakening evaluator
17
- * grouping when two distinct rules on the same attribute share a `ruleType`.)
12
+ * The 64-bit output puts the birthday-collision bound at ~2^32, far above any
13
+ * realistic policy size, so distinct value sets do not collide by accident.
14
+ * It is not collision-resistant: a birthday search of about 2^32 hashes,
15
+ * within reach of commodity hardware, finds one on purpose, and two distinct
16
+ * rules on the same attribute that share a `ruleType` are OR-combined.
18
17
  *
19
18
  * Iterates UTF-16 code units via `charCodeAt`. BigInt is used because
20
19
  * 64-bit multiplication overflows IEEE-754 doubles; `Math.imul` only
@@ -47,11 +46,9 @@ function describeType(value) {
47
46
  export const COMPARE_OPS = ["lt", "le", "gt", "ge"];
48
47
  /**
49
48
  * Validates that a config field used as an attribute name is a non-empty string
50
- * that does not contain the `:` separator character used in derived `ruleType`
51
- * strings. Allowing `:` would let distinct configs collide on the same
52
- * `ruleType` (e.g. `(a="x:y", b="z")` and `(a="x", b="y:z")` both produce
53
- * `attr_pair_equal:x:y:z`), which the evaluator would silently OR together
54
- * and weaken authorization.
49
+ * without `:`, the separator of derived `ruleType` strings. With `:` allowed,
50
+ * `(a="x:y", b="z")` and `(a="x", b="y:z")` would both produce
51
+ * `attr_pair_equal:x:y:z`, which the evaluator would silently OR together.
55
52
  * @param className - The rule class name, used in error messages.
56
53
  * @param fieldName - The config field name ("a" or "b"), used in error messages.
57
54
  * @param value - The raw value to validate.
@@ -72,10 +69,9 @@ export function requireAttrName(className, fieldName, value) {
72
69
  }
73
70
  /**
74
71
  * Validates that a config field used as a literal comparison value is a
75
- * string, number, or boolean (i.e. a LiteralValue). NaN is rejected because
76
- * its comparison semantics (`NaN !== NaN` is always true, `NaN === NaN` is
77
- * always false) would silently invert Equal/NotEqual rules — a NotEqual(v=NaN)
78
- * would always pass against any numeric attribute, weakening authorization.
72
+ * string, number, or boolean (i.e. a LiteralValue). NaN is rejected: `x !== NaN`
73
+ * is always true and `x === NaN` always false, so a NotEqual(v=NaN) would pass
74
+ * against any numeric attribute and an Equal(v=NaN) against none.
79
75
  * @param className - The rule class name, used in error messages.
80
76
  * @param fieldName - The config field name ("v"), used in error messages.
81
77
  * @param value - The raw value to validate.
@@ -185,20 +181,16 @@ export function requireOptionalGroup(className, value) {
185
181
  }
186
182
  /**
187
183
  * Computes a stable cache/dedup key for an array of LiteralValues.
188
- * Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is a 16-hex-character
189
- * FNV-1a 64-bit hash over a canonical serialization of `values`. The canonical
190
- * form first deduplicates `values` (to mirror the Set-based semantics of
191
- * AttrLiteralIn / AttrLiteralNotIn — duplicates do not change the rule's
192
- * behavior, so they must not change its `ruleType`), then applies
184
+ * Format: `{type}:{count}:{hashPrefix}` — `hashPrefix` is `fnv1a64` over a
185
+ * canonical serialization of `values`. The canonical form first deduplicates
186
+ * `values` (AttrLiteralIn / AttrLiteralNotIn test membership through a `Set`,
187
+ * so duplicates must not change the `ruleType`), then applies
193
188
  * `JSON.stringify(String(v))` to each remaining element before sorting and
194
189
  * joining with `,`. The per-element quoting prevents separator collisions
195
190
  * across different arrays (e.g. `["a,b", "c"]` vs `["a", "b,c"]`).
196
- * The `{count}` segment reflects the post-dedup element count.
191
+ * `{count}` is the post-dedup element count.
197
192
  * Caller must guarantee values is a non-empty homogeneous LiteralValue[].
198
- *
199
- * The hash is non-cryptographic by design, but uses 64-bit output to keep
200
- * the birthday-collision bound (~2^32) far above any realistic policy size
201
- * and to resist deliberate collision construction.
193
+ * The hash is non-cryptographic by design (see `fnv1a64`).
202
194
  */
203
195
  export function computeValuesKey(values) {
204
196
  const elementType = typeof values[0];
@@ -0,0 +1,36 @@
1
+ import type { CollectorContext, Rule, RuleCollector } from "@o3co/auth.policy-verifier.core";
2
+ /** Config entry accepted by `DelegationRangeRuleCollector`. */
3
+ export interface DelegationRangeRuleCollectorConfig {
4
+ /**
5
+ * The `authorization_details` type the delegation range is carried under.
6
+ * Required — the same value `DelegationRangeCollector` is given.
7
+ */
8
+ type: string;
9
+ /** The claim the entries are read from. Defaults to `authorization_details`. */
10
+ claim?: string;
11
+ }
12
+ /**
13
+ * Emits one `WithinDelegationRange` rule for the requested path — the raw
14
+ * resource, then the action, joined by `.` (`requestedRangePath`, which
15
+ * refuses an action that is not one action of the grammar, so the rule
16
+ * fails) — when the token carries a range (`entriesOfType`: an entry of the
17
+ * configured type, or the claim in another shape), and none otherwise.
18
+ * The rule is a group of its own and restricts, so it is decided together
19
+ * with the policies and never instead of them: a delegated token is allowed
20
+ * only what both allow, one no policy rule applies to goes to
21
+ * `onEmptyRuleSet`, and a token without a range is decided as it would be
22
+ * without this collector.
23
+ *
24
+ * The range itself is read by `DelegationRangeCollector`, which must be
25
+ * configured with the same `type` and `claim`; nothing checks that they agree.
26
+ * Where only this collector finds the token's entries, the rule finds no
27
+ * range and the request is denied; where only that one does, no rule is
28
+ * emitted and the range is not enforced.
29
+ */
30
+ export declare class DelegationRangeRuleCollector implements RuleCollector {
31
+ private readonly type;
32
+ private readonly claim;
33
+ constructor(config: DelegationRangeRuleCollectorConfig);
34
+ collect(context: CollectorContext): Promise<Rule[]>;
35
+ }
36
+ //# sourceMappingURL=DelegationRangeRuleCollector.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DelegationRangeRuleCollector.d.mts","sourceRoot":"","sources":["../../../src/rules/collectors/DelegationRangeRuleCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAU7F,+DAA+D;AAC/D,MAAM,WAAW,kCAAkC;IAClD;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,4BAA6B,YAAW,aAAa;IACjE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,MAAM,EAAE,kCAAkC;IAShD,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;CAIzD"}
@@ -0,0 +1,36 @@
1
+ // SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { resolveClaimName } from "../../collectors/_claims.mjs";
4
+ import { DEFAULT_AUTHORIZATION_DETAILS_CLAIM, entriesOfType, requestedRangePath, resolveRangeType, } from "../../delegation/range.mjs";
5
+ import { WithinDelegationRange } from "../WithinDelegationRange.mjs";
6
+ /**
7
+ * Emits one `WithinDelegationRange` rule for the requested path — the raw
8
+ * resource, then the action, joined by `.` (`requestedRangePath`, which
9
+ * refuses an action that is not one action of the grammar, so the rule
10
+ * fails) — when the token carries a range (`entriesOfType`: an entry of the
11
+ * configured type, or the claim in another shape), and none otherwise.
12
+ * The rule is a group of its own and restricts, so it is decided together
13
+ * with the policies and never instead of them: a delegated token is allowed
14
+ * only what both allow, one no policy rule applies to goes to
15
+ * `onEmptyRuleSet`, and a token without a range is decided as it would be
16
+ * without this collector.
17
+ *
18
+ * The range itself is read by `DelegationRangeCollector`, which must be
19
+ * configured with the same `type` and `claim`; nothing checks that they agree.
20
+ * Where only this collector finds the token's entries, the rule finds no
21
+ * range and the request is denied; where only that one does, no rule is
22
+ * emitted and the range is not enforced.
23
+ */
24
+ export class DelegationRangeRuleCollector {
25
+ type;
26
+ claim;
27
+ constructor(config) {
28
+ this.type = resolveRangeType("DelegationRangeRuleCollector", config?.type);
29
+ this.claim = resolveClaimName("DelegationRangeRuleCollector", config?.claim, DEFAULT_AUTHORIZATION_DETAILS_CLAIM);
30
+ }
31
+ async collect(context) {
32
+ if (entriesOfType(context.subject[this.claim], this.type) === null)
33
+ return [];
34
+ return [new WithinDelegationRange(requestedRangePath(context.resource.raw, context.action))];
35
+ }
36
+ }