@o3co/auth.policy-verifier.builtins 0.15.0 → 0.16.1

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
@@ -14,68 +14,30 @@ export type RequestContextAttributeCollectorConfig = AttributeMappingCollectorCo
14
14
  /**
15
15
  * Promotes declared fields of `CollectorContext.requestContext` into attributes.
16
16
  *
17
- * `requestContext` is the free-form container the transport fills in, and until
18
- * now nothing built-in consumed it: any environment or relationship attribute
19
- * meant writing a bespoke collector, even for the common case of "read this
20
- * field, check its type, write it under this key".
21
- *
22
- * Core deliberately assumes no shape for `requestContext` and keeps its `ATTR_*`
23
- * vocabulary to OAuth/OIDC/RBAC standards, so this collector invents no
24
- * vocabulary either: the operator declares which fields to read and what to call
25
- * them, and nothing undeclared is promoted. That declaration is also the trust
26
- * boundary — `requestContext` is caller-supplied and unvalidated, so a field the
27
- * config did not name cannot reach a rule, and one whose value does not match
28
- * its declared type is dropped rather than passed along.
29
- *
30
- * ## Reserved vocabulary is not a valid destination
31
- *
32
- * A mapping's `to` may not name a key any package has reserved. Core reserves
33
- * five — `scopes`, `permissions`, `roles`, `userId`, `clientId` — and every
34
- * package that owns attribute vocabulary reserves its own at module load:
35
- * `packages/cedar` reserves `requestAction`, `requestResourceType`,
36
- * `requestResourceId` and `requestResourceRaw`. The check is a lookup in core's
37
- * registry, not a list this collector or core keeps by hand, so a package core
38
- * cannot see is covered by importing it — which a composition naming that
39
- * package's collectors has already done.
40
- *
41
- * Those keys are what the engine decides from, and under the default server
42
- * `scopes` / `userId` / `clientId` are read out of the signature-verified
43
- * token; `requestContext` is the request body, which anyone holding a valid
44
- * token fills in as they like. The two carry different trust and must not share
45
- * a bucket.
46
- *
47
- * The reason it is refused outright rather than left to the operator is what
48
- * `AttributePipeline` does with two collectors writing one key: array-valued
49
- * entries **union**. So `{ from = "groups", to = "scopes" }` does not replace
50
- * the token's scopes and lose an argument with `PayloadScopeCollector` — it
51
- * quietly *adds to* them, and the request that arrives with
52
- * `context.groups = ["admin:write"]` is authorized for a scope its token never
53
- * carried. Nothing in the decision, the logs or the metrics distinguishes that
54
- * from an issuer that granted it.
55
- *
56
- * A scalar key is not safe either, in two different ways. Two writers that
57
- * disagree throw `AttributeConflictError`, which denies — fail-closed, but an
58
- * unannounced outage rather than a refusal. And where the owning collector
59
- * writes its key only *sometimes*, there is no second writer at all: cedar's
60
- * `RequestFactsCollector` omits `requestResourceId` for an id-less resource
61
- * such as `"document"`, so `{ from = "rid", to = "requestResourceId" }` would
62
- * land unopposed and the Cedar resource entity would be built out of the
63
- * caller's own request body.
64
- *
65
- * This is reachable only through operator configuration, which is why it is a
66
- * configuration error rather than a vulnerability. It is refused at
67
- * construction — boot, not first request — so a deployment that wrote it never
68
- * serves a decision. Every other destination keeps working: the *field* may be
69
- * called anything (`{ from = "scopes", to = "requestedScopes" }` is fine), only
70
- * the attribute key is reserved.
71
- *
72
- * ```hocon
73
- * { collector = "RequestContextAttributeCollector"
74
- * attributes = [
75
- * { from = "tenant.id", to = "tenantId" }
76
- * { from = "groups", type = "string[]" }
77
- * ] }
78
- * ```
17
+ * Core assumes no shape for `requestContext` and keeps its `ATTR_*` vocabulary
18
+ * to OAuth/OIDC/RBAC standards, so this collector invents no vocabulary either:
19
+ * the operator declares which fields to read and what to call them, and nothing
20
+ * undeclared is promoted. That declaration is the trust boundary —
21
+ * `requestContext` is caller-supplied and unvalidated, so a field the config did
22
+ * not name cannot reach a rule, and one whose value does not match its declared
23
+ * type is dropped rather than passed along.
24
+ *
25
+ * A mapping's `to` may not name a key any package has reserved: core's five —
26
+ * `scopes`, `permissions`, `roles`, `userId`, `clientId` — and whatever other
27
+ * packages reserved before this collector is constructed (cedar's four
28
+ * `request*` keys, once it is imported). The check is a lookup in core's
29
+ * registry at construction; a later reservation is not seen. Those keys are
30
+ * what the engine decides from, written by the deployment, and the request
31
+ * body must not join them: `AttributePipeline` unions array-valued attributes,
32
+ * so `{ from = "groups", to = "scopes" }` would quietly add to the token's
33
+ * scopes; a scalar key written with a different value throws
34
+ * `AttributeConflictError` and denies, and where its owner writes it only
35
+ * sometimes (cedar's `requestResourceId` for an id-less resource) the
36
+ * caller's value stands unopposed. Such a mapping is a configuration error,
37
+ * refused at construction so the deployment fails at boot. Only the attribute
38
+ * key is reserved: `{ from = "scopes", to = "requestedScopes" }` is fine.
39
+ *
40
+ * Configuration, with an example: the package README, RequestContextAttributeCollector.
79
41
  */
80
42
  export declare class RequestContextAttributeCollector implements AttributeCollector {
81
43
  private readonly mappings;
@@ -1 +1 @@
1
- {"version":3,"file":"RequestContextAttributeCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/RequestContextAttributeCollector.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACX,kBAAkB,EAElB,UAAU,EACV,gBAAgB,EAChB,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EAIzB,MAAM,yBAAyB,CAAC;AAEjC,yDAAyD;AACzD,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,CAAC;AAE9D,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAAG,+BAA+B,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,qBAAa,gCAAiC,YAAW,kBAAkB;IAC1E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;gBAE1C,MAAM,EAAE,sCAAsC;IAOpD,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAQ7D"}
1
+ {"version":3,"file":"RequestContextAttributeCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/RequestContextAttributeCollector.mts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACX,kBAAkB,EAElB,UAAU,EACV,gBAAgB,EAChB,MAAM,iCAAiC,CAAC;AAMzC,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACpC,KAAK,oBAAoB,EAIzB,MAAM,yBAAyB,CAAC;AAEjC,yDAAyD;AACzD,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,8BAA8B,GAAG,gBAAgB,CAAC;AAE9D,mEAAmE;AACnE,MAAM,MAAM,sCAAsC,GAAG,+BAA+B,CAAC;AAErF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,gCAAiC,YAAW,kBAAkB;IAC1E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;gBAE1C,MAAM,EAAE,sCAAsC;IAOpD,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAQ7D"}
@@ -5,75 +5,37 @@ import { parseAttributeMappings, promoteMappings, } from "./_attributeMapping.mj
5
5
  /**
6
6
  * Promotes declared fields of `CollectorContext.requestContext` into attributes.
7
7
  *
8
- * `requestContext` is the free-form container the transport fills in, and until
9
- * now nothing built-in consumed it: any environment or relationship attribute
10
- * meant writing a bespoke collector, even for the common case of "read this
11
- * field, check its type, write it under this key".
8
+ * Core assumes no shape for `requestContext` and keeps its `ATTR_*` vocabulary
9
+ * to OAuth/OIDC/RBAC standards, so this collector invents no vocabulary either:
10
+ * the operator declares which fields to read and what to call them, and nothing
11
+ * undeclared is promoted. That declaration is the trust boundary —
12
+ * `requestContext` is caller-supplied and unvalidated, so a field the config did
13
+ * not name cannot reach a rule, and one whose value does not match its declared
14
+ * type is dropped rather than passed along.
12
15
  *
13
- * Core deliberately assumes no shape for `requestContext` and keeps its `ATTR_*`
14
- * vocabulary to OAuth/OIDC/RBAC standards, so this collector invents no
15
- * vocabulary either: the operator declares which fields to read and what to call
16
- * them, and nothing undeclared is promoted. That declaration is also the trust
17
- * boundary — `requestContext` is caller-supplied and unvalidated, so a field the
18
- * config did not name cannot reach a rule, and one whose value does not match
19
- * its declared type is dropped rather than passed along.
16
+ * A mapping's `to` may not name a key any package has reserved: core's five —
17
+ * `scopes`, `permissions`, `roles`, `userId`, `clientId` — and whatever other
18
+ * packages reserved before this collector is constructed (cedar's four
19
+ * `request*` keys, once it is imported). The check is a lookup in core's
20
+ * registry at construction; a later reservation is not seen. Those keys are
21
+ * what the engine decides from, written by the deployment, and the request
22
+ * body must not join them: `AttributePipeline` unions array-valued attributes,
23
+ * so `{ from = "groups", to = "scopes" }` would quietly add to the token's
24
+ * scopes; a scalar key written with a different value throws
25
+ * `AttributeConflictError` and denies, and where its owner writes it only
26
+ * sometimes (cedar's `requestResourceId` for an id-less resource) the
27
+ * caller's value stands unopposed. Such a mapping is a configuration error,
28
+ * refused at construction so the deployment fails at boot. Only the attribute
29
+ * key is reserved: `{ from = "scopes", to = "requestedScopes" }` is fine.
20
30
  *
21
- * ## Reserved vocabulary is not a valid destination
22
- *
23
- * A mapping's `to` may not name a key any package has reserved. Core reserves
24
- * five — `scopes`, `permissions`, `roles`, `userId`, `clientId` — and every
25
- * package that owns attribute vocabulary reserves its own at module load:
26
- * `packages/cedar` reserves `requestAction`, `requestResourceType`,
27
- * `requestResourceId` and `requestResourceRaw`. The check is a lookup in core's
28
- * registry, not a list this collector or core keeps by hand, so a package core
29
- * cannot see is covered by importing it — which a composition naming that
30
- * package's collectors has already done.
31
- *
32
- * Those keys are what the engine decides from, and under the default server
33
- * `scopes` / `userId` / `clientId` are read out of the signature-verified
34
- * token; `requestContext` is the request body, which anyone holding a valid
35
- * token fills in as they like. The two carry different trust and must not share
36
- * a bucket.
37
- *
38
- * The reason it is refused outright rather than left to the operator is what
39
- * `AttributePipeline` does with two collectors writing one key: array-valued
40
- * entries **union**. So `{ from = "groups", to = "scopes" }` does not replace
41
- * the token's scopes and lose an argument with `PayloadScopeCollector` — it
42
- * quietly *adds to* them, and the request that arrives with
43
- * `context.groups = ["admin:write"]` is authorized for a scope its token never
44
- * carried. Nothing in the decision, the logs or the metrics distinguishes that
45
- * from an issuer that granted it.
46
- *
47
- * A scalar key is not safe either, in two different ways. Two writers that
48
- * disagree throw `AttributeConflictError`, which denies — fail-closed, but an
49
- * unannounced outage rather than a refusal. And where the owning collector
50
- * writes its key only *sometimes*, there is no second writer at all: cedar's
51
- * `RequestFactsCollector` omits `requestResourceId` for an id-less resource
52
- * such as `"document"`, so `{ from = "rid", to = "requestResourceId" }` would
53
- * land unopposed and the Cedar resource entity would be built out of the
54
- * caller's own request body.
55
- *
56
- * This is reachable only through operator configuration, which is why it is a
57
- * configuration error rather than a vulnerability. It is refused at
58
- * construction — boot, not first request — so a deployment that wrote it never
59
- * serves a decision. Every other destination keeps working: the *field* may be
60
- * called anything (`{ from = "scopes", to = "requestedScopes" }` is fine), only
61
- * the attribute key is reserved.
62
- *
63
- * ```hocon
64
- * { collector = "RequestContextAttributeCollector"
65
- * attributes = [
66
- * { from = "tenant.id", to = "tenantId" }
67
- * { from = "groups", type = "string[]" }
68
- * ] }
69
- * ```
31
+ * Configuration, with an example: the package README, RequestContextAttributeCollector.
70
32
  */
71
33
  export class RequestContextAttributeCollector {
72
34
  mappings;
73
35
  constructor(config) {
74
36
  // Every reserved destination is refused here — the source is the caller's
75
37
  // body. The declaration parsing is shared with the collector that reads
76
- // verified claims (#219); the policy is this collector's own.
38
+ // verified claims; the policy is this collector's own.
77
39
  this.mappings = parseAttributeMappings("RequestContextAttributeCollector", config, refusal);
78
40
  }
79
41
  async collect(context) {
@@ -87,17 +49,13 @@ export class RequestContextAttributeCollector {
87
49
  }
88
50
  }
89
51
  /**
90
- * Why this mapping is refused, in the words of whoever owns the key.
91
- *
92
- * Core's five get the account of the trust boundary they sit on. Another
93
- * package's keys get its name and its own stated reason instead: asserting they
94
- * are "the core vocabulary" would be false, and the operator's next move is to
95
- * go read that package's docs, so the message says which one.
52
+ * Why this mapping is refused, in the words of whoever owns the key. Core's
53
+ * five get the trust boundary they sit on; another package's keys get its name
54
+ * and its own stated reason, so the operator knows whose docs to read.
96
55
  *
97
- * The suggested rename is drawn from core's registry rather than spelled
98
- * `request<Key>` unconditionally, because `request*` is a real package's
99
- * namespace (cedar's) and advice that lands there would be refused by this same
100
- * guard.
56
+ * The suggested rename is drawn from core's registry rather than always
57
+ * `request<Key>`: `request*` is cedar's namespace, and advice landing there
58
+ * would be refused by this same guard.
101
59
  */
102
60
  function refusal(index, reservation) {
103
61
  const { key, owner, reason } = reservation;
@@ -111,8 +69,9 @@ function refusal(index, reservation) {
111
69
  : `RequestContextAttributeCollector: attributes[${index}] maps onto the reserved attribute "${key}", ` +
112
70
  `which belongs to ${owner}${reason === undefined ? "" : ` — ${reason}`}. ` +
113
71
  "That package writes the key from what the deployment established, while requestContext is " +
114
- "caller-supplied: where both write it the values collide and every such request is denied, and " +
115
- "where that package writes nothing for a given request the caller's value stands unopposed as " +
116
- "the one the deployment was supposed to supply. ";
72
+ "caller-supplied: where both write a scalar key the values collide and every such request is " +
73
+ "denied, where both write a list key the caller's entries join the package's, and where that " +
74
+ "package writes nothing for a given request the caller's value stands unopposed as the one the " +
75
+ "deployment was supposed to supply. ";
117
76
  return `${head}Promote the field under a key of your own (for example "${suggestUnreservedAttributeKey(key)}").`;
118
77
  }
@@ -4,19 +4,17 @@ import type { AttributeCollector, Attributes, CollectorContext } from "@o3co/aut
4
4
  * `ATTR_PERMISSIONS`, independent of the JWT payload. Useful for
5
5
  * environments where permissions are static per deployment.
6
6
  *
7
- * The list is copied at construction (#255): a caller that mutates the config
8
- * or its array afterwards changes nothing this collector emits.
7
+ * The list is copied at construction: a caller that mutates the config or its
8
+ * array afterwards changes nothing this collector emits.
9
9
  *
10
- * Only an array is accepted (#264). Anything else — a missing field, `null`, a
10
+ * Only an array is accepted. Anything else — a missing field, `null`, a
11
11
  * number, an object, a string — is refused with a `TypeError` naming the
12
12
  * collector and the field, so a misconfigured deployment fails at boot. A
13
- * string is the case that matters: it is iterable, so copying it would split
14
- * `"posts.*"` into single characters, and `HasPermission` honours a lone `*`
15
- * as grant-all.
13
+ * copied string would split `"posts.*"` into single characters, and
14
+ * `HasPermission` honours a lone `*` as grant-all.
16
15
  *
17
- * The entries are not checked, and a non-string one is copied as it is. It is
18
- * inert: `HasPermission` skips every permission that is not a string, so such
19
- * an entry matches no requirement and grants nothing.
16
+ * Entries are not checked; a non-string one is copied as it is and is inert,
17
+ * since `HasPermission` skips every permission that is not a string.
20
18
  */
21
19
  export declare class StaticPermissionCollector implements AttributeCollector {
22
20
  private readonly permissions;
@@ -1 +1 @@
1
- {"version":3,"file":"StaticPermissionCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/StaticPermissionCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,MAAM,iCAAiC,CAAC;AAGzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;gBAEpC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE;IAUvC,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAG9D"}
1
+ {"version":3,"file":"StaticPermissionCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/StaticPermissionCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,MAAM,iCAAiC,CAAC;AAGzC;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IACnE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;gBAEpC,MAAM,EAAE;QAAE,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE;IAUvC,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAG9D"}
@@ -6,19 +6,17 @@ import { ATTR_PERMISSIONS } from "@o3co/auth.policy-verifier.core";
6
6
  * `ATTR_PERMISSIONS`, independent of the JWT payload. Useful for
7
7
  * environments where permissions are static per deployment.
8
8
  *
9
- * The list is copied at construction (#255): a caller that mutates the config
10
- * or its array afterwards changes nothing this collector emits.
9
+ * The list is copied at construction: a caller that mutates the config or its
10
+ * array afterwards changes nothing this collector emits.
11
11
  *
12
- * Only an array is accepted (#264). Anything else — a missing field, `null`, a
12
+ * Only an array is accepted. Anything else — a missing field, `null`, a
13
13
  * number, an object, a string — is refused with a `TypeError` naming the
14
14
  * collector and the field, so a misconfigured deployment fails at boot. A
15
- * string is the case that matters: it is iterable, so copying it would split
16
- * `"posts.*"` into single characters, and `HasPermission` honours a lone `*`
17
- * as grant-all.
15
+ * copied string would split `"posts.*"` into single characters, and
16
+ * `HasPermission` honours a lone `*` as grant-all.
18
17
  *
19
- * The entries are not checked, and a non-string one is copied as it is. It is
20
- * inert: `HasPermission` skips every permission that is not a string, so such
21
- * an entry matches no requirement and grants nothing.
18
+ * Entries are not checked; a non-string one is copied as it is and is inert,
19
+ * since `HasPermission` skips every permission that is not a string.
22
20
  */
23
21
  export class StaticPermissionCollector {
24
22
  permissions;
@@ -4,18 +4,17 @@ import type { AttributeCollector, Attributes, CollectorContext, Role } from "@o3
4
4
  * `ATTR_ROLES`, independent of the JWT payload. Each role bundles a name and
5
5
  * the permissions it implies.
6
6
  *
7
- * The list is copied at construction, and so is each role in it and each
8
- * role's `permissions` array (#255): a caller that mutates the config, its
9
- * array or a `Role` in it afterwards changes nothing this collector emits.
7
+ * The list, each role in it and each role's `permissions` array are copied at
8
+ * construction: a caller that mutates the config, its array or a `Role` in it
9
+ * afterwards changes nothing this collector emits.
10
10
  *
11
- * Only an array is accepted (#264). Anything else — a missing field, `null`, a
12
- * number, an object, a string — is refused with a `TypeError` naming the
13
- * collector and the field, so a misconfigured deployment fails at boot. A
14
- * string is iterable and would otherwise be split into single characters, as
15
- * it would be in `StaticPermissionCollector`.
11
+ * Only an array is accepted. Anything else — a missing field, `null`, a
12
+ * number, an object, a string (which a copy would split into characters) — is
13
+ * refused with a `TypeError` naming the collector and the field, so a
14
+ * misconfigured deployment fails at boot.
16
15
  *
17
- * The entries are not checked: a malformed `Role` entry is copied as it is
18
- * (see `copyRole`). It is inert: `HasPermission` ignores a role that is not an
16
+ * Entries are not checked: a malformed `Role` is copied as it is (see
17
+ * `copyRole`) and is inert, since `HasPermission` ignores a role that is not an
19
18
  * object or whose `permissions` is not an array, and a non-string permission
20
19
  * inside a role. Each collect returns a shallow copy of the list, so its `Role`
21
20
  * copies are shared between the outputs of different collects.
@@ -1 +1 @@
1
- {"version":3,"file":"StaticRoleCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/StaticRoleCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,IAAI,EACJ,MAAM,iCAAiC,CAAC;AAGzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,mBAAoB,YAAW,kBAAkB;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;gBAE5B,MAAM,EAAE;QAAE,KAAK,EAAE,IAAI,EAAE,CAAA;KAAE;IAU/B,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAG9D"}
1
+ {"version":3,"file":"StaticRoleCollector.d.mts","sourceRoot":"","sources":["../../src/collectors/StaticRoleCollector.mts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACX,kBAAkB,EAClB,UAAU,EACV,gBAAgB,EAChB,IAAI,EACJ,MAAM,iCAAiC,CAAC;AAGzC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,mBAAoB,YAAW,kBAAkB;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;gBAE5B,MAAM,EAAE;QAAE,KAAK,EAAE,IAAI,EAAE,CAAA;KAAE;IAU/B,OAAO,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;CAG9D"}
@@ -6,18 +6,17 @@ import { ATTR_ROLES } from "@o3co/auth.policy-verifier.core";
6
6
  * `ATTR_ROLES`, independent of the JWT payload. Each role bundles a name and
7
7
  * the permissions it implies.
8
8
  *
9
- * The list is copied at construction, and so is each role in it and each
10
- * role's `permissions` array (#255): a caller that mutates the config, its
11
- * array or a `Role` in it afterwards changes nothing this collector emits.
9
+ * The list, each role in it and each role's `permissions` array are copied at
10
+ * construction: a caller that mutates the config, its array or a `Role` in it
11
+ * afterwards changes nothing this collector emits.
12
12
  *
13
- * Only an array is accepted (#264). Anything else — a missing field, `null`, a
14
- * number, an object, a string — is refused with a `TypeError` naming the
15
- * collector and the field, so a misconfigured deployment fails at boot. A
16
- * string is iterable and would otherwise be split into single characters, as
17
- * it would be in `StaticPermissionCollector`.
13
+ * Only an array is accepted. Anything else — a missing field, `null`, a
14
+ * number, an object, a string (which a copy would split into characters) — is
15
+ * refused with a `TypeError` naming the collector and the field, so a
16
+ * misconfigured deployment fails at boot.
18
17
  *
19
- * The entries are not checked: a malformed `Role` entry is copied as it is
20
- * (see `copyRole`). It is inert: `HasPermission` ignores a role that is not an
18
+ * Entries are not checked: a malformed `Role` is copied as it is (see
19
+ * `copyRole`) and is inert, since `HasPermission` ignores a role that is not an
21
20
  * object or whose `permissions` is not an array, and a non-string permission
22
21
  * inside a role. Each collect returns a shallow copy of the list, so its `Role`
23
22
  * copies are shared between the outputs of different collects.
@@ -40,7 +39,7 @@ export class StaticRoleCollector {
40
39
  *
41
40
  * Nothing about the entry is validated here, and a malformed one keeps its
42
41
  * shape rather than being coerced — `HasPermission` ignores a role that is not
43
- * an object or whose `permissions` is not an array (#180), and spreading a bare
42
+ * an object or whose `permissions` is not an array, and spreading a bare
44
43
  * string under `permissions` would splay it into characters it then honours.
45
44
  */
46
45
  function copyRole(role) {
@@ -1,4 +1,5 @@
1
1
  import type { AttributeKeyReservation, Attributes } from "@o3co/auth.policy-verifier.core";
2
+ import "../keys.mjs";
2
3
  /** Types a field may be promoted as. */
3
4
  export type AttributeMappingType = "string" | "number" | "boolean" | "string[]";
4
5
  export declare const ATTRIBUTE_MAPPING_TYPES: readonly AttributeMappingType[];
@@ -1 +1 @@
1
- {"version":3,"file":"_attributeMapping.d.mts","sourceRoot":"","sources":["../../src/collectors/_attributeMapping.mts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAG3F,wCAAwC;AACxC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAEhF,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAKlE,CAAC;AAEF,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,yFAAyF;IACzF,IAAI,CAAC,EAAE,oBAAoB,CAAC;CAC5B;AAED,2DAA2D;AAC3D,MAAM,WAAW,+BAA+B;IAC/C,wDAAwD;IACxD,UAAU,EAAE,gBAAgB,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAElE;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC/B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,uBAAuB,KAChC,MAAM,GAAG,SAAS,CAAC;AAExB;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,+BAA+B,GAAG,SAAS,EACnD,iBAAiB,EAAE,iBAAiB,GAClC,wBAAwB,EAAE,CA4B5B;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAS7E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAW/E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,UAAU,CASZ"}
1
+ {"version":3,"file":"_attributeMapping.d.mts","sourceRoot":"","sources":["../../src/collectors/_attributeMapping.mts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAI3F,OAAO,aAAa,CAAC;AAErB,wCAAwC;AACxC,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,CAAC;AAEhF,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAKlE,CAAC;AAEF,4BAA4B;AAC5B,MAAM,WAAW,gBAAgB;IAChC;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,yFAAyF;IACzF,IAAI,CAAC,EAAE,oBAAoB,CAAC;CAC5B;AAED,2DAA2D;AAC3D,MAAM,WAAW,+BAA+B;IAC/C,wDAAwD;IACxD,UAAU,EAAE,gBAAgB,EAAE,CAAC;CAC/B;AAED,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAElE;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC/B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,uBAAuB,KAChC,MAAM,GAAG,SAAS,CAAC;AAExB;;;GAGG;AACH,wBAAgB,sBAAsB,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,+BAA+B,GAAG,SAAS,EACnD,iBAAiB,EAAE,iBAAiB,GAClC,wBAAwB,EAAE,CA4B5B;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAS7E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAW/E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC9B,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,UAAU,CASZ"}
@@ -1,6 +1,9 @@
1
1
  // SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import { attributeKeyReservation } from "@o3co/auth.policy-verifier.core";
4
+ // This package's own keys are reserved by loading their module, which a
5
+ // mapping collector must not depend on the package entry to have done.
6
+ import "../keys.mjs";
4
7
  export const ATTRIBUTE_MAPPING_TYPES = [
5
8
  "string",
6
9
  "number",
@@ -12,9 +12,8 @@ export declare function resolveClaimName(collector: string, raw: unknown, fallba
12
12
  * Reads a scope claim as a list. The OAuth `scope` claim is a space-delimited
13
13
  * string; Okta's `scp` and Auth0's `permissions` are arrays of strings. Either
14
14
  * shape yields the non-empty entries. Anything else — absent, a number, a list
15
- * carrying a non-string — asserts no capability at all, the way a non-string
16
- * `scope` never did: fail closed rather than pick the strings out of a value
17
- * that is not a scope list.
15
+ * carrying a non-string — asserts no capability at all: fail closed rather
16
+ * than pick the strings out of a value that is not a scope list.
18
17
  */
19
18
  export declare function scopesFrom(value: unknown): string[];
20
19
  //# sourceMappingURL=_claims.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_claims.d.mts","sourceRoot":"","sources":["../../src/collectors/_claims.mts"],"names":[],"mappings":"AAaA,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM1F;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAMnD"}
1
+ {"version":3,"file":"_claims.d.mts","sourceRoot":"","sources":["../../src/collectors/_claims.mts"],"names":[],"mappings":"AAaA,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,UAAU,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM1F;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAMnD"}
@@ -1,7 +1,7 @@
1
1
  // SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  /*
4
- * Which claim carries the scopes, and how its value is read (#219).
4
+ * Which claim carries the scopes, and how its value is read.
5
5
  * `PayloadScopeCollector` and `ResourceActionScopeRuleCollector` share the
6
6
  * default claim name and refuse the same bad `claim` option; only
7
7
  * `PayloadScopeCollector` reads the value (`scopesFrom`), while the rule
@@ -30,9 +30,8 @@ export function resolveClaimName(collector, raw, fallback) {
30
30
  * Reads a scope claim as a list. The OAuth `scope` claim is a space-delimited
31
31
  * string; Okta's `scp` and Auth0's `permissions` are arrays of strings. Either
32
32
  * shape yields the non-empty entries. Anything else — absent, a number, a list
33
- * carrying a non-string — asserts no capability at all, the way a non-string
34
- * `scope` never did: fail closed rather than pick the strings out of a value
35
- * that is not a scope list.
33
+ * carrying a non-string — asserts no capability at all: fail closed rather
34
+ * than pick the strings out of a value that is not a scope list.
36
35
  */
37
36
  export function scopesFrom(value) {
38
37
  if (typeof value === "string")
@@ -0,0 +1,48 @@
1
+ /** The claim RFC 9396 carries the entries in, and the default. */
2
+ export declare const DEFAULT_AUTHORIZATION_DETAILS_CLAIM = "authorization_details";
3
+ /** One element of a path: a type, and an id when the path names one. */
4
+ export interface RangeElement {
5
+ type: string;
6
+ id?: string;
7
+ }
8
+ /**
9
+ * Reads `path` as `(type(:id)?.)*action`, or `null` when it is not one:
10
+ *
11
+ * - `type` and `action` are `[a-z][a-z0-9_]*`;
12
+ * - `id` is `[A-Za-z0-9_~-]`, any other character percent-encoded with
13
+ * upper-case hex. An id is compared as written, so `%41` and `A` are two
14
+ * ids: the issuer and the resource must encode alike;
15
+ * - the action, the last element, carries no id.
16
+ */
17
+ export declare function parseRangePath(path: string): RangeElement[] | null;
18
+ /**
19
+ * The requested path — the raw resource, then the action — or `null` when the
20
+ * action is not one action of the grammar. The action is checked on its own
21
+ * before the join: an action of several elements (`report.delete`) would
22
+ * re-split the joined path, so a request on the parent `project:p1` would
23
+ * read as one on `project:p1.report`, which a range may contain.
24
+ */
25
+ export declare function requestedRangePath(resource: string, action: string): string | null;
26
+ /**
27
+ * Whether the range entry `entry` contains the requested `path`: a
28
+ * segment-wise prefix of it, each element of the same type, and an element
29
+ * that names no id containing the same type with any id. The entry's last
30
+ * element is compared as a type whether it names an action or not, so
31
+ * `a:1.run` contains `a:1.run.more`. Either side outside the grammar contains
32
+ * nothing.
33
+ */
34
+ export declare function rangeContains(entry: string, path: string): boolean;
35
+ /**
36
+ * The claim's entries of `type`, or `null` when the token has no range: the
37
+ * claim is absent, or a list of RFC 9396 entries none of which is of the type.
38
+ * A token cannot shed its range by spelling it badly: an entry of the type
39
+ * counts however malformed its path, and a claim present in another shape — not
40
+ * a list, or a list holding something that is not an entry object — is a range
41
+ * with no entries, which contains nothing.
42
+ */
43
+ export declare function entriesOfType(claim: unknown, type: string): object[] | null;
44
+ /** The entries' paths that are in the grammar; the rest are left out, which narrows the range. */
45
+ export declare function rangePaths(entries: object[]): string[];
46
+ /** Resolves a collector's `type` option, which has no default: it is the deployment's delegation type. */
47
+ export declare function resolveRangeType(collector: string, raw: unknown): string;
48
+ //# sourceMappingURL=range.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"range.d.mts","sourceRoot":"","sources":["../../src/delegation/range.mts"],"names":[],"mappings":"AAeA,kEAAkE;AAClE,eAAO,MAAM,mCAAmC,0BAA0B,CAAC;AAE3E,wEAAwE;AACxE,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;CACZ;AAOD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,GAAG,IAAI,CAiBlE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAElF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASlE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAK3E;AAMD,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAItD;AAED,0GAA0G;AAC1G,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAOxE"}
@@ -0,0 +1,108 @@
1
+ // SPDX-FileCopyrightText: 2026 1o1 Co. Ltd.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /*
4
+ * A delegated token's range, as the provider-verifier claims contract states
5
+ * it (o3co/auth, docs/claims-contract.md): RFC 9396 `authorization_details`
6
+ * entries of one type, each carrying a path in the grammar
7
+ * `(type(:id)?.)*action`, and the rule by which an entry contains a requested
8
+ * path. Shared by `DelegationRangeCollector`, which reads the range out of
9
+ * the token, `DelegationRangeRuleCollector`, which decides whether a token
10
+ * has one, and `WithinDelegationRange`, which decides containment. Nothing is
11
+ * normalized: a path is compared as written, as `HasScope` compares scopes.
12
+ * The attribute key the range travels under is `../keys.mts`'s.
13
+ */
14
+ /** The claim RFC 9396 carries the entries in, and the default. */
15
+ export const DEFAULT_AUTHORIZATION_DETAILS_CLAIM = "authorization_details";
16
+ /** A type, or the action that ends a path. */
17
+ const NAME = /^[a-z][a-z0-9_]*$/;
18
+ /** An id: unreserved characters, anything else percent-encoded in upper-case hex. */
19
+ const ID = /^(?:[A-Za-z0-9_~-]|%[0-9A-F]{2})+$/;
20
+ /**
21
+ * Reads `path` as `(type(:id)?.)*action`, or `null` when it is not one:
22
+ *
23
+ * - `type` and `action` are `[a-z][a-z0-9_]*`;
24
+ * - `id` is `[A-Za-z0-9_~-]`, any other character percent-encoded with
25
+ * upper-case hex. An id is compared as written, so `%41` and `A` are two
26
+ * ids: the issuer and the resource must encode alike;
27
+ * - the action, the last element, carries no id.
28
+ */
29
+ export function parseRangePath(path) {
30
+ if (typeof path !== "string" || path === "")
31
+ return null;
32
+ const parts = path.split(".");
33
+ const elements = [];
34
+ for (const [index, part] of parts.entries()) {
35
+ const pieces = part.split(":");
36
+ if (pieces.length > 2)
37
+ return null;
38
+ const [type, id] = pieces;
39
+ if (!NAME.test(type))
40
+ return null;
41
+ if (id === undefined) {
42
+ elements.push({ type });
43
+ continue;
44
+ }
45
+ if (index === parts.length - 1 || !ID.test(id))
46
+ return null;
47
+ elements.push({ type, id });
48
+ }
49
+ return elements;
50
+ }
51
+ /**
52
+ * The requested path — the raw resource, then the action — or `null` when the
53
+ * action is not one action of the grammar. The action is checked on its own
54
+ * before the join: an action of several elements (`report.delete`) would
55
+ * re-split the joined path, so a request on the parent `project:p1` would
56
+ * read as one on `project:p1.report`, which a range may contain.
57
+ */
58
+ export function requestedRangePath(resource, action) {
59
+ return NAME.test(action) ? `${resource}.${action}` : null;
60
+ }
61
+ /**
62
+ * Whether the range entry `entry` contains the requested `path`: a
63
+ * segment-wise prefix of it, each element of the same type, and an element
64
+ * that names no id containing the same type with any id. The entry's last
65
+ * element is compared as a type whether it names an action or not, so
66
+ * `a:1.run` contains `a:1.run.more`. Either side outside the grammar contains
67
+ * nothing.
68
+ */
69
+ export function rangeContains(entry, path) {
70
+ const within = parseRangePath(entry);
71
+ const requested = parseRangePath(path);
72
+ if (within === null || requested === null || within.length > requested.length)
73
+ return false;
74
+ return within.every((element, index) => element.type === requested[index].type &&
75
+ (element.id === undefined || element.id === requested[index].id));
76
+ }
77
+ /**
78
+ * The claim's entries of `type`, or `null` when the token has no range: the
79
+ * claim is absent, or a list of RFC 9396 entries none of which is of the type.
80
+ * A token cannot shed its range by spelling it badly: an entry of the type
81
+ * counts however malformed its path, and a claim present in another shape — not
82
+ * a list, or a list holding something that is not an entry object — is a range
83
+ * with no entries, which contains nothing.
84
+ */
85
+ export function entriesOfType(claim, type) {
86
+ if (claim === undefined)
87
+ return null;
88
+ if (!Array.isArray(claim) || !claim.every(isEntryObject))
89
+ return [];
90
+ const entries = claim.filter((entry) => entry.type === type);
91
+ return entries.length > 0 ? entries : null;
92
+ }
93
+ function isEntryObject(entry) {
94
+ return typeof entry === "object" && entry !== null && !Array.isArray(entry);
95
+ }
96
+ /** The entries' paths that are in the grammar; the rest are left out, which narrows the range. */
97
+ export function rangePaths(entries) {
98
+ return entries
99
+ .map((entry) => entry.path)
100
+ .filter((path) => typeof path === "string" && parseRangePath(path) !== null);
101
+ }
102
+ /** Resolves a collector's `type` option, which has no default: it is the deployment's delegation type. */
103
+ export function resolveRangeType(collector, raw) {
104
+ if (typeof raw !== "string" || raw === "") {
105
+ throw new Error(`${collector}: type must be the authorization_details type the delegation range is carried under, a non-empty string`);
106
+ }
107
+ return raw;
108
+ }