@pipeline-builder/api-core 4.1.13 → 4.1.15

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 (187) hide show
  1. package/README.md +3 -2
  2. package/lib/constants/index.d.ts +0 -1
  3. package/lib/constants/index.js +1 -2
  4. package/lib/errors/app-errors.d.ts +8 -3
  5. package/lib/errors/app-errors.js +10 -5
  6. package/lib/helpers/access-helpers.d.ts +4 -6
  7. package/lib/helpers/access-helpers.js +6 -8
  8. package/lib/helpers/index.d.ts +3 -3
  9. package/lib/helpers/index.js +4 -4
  10. package/lib/helpers/org-hierarchy-http.d.ts +11 -10
  11. package/lib/helpers/org-hierarchy-http.js +24 -35
  12. package/lib/helpers/org-hierarchy.d.ts +37 -3
  13. package/lib/helpers/org-hierarchy.js +45 -4
  14. package/lib/helpers/restore-helpers.d.ts +44 -24
  15. package/lib/helpers/restore-helpers.js +41 -52
  16. package/lib/helpers/sse-helpers.d.ts +29 -7
  17. package/lib/helpers/sse-helpers.js +36 -9
  18. package/lib/index.d.ts +16 -14
  19. package/lib/index.js +17 -15
  20. package/lib/middleware/assurance.d.ts +116 -0
  21. package/lib/middleware/assurance.js +176 -0
  22. package/lib/middleware/auth.d.ts +5 -427
  23. package/lib/middleware/auth.js +36 -789
  24. package/lib/middleware/ecosystem-guard.d.ts +47 -0
  25. package/lib/middleware/ecosystem-guard.js +60 -0
  26. package/lib/middleware/index.d.ts +11 -4
  27. package/lib/middleware/index.js +12 -5
  28. package/lib/middleware/jwt-verify.d.ts +52 -0
  29. package/lib/middleware/jwt-verify.js +105 -0
  30. package/lib/middleware/permission-gates.d.ts +133 -0
  31. package/lib/middleware/permission-gates.js +260 -0
  32. package/lib/middleware/revocation.d.ts +96 -0
  33. package/lib/middleware/revocation.js +162 -0
  34. package/lib/middleware/route-table.d.ts +11 -0
  35. package/lib/middleware/route-table.js +4 -1
  36. package/lib/middleware/service-tokens.d.ts +83 -0
  37. package/lib/middleware/service-tokens.js +158 -0
  38. package/lib/middleware/step-up.d.ts +2 -2
  39. package/lib/middleware/step-up.js +8 -7
  40. package/lib/middleware/system-org.d.ts +20 -0
  41. package/lib/middleware/system-org.js +25 -0
  42. package/lib/openapi/routes/plugin-routes.js +133 -10
  43. package/lib/openapi/schema-registry.js +2 -5
  44. package/lib/routes/index.d.ts +1 -1
  45. package/lib/routes/index.js +2 -2
  46. package/lib/services/api-key-exchange.js +7 -5
  47. package/lib/services/audit-spool.d.ts +34 -10
  48. package/lib/services/audit-spool.js +75 -34
  49. package/lib/services/cache-service.d.ts +7 -0
  50. package/lib/services/cache-service.js +1 -1
  51. package/lib/services/circuit-breaker.d.ts +14 -1
  52. package/lib/services/circuit-breaker.js +36 -10
  53. package/lib/services/compliance-client.d.ts +10 -2
  54. package/lib/services/compliance-client.js +17 -10
  55. package/lib/services/compliance-event-subscriber.js +6 -6
  56. package/lib/services/durable-event-bus.js +8 -11
  57. package/lib/services/ecosystem-notify-client.d.ts +32 -0
  58. package/lib/services/ecosystem-notify-client.js +71 -0
  59. package/lib/services/env-redis.d.ts +4 -1
  60. package/lib/services/env-redis.js +32 -3
  61. package/lib/services/http-client.d.ts +19 -0
  62. package/lib/services/http-client.js +98 -19
  63. package/lib/services/index.d.ts +25 -24
  64. package/lib/services/index.js +25 -22
  65. package/lib/services/jwks-cache.js +5 -4
  66. package/lib/services/leader-lock.d.ts +22 -5
  67. package/lib/services/leader-lock.js +61 -6
  68. package/lib/services/quota.d.ts +9 -4
  69. package/lib/services/quota.js +34 -40
  70. package/lib/services/remote-audit-client.d.ts +28 -32
  71. package/lib/services/remote-audit-client.js +166 -49
  72. package/lib/services/retry-forever.js +5 -11
  73. package/lib/services/retry-strategy.d.ts +1 -1
  74. package/lib/services/retry-strategy.js +5 -4
  75. package/lib/services/scheduler.d.ts +8 -3
  76. package/lib/services/scheduler.js +17 -5
  77. package/lib/services/service-boot.d.ts +14 -13
  78. package/lib/services/service-boot.js +18 -15
  79. package/lib/services/service-keys.d.ts +1 -1
  80. package/lib/services/service-keys.js +12 -18
  81. package/lib/services/sse-ticket-store.d.ts +9 -1
  82. package/lib/services/sse-ticket-store.js +24 -9
  83. package/lib/services/system-notification.d.ts +27 -0
  84. package/lib/services/system-notification.js +50 -0
  85. package/lib/services/token-revocation.d.ts +50 -1
  86. package/lib/services/token-revocation.js +103 -3
  87. package/lib/types/audit-events.d.ts +6 -6
  88. package/lib/types/audit-events.js +1 -1
  89. package/lib/types/common.d.ts +32 -13
  90. package/lib/types/common.js +2 -1
  91. package/lib/types/ecosystem-notifications.d.ts +142 -0
  92. package/lib/types/ecosystem-notifications.js +210 -0
  93. package/lib/types/ecosystem.d.ts +77 -0
  94. package/lib/types/ecosystem.js +119 -0
  95. package/lib/types/error-codes.d.ts +81 -0
  96. package/lib/types/error-codes.js +108 -1
  97. package/lib/types/feature-flags.d.ts +21 -1
  98. package/lib/types/feature-flags.js +36 -2
  99. package/lib/types/index.d.ts +14 -9
  100. package/lib/types/index.js +15 -10
  101. package/lib/types/permissions.d.ts +46 -8
  102. package/lib/types/permissions.js +75 -12
  103. package/lib/types/plugin-catalog.d.ts +35 -0
  104. package/lib/types/plugin-catalog.js +39 -0
  105. package/lib/types/plugin-health.d.ts +102 -0
  106. package/lib/types/plugin-health.js +184 -0
  107. package/lib/types/quota-tiers.d.ts +15 -0
  108. package/lib/types/quota-tiers.js +23 -3
  109. package/lib/types/wire-vocabulary.d.ts +102 -0
  110. package/lib/types/wire-vocabulary.js +94 -0
  111. package/lib/utils/alias-resolver.js +2 -2
  112. package/lib/utils/audit.d.ts +5 -2
  113. package/lib/utils/audit.js +6 -3
  114. package/lib/utils/compliance-attributes.js +3 -4
  115. package/lib/utils/concurrency.d.ts +7 -0
  116. package/lib/utils/concurrency.js +13 -1
  117. package/lib/utils/env.d.ts +3 -4
  118. package/lib/utils/env.js +4 -5
  119. package/lib/utils/identity.d.ts +13 -4
  120. package/lib/utils/identity.js +18 -10
  121. package/lib/utils/index.d.ts +13 -10
  122. package/lib/utils/index.js +13 -10
  123. package/lib/utils/jwk.d.ts +4 -0
  124. package/lib/utils/jwk.js +13 -1
  125. package/lib/utils/logger.d.ts +1 -1
  126. package/lib/utils/logger.js +1 -1
  127. package/lib/utils/object.d.ts +11 -0
  128. package/lib/utils/object.js +24 -1
  129. package/lib/utils/params.d.ts +8 -22
  130. package/lib/utils/params.js +15 -43
  131. package/lib/utils/plugin-compliance.d.ts +98 -0
  132. package/lib/utils/plugin-compliance.js +78 -0
  133. package/lib/utils/proof-of-work.d.ts +69 -0
  134. package/lib/utils/proof-of-work.js +139 -0
  135. package/lib/utils/response.d.ts +16 -6
  136. package/lib/utils/response.js +18 -15
  137. package/lib/utils/retention.d.ts +17 -0
  138. package/lib/utils/retention.js +30 -0
  139. package/lib/utils/secret-encryption.d.ts +7 -7
  140. package/lib/utils/secret-encryption.js +9 -10
  141. package/lib/utils/secret-rotation.js +2 -2
  142. package/lib/utils/sensitive-patterns.js +4 -5
  143. package/lib/utils/service-registry.d.ts +15 -0
  144. package/lib/utils/service-registry.js +24 -0
  145. package/lib/utils/ssrf.d.ts +11 -2
  146. package/lib/utils/ssrf.js +112 -33
  147. package/lib/validation/ai-schemas.js +3 -2
  148. package/lib/validation/common-schemas.d.ts +0 -9
  149. package/lib/validation/common-schemas.js +1 -7
  150. package/lib/validation/dockerfile-static.d.ts +26 -0
  151. package/lib/validation/dockerfile-static.js +210 -0
  152. package/lib/validation/index.d.ts +13 -4
  153. package/lib/validation/index.js +14 -5
  154. package/lib/validation/message-schemas.js +6 -4
  155. package/lib/validation/pipeline-schemas.d.ts +8 -2
  156. package/lib/validation/pipeline-schemas.js +30 -4
  157. package/lib/validation/plugin-base-images.d.ts +20 -0
  158. package/lib/validation/plugin-base-images.js +17 -0
  159. package/lib/validation/plugin-catalog-detect.d.ts +86 -0
  160. package/lib/validation/plugin-catalog-detect.js +237 -0
  161. package/lib/validation/plugin-catalog-metadata.d.ts +141 -0
  162. package/lib/validation/plugin-catalog-metadata.js +162 -0
  163. package/lib/validation/plugin-heuristics.d.ts +41 -0
  164. package/lib/validation/plugin-heuristics.js +211 -0
  165. package/lib/validation/plugin-lint.d.ts +18 -0
  166. package/lib/validation/plugin-lint.js +136 -0
  167. package/lib/validation/plugin-name-confusable.d.ts +15 -0
  168. package/lib/validation/plugin-name-confusable.js +81 -0
  169. package/lib/validation/plugin-schemas.d.ts +40 -26
  170. package/lib/validation/plugin-schemas.js +26 -23
  171. package/lib/validation/plugin-spec-schema.d.ts +112 -0
  172. package/lib/validation/plugin-spec-schema.js +163 -0
  173. package/lib/validation/plugin-template-contract.d.ts +64 -0
  174. package/lib/validation/plugin-template-contract.js +113 -0
  175. package/package.json +69 -4
  176. package/lib/testing/mock-api-core.d.ts +0 -150
  177. package/lib/testing/mock-api-core.js +0 -246
  178. package/lib/testing/mock-drizzle.d.ts +0 -6
  179. package/lib/testing/mock-drizzle.js +0 -57
  180. package/lib/testing/route-coverage.d.ts +0 -64
  181. package/lib/testing/route-coverage.js +0 -183
  182. package/lib/testing/service-tokens.d.ts +0 -66
  183. package/lib/testing/service-tokens.js +0 -137
  184. package/lib/testing/tier-mock.d.ts +0 -44
  185. package/lib/testing/tier-mock.js +0 -65
  186. package/lib/testing/user-tokens.d.ts +0 -75
  187. package/lib/testing/user-tokens.js +0 -115
@@ -0,0 +1,260 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { tagRouteGate } from './route-table.js';
4
+ import { HttpStatus } from '../constants/http-status.js';
5
+ import { serviceIdentity } from '../services/service-keys.js';
6
+ import { ErrorCode } from '../types/error-codes.js';
7
+ import { hasPermission } from '../types/permissions.js';
8
+ import { emitCounter } from '../utils/metric-emitter.js';
9
+ import { sendError } from '../utils/response.js';
10
+ import { isServicePrincipal, serviceNameOf } from './service-tokens.js';
11
+ import { createLogger } from '../utils/logger.js';
12
+ const logger = createLogger('auth-middleware');
13
+ /**
14
+ * Whether the request's user holds `permission`. Superadmins implicitly hold
15
+ * every permission. Reads the resolved `permissions` claim (set at token issue,
16
+ * or re-derived per request by the platform).
17
+ */
18
+ export function userHasPermission(req, permission) {
19
+ return hasPermission(req.user?.permissions, permission, req.user?.isSuperAdmin);
20
+ }
21
+ /**
22
+ * Optional sink for denied-authorization events. Left unset by default so
23
+ * api-core stays decoupled from any audit transport — a service registers one
24
+ * at boot (typically forwarding to its `RemoteAuditClient` as an
25
+ * `authz.denied` event, or, on platform, to the local `audit()` helper).
26
+ * MUST be best-effort: it is invoked inside the request path, so it must never
27
+ * throw or block (the gate wraps the call in try/catch regardless).
28
+ */
29
+ let authzDenialAuditor;
30
+ /**
31
+ * Register (or clear, with `undefined`) the process-wide authorization-denial
32
+ * auditor. Idempotent; the last registration wins.
33
+ */
34
+ export function setAuthzDenialAuditor(fn) {
35
+ authzDenialAuditor = fn;
36
+ }
37
+ /**
38
+ * Emit a denial event, best-effort. Only fires for state-changing (non-GET,
39
+ * non-HEAD/OPTIONS) requests — a rejected GET is low-signal probing noise and
40
+ * would amplify audit volume under a scan. Never throws.
41
+ *
42
+ * The permission gates here call it themselves. Export it for data-driven checks
43
+ * a route makes inline (cross-org access, service-only endpoints), so those 403s
44
+ * reach the same `authz.denied` trail. `required` names what was missing.
45
+ */
46
+ export function recordAuthzDenial(req, required) {
47
+ const auditor = authzDenialAuditor;
48
+ if (!auditor)
49
+ return;
50
+ const method = req.method.toUpperCase();
51
+ if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS')
52
+ return;
53
+ try {
54
+ auditor({
55
+ actorId: req.user?.sub,
56
+ actorEmail: req.user?.email,
57
+ orgId: req.user?.organizationId,
58
+ method,
59
+ // Strip the query string — a denied request may carry a token/secret as a
60
+ // query param, and this path is persisted verbatim into the audit log.
61
+ path: (req.originalUrl || req.url).split('?')[0],
62
+ required,
63
+ });
64
+ }
65
+ catch {
66
+ // Best-effort — a broken auditor must never break the auth gate.
67
+ }
68
+ }
69
+ /** The shared gate preamble: answer 401 and return false when unauthenticated. */
70
+ function requireUser(req, res) {
71
+ if (req.user)
72
+ return true;
73
+ sendError(res, HttpStatus.UNAUTHORIZED, 'Authentication required', ErrorCode.UNAUTHORIZED);
74
+ return false;
75
+ }
76
+ /** Audit + answer a permission refusal (`missing` defaults to every required permission). */
77
+ function denyPermission(req, res, permissions, mode, missing = permissions) {
78
+ recordAuthzDenial(req, permissions.join(` ${mode} `));
79
+ sendError(res, HttpStatus.FORBIDDEN, `Missing required permission: ${missing.join(` ${mode} `)}`, ErrorCode.INSUFFICIENT_PERMISSIONS);
80
+ }
81
+ /**
82
+ * Requires that the user hold AT LEAST ONE of the given permissions (or be a
83
+ * superadmin). Use after requireAuth. Mirrors `requireRole`'s any-of semantics;
84
+ * pass a single permission for a specific action, or several when any one of
85
+ * them should grant access.
86
+ */
87
+ export function requirePermission(...permissions) {
88
+ return tagRouteGate((req, res, next) => {
89
+ if (!requireUser(req, res))
90
+ return;
91
+ // userHasPermission → hasPermission already grants superadmins every
92
+ // permission, so this single check covers both the superadmin bypass and
93
+ // the any-of membership test without re-inlining either.
94
+ if (permissions.some((p) => userHasPermission(req, p)))
95
+ return next();
96
+ denyPermission(req, res, permissions, 'or');
97
+ }, { kind: 'permission', mode: 'any', permissions });
98
+ }
99
+ /**
100
+ * Requires that the user hold EVERY one of the given permissions (or be a
101
+ * superadmin) — the AND counterpart to `requirePermission`'s any-of. Use for a
102
+ * sensitive action that legitimately demands two distinct capabilities at once
103
+ * (e.g. a cross-surface operation), so the guarantee is explicit rather than
104
+ * approximated by chaining two `requirePermission` gates. Superadmins bypass
105
+ * (they implicitly hold every permission).
106
+ */
107
+ export function requireAllPermissions(...permissions) {
108
+ return tagRouteGate((req, res, next) => {
109
+ if (!requireUser(req, res))
110
+ return;
111
+ const missing = permissions.filter((p) => !userHasPermission(req, p));
112
+ if (missing.length === 0)
113
+ return next();
114
+ denyPermission(req, res, permissions, 'and', missing);
115
+ }, { kind: 'permission', mode: 'all', permissions });
116
+ }
117
+ /**
118
+ * Like {@link requirePermission} (any-of) but ALSO admits an internal service
119
+ * principal (`principalType: 'service'`). For READ endpoints that BOTH
120
+ * interactive users — who must hold one of the `:read` capabilities — AND
121
+ * service-to-service callers legitimately hit; service tokens carry no
122
+ * permission claims (they're least-privilege `role:member`), so a plain
123
+ * `requirePermission` would wrongly 403 them. Superadmins pass via
124
+ * `userHasPermission`'s implicit-all. Use only where a service caller is a known,
125
+ * intended consumer of the route (verify the callers); prefer plain
126
+ * `requirePermission` for purely user-facing reads.
127
+ */
128
+ export function requirePermissionOrService(...permissions) {
129
+ return tagRouteGate((req, res, next) => {
130
+ if (!requireUser(req, res))
131
+ return;
132
+ if (isServicePrincipal(req) || permissions.some((p) => userHasPermission(req, p)))
133
+ return next();
134
+ denyPermission(req, res, permissions, 'or');
135
+ }, { kind: 'permission', mode: 'any', permissions, allowService: true });
136
+ }
137
+ /**
138
+ * Gate an endpoint to internal service-to-service callers only (a
139
+ * `principalType: 'service'` principal minted via {@link signServiceToken} / `getServiceAuthHeader`).
140
+ * Rejects any user token. For NON-user-facing endpoints — entity-event ingest,
141
+ * auto-subscribe, and similar peer-service hooks — that a browser must never
142
+ * reach. Compose after `requireAuth` so `req.user` is populated.
143
+ */
144
+ export function requireServicePrincipal(req, res, next) {
145
+ if (!isServicePrincipal(req)) {
146
+ // 403 (not 400) — this is an authorization refusal; status must match the
147
+ // INSUFFICIENT_PERMISSIONS code and the sibling gates (requirePermission, etc.).
148
+ return sendError(res, HttpStatus.FORBIDDEN, 'Internal service calls only', ErrorCode.INSUFFICIENT_PERMISSIONS);
149
+ }
150
+ next();
151
+ }
152
+ tagRouteGate(requireServicePrincipal, { kind: 'servicePrincipal' });
153
+ /**
154
+ * THE gate for an INTERNAL route — the one every `/internal/*` path, the
155
+ * quota usage counters, the entity-event ingest and the audit ingest go through.
156
+ *
157
+ * Two rules, both fail-closed:
158
+ *
159
+ * 1. **No user token, ever.** Not a member's, not a superadmin's, not a
160
+ * service-account key's. An internal route is a peer-service API surface, so
161
+ * "a sufficiently privileged human" is not an acceptable caller — that
162
+ * equivalence is exactly how a browser-reachable path becomes a tenant
163
+ * boundary bug. `requireServicePrincipal` already refused users; this also
164
+ * closes the `|| isSystemAdmin(req)` escape hatches that had grown around
165
+ * the individual internal routes.
166
+ * 2. **Only the NAMED callers.** `callers` lists the services that legitimately
167
+ * call this route, and the name is cryptographically bound to the signing
168
+ * key (`services/service-keys.ts`), so this is an identity check rather than
169
+ * a claim check. It is the same allow-list the mesh policy names, at the
170
+ * layer that also holds in docker compose, where there is no mesh at all —
171
+ * the Istio `AuthorizationPolicy` is defence in depth on top, never the
172
+ * enforcement.
173
+ *
174
+ * Compose after `requireAuth` so `req.user` is populated. Refusals are counted
175
+ * (`internal_route_refused_total`) and audited through the shared `authz.denied`
176
+ * sink.
177
+ */
178
+ export function requireInternalService(options) {
179
+ const allowed = new Set(options.callers);
180
+ return tagRouteGate((req, res, next) => {
181
+ const refuse = (reason) => {
182
+ emitCounter('internal_route_refused_total', {
183
+ service: serviceIdentity(),
184
+ route: req.route?.path ? `${req.method} ${req.baseUrl}${req.route.path}` : `${req.method} ${(req.originalUrl || req.url).split('?')[0]}`,
185
+ reason,
186
+ caller: serviceNameOf(req.user) ?? 'none',
187
+ });
188
+ recordAuthzDenial(req, `internal-service (${options.callers.join(', ')})`);
189
+ if (reason === 'unauthenticated') {
190
+ return sendError(res, HttpStatus.UNAUTHORIZED, 'Authentication required', ErrorCode.UNAUTHORIZED);
191
+ }
192
+ logger.warn('Refused an internal route', { reason, path: (req.originalUrl || req.url).split('?')[0], caller: serviceNameOf(req.user) });
193
+ return sendError(res, HttpStatus.FORBIDDEN, 'Internal service calls only', ErrorCode.INSUFFICIENT_PERMISSIONS);
194
+ };
195
+ if (!req.user)
196
+ return refuse('unauthenticated');
197
+ if (!isServicePrincipal(req))
198
+ return refuse('user_token');
199
+ const caller = serviceNameOf(req.user);
200
+ if (!caller || !allowed.has(caller))
201
+ return refuse('wrong_caller');
202
+ next();
203
+ }, { kind: 'servicePrincipal' }, { kind: 'internalService', callers: [...options.callers] });
204
+ }
205
+ /**
206
+ * Check if the request is from a system admin.
207
+ *
208
+ * Authority is granted solely by the user-level `isSuperAdmin` flag carried
209
+ * in the JWT — never by membership in the well-known 'system' org, which would
210
+ * conflate a Pipeline Builder operator with a customer tenant and let any
211
+ * write that created a 'system'-named org quietly grant cross-org authority. The `system` org still exists as a *content holder* for shared
212
+ * sample data; it confers no privilege.
213
+ */
214
+ export function isSystemAdmin(req) {
215
+ return req.user?.isSuperAdmin === true;
216
+ }
217
+ /**
218
+ * Whether the request's token carries a specific capability `scope` (e.g.
219
+ * `'reporting:ingest'`). Used to gate endpoints that accept a narrow machine
220
+ * identity — a normal interactive user token has no `scope` and returns false.
221
+ */
222
+ export function hasScope(req, scope) {
223
+ return req.user?.scope === scope;
224
+ }
225
+ /** Requires a system admin — granted solely by the `isSuperAdmin` token claim.
226
+ * Use after requireAuth. */
227
+ export function requireSystemAdmin(req, res, next) {
228
+ if (!isSystemAdmin(req)) {
229
+ recordAuthzDenial(req, 'system-admin');
230
+ return sendError(res, HttpStatus.FORBIDDEN, 'Access denied. Only system administrators can perform this action.', ErrorCode.INSUFFICIENT_PERMISSIONS);
231
+ }
232
+ next();
233
+ }
234
+ tagRouteGate(requireSystemAdmin, { kind: 'systemAdmin' });
235
+ /**
236
+ * Require a specific feature flag. Use after requireAuth.
237
+ * Checks the `features` array in the JWT payload (set at token issuance).
238
+ * Sysadmins (isSuperAdmin) bypass — they always have every feature.
239
+ */
240
+ export function requireFeature(feature) {
241
+ return tagRouteGate((req, res, next) => {
242
+ if (!requireUser(req, res))
243
+ return;
244
+ // Sysadmins get all features; the `features` array on their token is
245
+ // populated with every flag at issuance time but the bypass here keeps
246
+ // the rule explicit and survives a token issued before a new feature
247
+ // flag was added.
248
+ if (req.user.isSuperAdmin === true)
249
+ return next();
250
+ if (!req.user.features?.includes(feature)) {
251
+ // Route feature-gate denials through the same audit sink as
252
+ // requirePermission / requireSystemAdmin so a probe for an
253
+ // unentitled capability leaves a trail (state-changing methods only).
254
+ recordAuthzDenial(req, `feature:${feature}`);
255
+ return sendError(res, HttpStatus.FORBIDDEN, `This feature requires a higher plan (${feature})`, ErrorCode.INSUFFICIENT_PERMISSIONS);
256
+ }
257
+ next();
258
+ }, { kind: 'feature', feature });
259
+ }
260
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGVybWlzc2lvbi1nYXRlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWRkbGV3YXJlL3Blcm1pc3Npb24tZ2F0ZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQVF0QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDaEQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3pELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUM5RCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDcEQsT0FBTyxFQUFtQixhQUFhLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUN6RSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDekQsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxhQUFhLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN4RSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFFbEQsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLGlCQUFpQixDQUFDLENBQUM7QUFDL0M7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxHQUFZLEVBQUUsVUFBc0I7SUFDcEUsT0FBTyxhQUFhLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLEdBQUcsQ0FBQyxJQUFJLEVBQUUsWUFBWSxDQUFDLENBQUM7QUFDbEYsQ0FBQztBQW9CRDs7Ozs7OztHQU9HO0FBQ0gsSUFBSSxrQkFBaUUsQ0FBQztBQUV0RTs7O0dBR0c7QUFDSCxNQUFNLFVBQVUscUJBQXFCLENBQUMsRUFBaUQ7SUFDckYsa0JBQWtCLEdBQUcsRUFBRSxDQUFDO0FBQzFCLENBQUM7QUFF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QUMsSUFBSSxDQUFDLFlBQVksS0FBSyxJQUFJO1lBQUUsT0FBTyxJQUFJLEVBQUUsQ0FBQztRQUVsRCxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDMUMsNERBQTREO1lBQzVELDJEQUEyRDtZQUMzRCxzRUFBc0U7WUFDdEUsaUJBQWlCLENBQUMsR0FBRyxFQUFFLFdBQVcsT0FBTyxFQUFFLENBQUMsQ0FBQztZQUM3QyxPQUFPLFNBQVMsQ0FDZCxHQUFHLEVBQUUsVUFBVSxDQUFDLFNBQVMsRUFDekIsd0NBQXdDLE9BQU8sR0FBRyxFQUNsRCxTQUFTLENBQUMsd0JBQXdCLENBQ25DLENBQUM7UUFDSixDQUFDO1FBRUQsSUFBSSxFQUFFLENBQUM7SUFDVCxDQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7QUFDbkMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIEF1dGhvcml6YXRpb24gZ2F0ZXMg4oCUIHBlcm1pc3Npb25zLCBzeXN0ZW0gYWRtaW4sIGZlYXR1cmVzIOKAlCBhbmQgdGhlXG4gKiBgYXV0aHouZGVuaWVkYCBhdWRpdCBzaW5rIHRoZXkgcmVwb3J0IHJlZnVzYWxzIHRvLlxuICovXG5cbmltcG9ydCB0eXBlIHsgUmVxdWVzdCwgUmVzcG9uc2UsIE5leHRGdW5jdGlvbiB9IGZyb20gJ2V4cHJlc3MnO1xuaW1wb3J0IHsgdGFnUm91dGVHYXRlIH0gZnJvbSAnLi9yb3V0ZS10YWJsZS5qcyc7XG5pbXBvcnQgeyBIdHRwU3RhdHVzIH0gZnJvbSAnLi4vY29uc3RhbnRzL2h0dHAtc3RhdHVzLmpzJztcbmltcG9ydCB7IHNlcnZpY2VJZGVudGl0eSB9IGZyb20gJy4uL3NlcnZpY2VzL3NlcnZpY2Uta2V5cy5qcyc7XG5pbXBvcnQgeyBFcnJvckNvZGUgfSBmcm9tICcuLi90eXBlcy9lcnJvci1jb2Rlcy5qcyc7XG5pbXBvcnQgeyB0eXBlIFBlcm1pc3Npb24sIGhhc1Blcm1pc3Npb24gfSBmcm9tICcuLi90eXBlcy9wZXJtaXNzaW9ucy5qcyc7XG5pbXBvcnQgeyBlbWl0Q291bnRlciB9IGZyb20gJy4uL3V0aWxzL21ldHJpYy1lbWl0dGVyLmpzJztcbmltcG9ydCB7IHNlbmRFcnJvciB9IGZyb20gJy4uL3V0aWxzL3Jlc3BvbnNlLmpzJztcbmltcG9ydCB7IGlzU2VydmljZVByaW5jaXBhbCwgc2VydmljZU5hbWVPZiB9IGZyb20gJy4vc2VydmljZS10b2tlbnMuanMnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdhdXRoLW1pZGRsZXdhcmUnKTtcbi8qKlxuICogV2hldGhlciB0aGUgcmVxdWVzdCdzIHVzZXIgaG9sZHMgYHBlcm1pc3Npb25gLiBTdXBlcmFkbWlucyBpbXBsaWNpdGx5IGhvbGRcbiAqIGV2ZXJ5IHBlcm1pc3Npb24uIFJlYWRzIHRoZSByZXNvbHZlZCBgcGVybWlzc2lvbnNgIGNsYWltIChzZXQgYXQgdG9rZW4gaXNzdWUsXG4gKiBvciByZS1kZXJpdmVkIHBlciByZXF1ZXN0IGJ5IHRoZSBwbGF0Zm9ybSkuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB1c2VySGFzUGVybWlzc2lvbihyZXE6IFJlcXVlc3QsIHBlcm1pc3Npb246IFBlcm1pc3Npb24pOiBib29sZWFuIHtcbiAgcmV0dXJuIGhhc1Blcm1pc3Npb24ocmVxLnVzZXI/LnBlcm1pc3Npb25zLCBwZXJtaXNzaW9uLCByZXEudXNlcj8uaXNTdXBlckFkbWluKTtcbn1cblxuLyoqXG4gKiBDb250ZXh0IHBhc3NlZCB0byBhIHJlZ2lzdGVyZWQgYXV0aG9yaXphdGlvbi1kZW5pYWwgYXVkaXRvciB3aGVuIGFcbiAqIHN0YXRlLWNoYW5naW5nIHJlcXVlc3QgaXMgcmVqZWN0ZWQgYnkgYHJlcXVpcmVQZXJtaXNzaW9uYCAvXG4gKiBgcmVxdWlyZVN5c3RlbUFkbWluYC5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBBdXRoekRlbmlhbEluZm8ge1xuICAvKiogVGhlIGRlbmllZCB1c2VyJ3MgaWQgKGBzdWJgKSwgaWYgYXV0aGVudGljYXRlZC4gKi9cbiAgYWN0b3JJZD86IHN0cmluZztcbiAgYWN0b3JFbWFpbD86IHN0cmluZztcbiAgLyoqIFRoZSB1c2VyJ3MgYWN0aXZlIG9yZyBhdCBkZW5pYWwgdGltZS4gKi9cbiAgb3JnSWQ/OiBzdHJpbmc7XG4gIC8qKiBIVFRQIG1ldGhvZCArIHBhdGggb2YgdGhlIHJlamVjdGVkIHJlcXVlc3QuICovXG4gIG1ldGhvZDogc3RyaW5nO1xuICBwYXRoOiBzdHJpbmc7XG4gIC8qKiBXaGF0IHdhcyByZXF1aXJlZDogdGhlIG1pc3NpbmcgcGVybWlzc2lvbihzKSwgb3IgJ3N5c3RlbS1hZG1pbicuICovXG4gIHJlcXVpcmVkOiBzdHJpbmc7XG59XG5cbi8qKlxuICogT3B0aW9uYWwgc2luayBmb3IgZGVuaWVkLWF1dGhvcml6YXRpb24gZXZlbnRzLiBMZWZ0IHVuc2V0IGJ5IGRlZmF1bHQgc29cbiAqIGFwaS1jb3JlIHN0YXlzIGRlY291cGxlZCBmcm9tIGFueSBhdWRpdCB0cmFuc3BvcnQg4oCUIGEgc2VydmljZSByZWdpc3RlcnMgb25lXG4gKiBhdCBib290ICh0eXBpY2FsbHkgZm9yd2FyZGluZyB0byBpdHMgYFJlbW90ZUF1ZGl0Q2xpZW50YCBhcyBhblxuICogYGF1dGh6LmRlbmllZGAgZXZlbnQsIG9yLCBvbiBwbGF0Zm9ybSwgdG8gdGhlIGxvY2FsIGBhdWRpdCgpYCBoZWxwZXIpLlxuICogTVVTVCBiZSBiZXN0LWVmZm9ydDogaXQgaXMgaW52b2tlZCBpbnNpZGUgdGhlIHJlcXVlc3QgcGF0aCwgc28gaXQgbXVzdCBuZXZlclxuICogdGhyb3cgb3IgYmxvY2sgKHRoZSBnYXRlIHdyYXBzIHRoZSBjYWxsIGluIHRyeS9jYXRjaCByZWdhcmRsZXNzKS5cbiAqL1xubGV0IGF1dGh6RGVuaWFsQXVkaXRvcjogKChpbmZvOiBBdXRoekRlbmlhbEluZm8pID0+IHZvaWQpIHwgdW5kZWZpbmVkO1xuXG4vKipcbiAqIFJlZ2lzdGVyIChvciBjbGVhciwgd2l0aCBgdW5kZWZpbmVkYCkgdGhlIHByb2Nlc3Mtd2lkZSBhdXRob3JpemF0aW9uLWRlbmlhbFxuICogYXVkaXRvci4gSWRlbXBvdGVudDsgdGhlIGxhc3QgcmVnaXN0cmF0aW9uIHdpbnMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzZXRBdXRoekRlbmlhbEF1ZGl0b3IoZm46ICgoaW5mbzogQXV0aHpEZW5pYWxJbmZvKSA9PiB2b2lkKSB8IHVuZGVmaW5lZCk6IHZvaWQge1xuICBhdXRoekRlbmlhbEF1ZGl0b3IgPSBmbjtcbn1cblxuLyoqXG4gKiBFbWl0IGEgZGVuaWFsIGV2ZW50LCBiZXN0LWVmZm9ydC4gT25seSBmaXJlcyBmb3Igc3RhdGUtY2hhbmdpbmcgKG5vbi1HRVQsXG4gKiBub24tSEVBRC9PUFRJT05TKSByZXF1ZXN0cyDigJQgYSByZWplY3RlZCBHRVQgaXMgbG93LXNpZ25hbCBwcm9iaW5nIG5vaXNlIGFuZFxuICogd291bGQgYW1wbGlmeSBhdWRpdCB2b2x1bWUgdW5kZXIgYSBzY2FuLiBOZXZlciB0aHJvd3MuXG4gKlxuICogVGhlIHBlcm1pc3Npb24gZ2F0ZXMgaGVyZSBjYWxsIGl0IHRoZW1zZWx2ZXMuIEV4cG9ydCBpdCBmb3IgZGF0YS1kcml2ZW4gY2hlY2tzXG4gKiBhIHJvdXRlIG1ha2VzIGlubGluZSAoY3Jvc3Mtb3JnIGFjY2Vzcywgc2VydmljZS1vbmx5IGVuZHBvaW50cyksIHNvIHRob3NlIDQwM3NcbiAqIHJlYWNoIHRoZSBzYW1lIGBhdXRoei5kZW5pZWRgIHRyYWlsLiBgcmVxdWlyZWRgIG5hbWVzIHdoYXQgd2FzIG1pc3NpbmcuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZWNvcmRBdXRoekRlbmlhbChyZXE6IFJlcXVlc3QsIHJlcXVpcmVkOiBzdHJpbmcpOiB2b2lkIHtcbiAgY29uc3QgYXVkaXRvciA9IGF1dGh6RGVuaWFsQXVkaXRvcjtcbiAgaWYgKCFhdWRpdG9yKSByZXR1cm47XG4gIGNvbnN0IG1ldGhvZCA9IHJlcS5tZXRob2QudG9VcHBlckNhc2UoKTtcbiAgaWYgKG1ldGhvZCA9PT0gJ0dFVCcgfHwgbWV0aG9kID09PSAnSEVBRCcgfHwgbWV0aG9kID09PSAnT1BUSU9OUycpIHJldHVybjtcbiAgdHJ5IHtcbiAgICBhdWRpdG9yKHtcbiAgICAgIGFjdG9ySWQ6IHJlcS51c2VyPy5zdWIsXG4gICAgICBhY3RvckVtYWlsOiByZXEudXNlcj8uZW1haWwsXG4gICAgICBvcmdJZDogcmVxLnVzZXI/Lm9yZ2FuaXphdGlvbklkLFxuICAgICAgbWV0aG9kLFxuICAgICAgLy8gU3RyaXAgdGhlIHF1ZXJ5IHN0cmluZyDigJQgYSBkZW5pZWQgcmVxdWVzdCBtYXkgY2FycnkgYSB0b2tlbi9zZWNyZXQgYXMgYVxuICAgICAgLy8gcXVlcnkgcGFyYW0sIGFuZCB0aGlzIHBhdGggaXMgcGVyc2lzdGVkIHZlcmJhdGltIGludG8gdGhlIGF1ZGl0IGxvZy5cbiAgICAgIHBhdGg6IChyZXEub3JpZ2luYWxVcmwgfHwgcmVxLnVybCkuc3BsaXQoJz8nKVswXSxcbiAgICAgIHJlcXVpcmVkLFxuICAgIH0pO1xuICB9IGNhdGNoIHtcbiAgICAvLyBCZXN0LWVmZm9ydCDigJQgYSBicm9rZW4gYXVkaXRvciBtdXN0IG5ldmVyIGJyZWFrIHRoZSBhdXRoIGdhdGUuXG4gIH1cbn1cblxuLyoqIFRoZSBzaGFyZWQgZ2F0ZSBwcmVhbWJsZTogYW5zd2VyIDQwMSBhbmQgcmV0dXJuIGZhbHNlIHdoZW4gdW5hdXRoZW50aWNhdGVkLiAqL1xuZnVuY3Rpb24gcmVxdWlyZVVzZXIocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlKTogcmVxIGlzIFJlcXVlc3QgJiB7IHVzZXI6IE5vbk51bGxhYmxlPFJlcXVlc3RbJ3VzZXInXT4gfSB7XG4gIGlmIChyZXEudXNlcikgcmV0dXJuIHRydWU7XG4gIHNlbmRFcnJvcihyZXMsIEh0dHBTdGF0dXMuVU5BVVRIT1JJWkVELCAnQXV0aGVudGljYXRpb24gcmVxdWlyZWQnLCBFcnJvckNvZGUuVU5BVVRIT1JJWkVEKTtcbiAgcmV0dXJuIGZhbHNlO1xufVxuXG4vKiogQXVkaXQgKyBhbnN3ZXIgYSBwZXJtaXNzaW9uIHJlZnVzYWwgKGBtaXNzaW5nYCBkZWZhdWx0cyB0byBldmVyeSByZXF1aXJlZCBwZXJtaXNzaW9uKS4gKi9cbmZ1bmN0aW9uIGRlbnlQZXJtaXNzaW9uKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgcGVybWlzc2lvbnM6IFBlcm1pc3Npb25bXSwgbW9kZTogJ29yJyB8ICdhbmQnLCBtaXNzaW5nOiBQZXJtaXNzaW9uW10gPSBwZXJtaXNzaW9ucyk6IHZvaWQge1xuICByZWNvcmRBdXRoekRlbmlhbChyZXEsIHBlcm1pc3Npb25zLmpvaW4oYCAke21vZGV9IGApKTtcbiAgc2VuZEVycm9yKFxuICAgIHJlcywgSHR0cFN0YXR1cy5GT1JCSURERU4sXG4gICAgYE1pc3NpbmcgcmVxdWlyZWQgcGVybWlzc2lvbjogJHttaXNzaW5nLmpvaW4oYCAke21vZGV9IGApfWAsXG4gICAgRXJyb3JDb2RlLklOU1VGRklDSUVOVF9QRVJNSVNTSU9OUyxcbiAgKTtcbn1cblxuLyoqXG4gKiBSZXF1aXJlcyB0aGF0IHRoZSB1c2VyIGhvbGQgQVQgTEVBU1QgT05FIG9mIHRoZSBnaXZlbiBwZXJtaXNzaW9ucyAob3IgYmUgYVxuICogc3VwZXJhZG1pbikuIFVzZSBhZnRlciByZXF1aXJlQXV0aC4gTWlycm9ycyBgcmVxdWlyZVJvbGVgJ3MgYW55LW9mIHNlbWFudGljcztcbiAqIHBhc3MgYSBzaW5nbGUgcGVybWlzc2lvbiBmb3IgYSBzcGVjaWZpYyBhY3Rpb24sIG9yIHNldmVyYWwgd2hlbiBhbnkgb25lIG9mXG4gKiB0aGVtIHNob3VsZCBncmFudCBhY2Nlc3MuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXF1aXJlUGVybWlzc2lvbiguLi5wZXJtaXNzaW9uczogUGVybWlzc2lvbltdKSB7XG4gIHJldHVybiB0YWdSb3V0ZUdhdGUoKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKTogdm9pZCA9PiB7XG4gICAgaWYgKCFyZXF1aXJlVXNlcihyZXEsIHJlcykpIHJldHVybjtcbiAgICAvLyB1c2VySGFzUGVybWlzc2lvbiDihpIgaGFzUGVybWlzc2lvbiBhbHJlYWR5IGdyYW50cyBzdXBlcmFkbWlucyBldmVyeVxuICAgIC8vIHBlcm1pc3Npb24sIHNvIHRoaXMgc2luZ2xlIGNoZWNrIGNvdmVycyBib3RoIHRoZSBzdXBlcmFkbWluIGJ5cGFzcyBhbmRcbiAgICAvLyB0aGUgYW55LW9mIG1lbWJlcnNoaXAgdGVzdCB3aXRob3V0IHJlLWlubGluaW5nIGVpdGhlci5cbiAgICBpZiAocGVybWlzc2lvbnMuc29tZSgocCkgPT4gdXNlckhhc1Blcm1pc3Npb24ocmVxLCBwKSkpIHJldHVybiBuZXh0KCk7XG4gICAgZGVueVBlcm1pc3Npb24ocmVxLCByZXMsIHBlcm1pc3Npb25zLCAnb3InKTtcbiAgfSwgeyBraW5kOiAncGVybWlzc2lvbicsIG1vZGU6ICdhbnknLCBwZXJtaXNzaW9ucyB9KTtcbn1cblxuLyoqXG4gKiBSZXF1aXJlcyB0aGF0IHRoZSB1c2VyIGhvbGQgRVZFUlkgb25lIG9mIHRoZSBnaXZlbiBwZXJtaXNzaW9ucyAob3IgYmUgYVxuICogc3VwZXJhZG1pbikg4oCUIHRoZSBBTkQgY291bnRlcnBhcnQgdG8gYHJlcXVpcmVQZXJtaXNzaW9uYCdzIGFueS1vZi4gVXNlIGZvciBhXG4gKiBzZW5zaXRpdmUgYWN0aW9uIHRoYXQgbGVnaXRpbWF0ZWx5IGRlbWFuZHMgdHdvIGRpc3RpbmN0IGNhcGFiaWxpdGllcyBhdCBvbmNlXG4gKiAoZS5nLiBhIGNyb3NzLXN1cmZhY2Ugb3BlcmF0aW9uKSwgc28gdGhlIGd1YXJhbnRlZSBpcyBleHBsaWNpdCByYXRoZXIgdGhhblxuICogYXBwcm94aW1hdGVkIGJ5IGNoYWluaW5nIHR3byBgcmVxdWlyZVBlcm1pc3Npb25gIGdhdGVzLiBTdXBlcmFkbWlucyBieXBhc3NcbiAqICh0aGV5IGltcGxpY2l0bHkgaG9sZCBldmVyeSBwZXJtaXNzaW9uKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlcXVpcmVBbGxQZXJtaXNzaW9ucyguLi5wZXJtaXNzaW9uczogUGVybWlzc2lvbltdKSB7XG4gIHJldHVybiB0YWdSb3V0ZUdhdGUoKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKTogdm9pZCA9PiB7XG4gICAgaWYgKCFyZXF1aXJlVXNlcihyZXEsIHJlcykpIHJldHVybjtcbiAgICBjb25zdCBtaXNzaW5nID0gcGVybWlzc2lvbnMuZmlsdGVyKChwKSA9PiAhdXNlckhhc1Blcm1pc3Npb24ocmVxLCBwKSk7XG4gICAgaWYgKG1pc3NpbmcubGVuZ3RoID09PSAwKSByZXR1cm4gbmV4dCgpO1xuICAgIGRlbnlQZXJtaXNzaW9uKHJlcSwgcmVzLCBwZXJtaXNzaW9ucywgJ2FuZCcsIG1pc3NpbmcpO1xuICB9LCB7IGtpbmQ6ICdwZXJtaXNzaW9uJywgbW9kZTogJ2FsbCcsIHBlcm1pc3Npb25zIH0pO1xufVxuXG4vKipcbiAqIExpa2Uge0BsaW5rIHJlcXVpcmVQZXJtaXNzaW9ufSAoYW55LW9mKSBidXQgQUxTTyBhZG1pdHMgYW4gaW50ZXJuYWwgc2VydmljZVxuICogcHJpbmNpcGFsIChgcHJpbmNpcGFsVHlwZTogJ3NlcnZpY2UnYCkuIEZvciBSRUFEIGVuZHBvaW50cyB0aGF0IEJPVEhcbiAqIGludGVyYWN0aXZlIHVzZXJzIOKAlCB3aG8gbXVzdCBob2xkIG9uZSBvZiB0aGUgYDpyZWFkYCBjYXBhYmlsaXRpZXMg4oCUIEFORFxuICogc2VydmljZS10by1zZXJ2aWNlIGNhbGxlcnMgbGVnaXRpbWF0ZWx5IGhpdDsgc2VydmljZSB0b2tlbnMgY2Fycnkgbm9cbiAqIHBlcm1pc3Npb24gY2xhaW1zICh0aGV5J3JlIGxlYXN0LXByaXZpbGVnZSBgcm9sZTptZW1iZXJgKSwgc28gYSBwbGFpblxuICogYHJlcXVpcmVQZXJtaXNzaW9uYCB3b3VsZCB3cm9uZ2x5IDQwMyB0aGVtLiBTdXBlcmFkbWlucyBwYXNzIHZpYVxuICogYHVzZXJIYXNQZXJtaXNzaW9uYCdzIGltcGxpY2l0LWFsbC4gVXNlIG9ubHkgd2hlcmUgYSBzZXJ2aWNlIGNhbGxlciBpcyBhIGtub3duLFxuICogaW50ZW5kZWQgY29uc3VtZXIgb2YgdGhlIHJvdXRlICh2ZXJpZnkgdGhlIGNhbGxlcnMpOyBwcmVmZXIgcGxhaW5cbiAqIGByZXF1aXJlUGVybWlzc2lvbmAgZm9yIHB1cmVseSB1c2VyLWZhY2luZyByZWFkcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlcXVpcmVQZXJtaXNzaW9uT3JTZXJ2aWNlKC4uLnBlcm1pc3Npb25zOiBQZXJtaXNzaW9uW10pIHtcbiAgcmV0dXJuIHRhZ1JvdXRlR2F0ZSgocmVxOiBSZXF1ZXN0LCByZXM6IFJlc3BvbnNlLCBuZXh0OiBOZXh0RnVuY3Rpb24pOiB2b2lkID0+IHtcbiAgICBpZiAoIXJlcXVpcmVVc2VyKHJlcSwgcmVzKSkgcmV0dXJuO1xuICAgIGlmIChpc1NlcnZpY2VQcmluY2lwYWwocmVxKSB8fCBwZXJtaXNzaW9ucy5zb21lKChwKSA9PiB1c2VySGFzUGVybWlzc2lvbihyZXEsIHApKSkgcmV0dXJuIG5leHQoKTtcbiAgICBkZW55UGVybWlzc2lvbihyZXEsIHJlcywgcGVybWlzc2lvbnMsICdvcicpO1xuICB9LCB7IGtpbmQ6ICdwZXJtaXNzaW9uJywgbW9kZTogJ2FueScsIHBlcm1pc3Npb25zLCBhbGxvd1NlcnZpY2U6IHRydWUgfSk7XG59XG5cbi8qKlxuICogR2F0ZSBhbiBlbmRwb2ludCB0byBpbnRlcm5hbCBzZXJ2aWNlLXRvLXNlcnZpY2UgY2FsbGVycyBvbmx5IChhXG4gKiBgcHJpbmNpcGFsVHlwZTogJ3NlcnZpY2UnYCBwcmluY2lwYWwgbWludGVkIHZpYSB7QGxpbmsgc2lnblNlcnZpY2VUb2tlbn0gLyBgZ2V0U2VydmljZUF1dGhIZWFkZXJgKS5cbiAqIFJlamVjdHMgYW55IHVzZXIgdG9rZW4uIEZvciBOT04tdXNlci1mYWNpbmcgZW5kcG9pbnRzIOKAlCBlbnRpdHktZXZlbnQgaW5nZXN0LFxuICogYXV0by1zdWJzY3JpYmUsIGFuZCBzaW1pbGFyIHBlZXItc2VydmljZSBob29rcyDigJQgdGhhdCBhIGJyb3dzZXIgbXVzdCBuZXZlclxuICogcmVhY2guIENvbXBvc2UgYWZ0ZXIgYHJlcXVpcmVBdXRoYCBzbyBgcmVxLnVzZXJgIGlzIHBvcHVsYXRlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlcXVpcmVTZXJ2aWNlUHJpbmNpcGFsKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKTogdm9pZCB7XG4gIGlmICghaXNTZXJ2aWNlUHJpbmNpcGFsKHJlcSkpIHtcbiAgICAvLyA0MDMgKG5vdCA0MDApIOKAlCB0aGlzIGlzIGFuIGF1dGhvcml6YXRpb24gcmVmdXNhbDsgc3RhdHVzIG11c3QgbWF0Y2ggdGhlXG4gICAgLy8gSU5TVUZGSUNJRU5UX1BFUk1JU1NJT05TIGNvZGUgYW5kIHRoZSBzaWJsaW5nIGdhdGVzIChyZXF1aXJlUGVybWlzc2lvbiwgZXRjLikuXG4gICAgcmV0dXJuIHNlbmRFcnJvcihyZXMsIEh0dHBTdGF0dXMuRk9SQklEREVOLCAnSW50ZXJuYWwgc2VydmljZSBjYWxscyBvbmx5JywgRXJyb3JDb2RlLklOU1VGRklDSUVOVF9QRVJNSVNTSU9OUyk7XG4gIH1cbiAgbmV4dCgpO1xufVxudGFnUm91dGVHYXRlKHJlcXVpcmVTZXJ2aWNlUHJpbmNpcGFsLCB7IGtpbmQ6ICdzZXJ2aWNlUHJpbmNpcGFsJyB9KTtcblxuLyoqXG4gKiBUSEUgZ2F0ZSBmb3IgYW4gSU5URVJOQUwgcm91dGUg4oCUIHRoZSBvbmUgZXZlcnkgYC9pbnRlcm5hbC8qYCBwYXRoLCB0aGVcbiAqIHF1b3RhIHVzYWdlIGNvdW50ZXJzLCB0aGUgZW50aXR5LWV2ZW50IGluZ2VzdCBhbmQgdGhlIGF1ZGl0IGluZ2VzdCBnbyB0aHJvdWdoLlxuICpcbiAqIFR3byBydWxlcywgYm90aCBmYWlsLWNsb3NlZDpcbiAqXG4gKiAgMS4gKipObyB1c2VyIHRva2VuLCBldmVyLioqIE5vdCBhIG1lbWJlcidzLCBub3QgYSBzdXBlcmFkbWluJ3MsIG5vdCBhXG4gKiAgICAgc2VydmljZS1hY2NvdW50IGtleSdzLiBBbiBpbnRlcm5hbCByb3V0ZSBpcyBhIHBlZXItc2VydmljZSBBUEkgc3VyZmFjZSwgc29cbiAqICAgICBcImEgc3VmZmljaWVudGx5IHByaXZpbGVnZWQgaHVtYW5cIiBpcyBub3QgYW4gYWNjZXB0YWJsZSBjYWxsZXIg4oCUIHRoYXRcbiAqICAgICBlcXVpdmFsZW5jZSBpcyBleGFjdGx5IGhvdyBhIGJyb3dzZXItcmVhY2hhYmxlIHBhdGggYmVjb21lcyBhIHRlbmFudFxuICogICAgIGJvdW5kYXJ5IGJ1Zy4gYHJlcXVpcmVTZXJ2aWNlUHJpbmNpcGFsYCBhbHJlYWR5IHJlZnVzZWQgdXNlcnM7IHRoaXMgYWxzb1xuICogICAgIGNsb3NlcyB0aGUgYHx8IGlzU3lzdGVtQWRtaW4ocmVxKWAgZXNjYXBlIGhhdGNoZXMgdGhhdCBoYWQgZ3Jvd24gYXJvdW5kXG4gKiAgICAgdGhlIGluZGl2aWR1YWwgaW50ZXJuYWwgcm91dGVzLlxuICogIDIuICoqT25seSB0aGUgTkFNRUQgY2FsbGVycy4qKiBgY2FsbGVyc2AgbGlzdHMgdGhlIHNlcnZpY2VzIHRoYXQgbGVnaXRpbWF0ZWx5XG4gKiAgICAgY2FsbCB0aGlzIHJvdXRlLCBhbmQgdGhlIG5hbWUgaXMgY3J5cHRvZ3JhcGhpY2FsbHkgYm91bmQgdG8gdGhlIHNpZ25pbmdcbiAqICAgICBrZXkgKGBzZXJ2aWNlcy9zZXJ2aWNlLWtleXMudHNgKSwgc28gdGhpcyBpcyBhbiBpZGVudGl0eSBjaGVjayByYXRoZXIgdGhhblxuICogICAgIGEgY2xhaW0gY2hlY2suIEl0IGlzIHRoZSBzYW1lIGFsbG93LWxpc3QgdGhlIG1lc2ggcG9saWN5IG5hbWVzLCBhdCB0aGVcbiAqICAgICBsYXllciB0aGF0IGFsc28gaG9sZHMgaW4gZG9ja2VyIGNvbXBvc2UsIHdoZXJlIHRoZXJlIGlzIG5vIG1lc2ggYXQgYWxsIOKAlFxuICogICAgIHRoZSBJc3RpbyBgQXV0aG9yaXphdGlvblBvbGljeWAgaXMgZGVmZW5jZSBpbiBkZXB0aCBvbiB0b3AsIG5ldmVyIHRoZVxuICogICAgIGVuZm9yY2VtZW50LlxuICpcbiAqIENvbXBvc2UgYWZ0ZXIgYHJlcXVpcmVBdXRoYCBzbyBgcmVxLnVzZXJgIGlzIHBvcHVsYXRlZC4gUmVmdXNhbHMgYXJlIGNvdW50ZWRcbiAqIChgaW50ZXJuYWxfcm91dGVfcmVmdXNlZF90b3RhbGApIGFuZCBhdWRpdGVkIHRocm91Z2ggdGhlIHNoYXJlZCBgYXV0aHouZGVuaWVkYFxuICogc2luay5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlcXVpcmVJbnRlcm5hbFNlcnZpY2Uob3B0aW9uczogeyBjYWxsZXJzOiByZWFkb25seSBzdHJpbmdbXSB9KSB7XG4gIGNvbnN0IGFsbG93ZWQgPSBuZXcgU2V0KG9wdGlvbnMuY2FsbGVycyk7XG4gIHJldHVybiB0YWdSb3V0ZUdhdGUoKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKTogdm9pZCA9PiB7XG4gICAgY29uc3QgcmVmdXNlID0gKHJlYXNvbjogJ3VuYXV0aGVudGljYXRlZCcgfCAndXNlcl90b2tlbicgfCAnd3JvbmdfY2FsbGVyJyk6IHZvaWQgPT4ge1xuICAgICAgZW1pdENvdW50ZXIoJ2ludGVybmFsX3JvdXRlX3JlZnVzZWRfdG90YWwnLCB7XG4gICAgICAgIHNlcnZpY2U6IHNlcnZpY2VJZGVudGl0eSgpLFxuICAgICAgICByb3V0ZTogcmVxLnJvdXRlPy5wYXRoID8gYCR7cmVxLm1ldGhvZH0gJHtyZXEuYmFzZVVybH0ke3JlcS5yb3V0ZS5wYXRofWAgOiBgJHtyZXEubWV0aG9kfSAkeyhyZXEub3JpZ2luYWxVcmwgfHwgcmVxLnVybCkuc3BsaXQoJz8nKVswXX1gLFxuICAgICAgICByZWFzb24sXG4gICAgICAgIGNhbGxlcjogc2VydmljZU5hbWVPZihyZXEudXNlcikgPz8gJ25vbmUnLFxuICAgICAgfSk7XG4gICAgICByZWNvcmRBdXRoekRlbmlhbChyZXEsIGBpbnRlcm5hbC1zZXJ2aWNlICgke29wdGlvbnMuY2FsbGVycy5qb2luKCcsICcpfSlgKTtcbiAgICAgIGlmIChyZWFzb24gPT09ICd1bmF1dGhlbnRpY2F0ZWQnKSB7XG4gICAgICAgIHJldHVybiBzZW5kRXJyb3IocmVzLCBIdHRwU3RhdHVzLlVOQVVUSE9SSVpFRCwgJ0F1dGhlbnRpY2F0aW9uIHJlcXVpcmVkJywgRXJyb3JDb2RlLlVOQVVUSE9SSVpFRCk7XG4gICAgICB9XG4gICAgICBsb2dnZXIud2FybignUmVmdXNlZCBhbiBpbnRlcm5hbCByb3V0ZScsIHsgcmVhc29uLCBwYXRoOiAocmVxLm9yaWdpbmFsVXJsIHx8IHJlcS51cmwpLnNwbGl0KCc/JylbMF0sIGNhbGxlcjogc2VydmljZU5hbWVPZihyZXEudXNlcikgfSk7XG4gICAgICByZXR1cm4gc2VuZEVycm9yKHJlcywgSHR0cFN0YXR1cy5GT1JCSURERU4sICdJbnRlcm5hbCBzZXJ2aWNlIGNhbGxzIG9ubHknLCBFcnJvckNvZGUuSU5TVUZGSUNJRU5UX1BFUk1JU1NJT05TKTtcbiAgICB9O1xuXG4gICAgaWYgKCFyZXEudXNlcikgcmV0dXJuIHJlZnVzZSgndW5hdXRoZW50aWNhdGVkJyk7XG4gICAgaWYgKCFpc1NlcnZpY2VQcmluY2lwYWwocmVxKSkgcmV0dXJuIHJlZnVzZSgndXNlcl90b2tlbicpO1xuICAgIGNvbnN0IGNhbGxlciA9IHNlcnZpY2VOYW1lT2YocmVxLnVzZXIpO1xuICAgIGlmICghY2FsbGVyIHx8ICFhbGxvd2VkLmhhcyhjYWxsZXIpKSByZXR1cm4gcmVmdXNlKCd3cm9uZ19jYWxsZXInKTtcbiAgICBuZXh0KCk7XG4gIH0sIHsga2luZDogJ3NlcnZpY2VQcmluY2lwYWwnIH0sIHsga2luZDogJ2ludGVybmFsU2VydmljZScsIGNhbGxlcnM6IFsuLi5vcHRpb25zLmNhbGxlcnNdIH0pO1xufVxuXG4vKipcbiAqIENoZWNrIGlmIHRoZSByZXF1ZXN0IGlzIGZyb20gYSBzeXN0ZW0gYWRtaW4uXG4gKlxuICogQXV0aG9yaXR5IGlzIGdyYW50ZWQgc29sZWx5IGJ5IHRoZSB1c2VyLWxldmVsIGBpc1N1cGVyQWRtaW5gIGZsYWcgY2FycmllZFxuICogaW4gdGhlIEpXVCDigJQgbmV2ZXIgYnkgbWVtYmVyc2hpcCBpbiB0aGUgd2VsbC1rbm93biAnc3lzdGVtJyBvcmcsIHdoaWNoIHdvdWxkXG4gKiBjb25mbGF0ZSBhIFBpcGVsaW5lIEJ1aWxkZXIgb3BlcmF0b3Igd2l0aCBhIGN1c3RvbWVyIHRlbmFudCBhbmQgbGV0IGFueVxuICogd3JpdGUgdGhhdCBjcmVhdGVkIGEgJ3N5c3RlbSctbmFtZWQgb3JnIHF1aWV0bHkgZ3JhbnQgY3Jvc3Mtb3JnIGF1dGhvcml0eS4gVGhlIGBzeXN0ZW1gIG9yZyBzdGlsbCBleGlzdHMgYXMgYSAqY29udGVudCBob2xkZXIqIGZvciBzaGFyZWRcbiAqIHNhbXBsZSBkYXRhOyBpdCBjb25mZXJzIG5vIHByaXZpbGVnZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzU3lzdGVtQWRtaW4ocmVxOiBSZXF1ZXN0KTogYm9vbGVhbiB7XG4gIHJldHVybiByZXEudXNlcj8uaXNTdXBlckFkbWluID09PSB0cnVlO1xufVxuXG4vKipcbiAqIFdoZXRoZXIgdGhlIHJlcXVlc3QncyB0b2tlbiBjYXJyaWVzIGEgc3BlY2lmaWMgY2FwYWJpbGl0eSBgc2NvcGVgIChlLmcuXG4gKiBgJ3JlcG9ydGluZzppbmdlc3QnYCkuIFVzZWQgdG8gZ2F0ZSBlbmRwb2ludHMgdGhhdCBhY2NlcHQgYSBuYXJyb3cgbWFjaGluZVxuICogaWRlbnRpdHkg4oCUIGEgbm9ybWFsIGludGVyYWN0aXZlIHVzZXIgdG9rZW4gaGFzIG5vIGBzY29wZWAgYW5kIHJldHVybnMgZmFsc2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBoYXNTY29wZShyZXE6IFJlcXVlc3QsIHNjb3BlOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgcmV0dXJuIHJlcS51c2VyPy5zY29wZSA9PT0gc2NvcGU7XG59XG5cbi8qKiBSZXF1aXJlcyBhIHN5c3RlbSBhZG1pbiDigJQgZ3JhbnRlZCBzb2xlbHkgYnkgdGhlIGBpc1N1cGVyQWRtaW5gIHRva2VuIGNsYWltLlxuICogIFVzZSBhZnRlciByZXF1aXJlQXV0aC4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXF1aXJlU3lzdGVtQWRtaW4oXG4gIHJlcTogUmVxdWVzdCxcbiAgcmVzOiBSZXNwb25zZSxcbiAgbmV4dDogTmV4dEZ1bmN0aW9uLFxuKTogdm9pZCB7XG4gIGlmICghaXNTeXN0ZW1BZG1pbihyZXEpKSB7XG4gICAgcmVjb3JkQXV0aHpEZW5pYWwocmVxLCAnc3lzdGVtLWFkbWluJyk7XG4gICAgcmV0dXJuIHNlbmRFcnJvcihcbiAgICAgIHJlcywgSHR0cFN0YXR1cy5GT1JCSURERU4sXG4gICAgICAnQWNjZXNzIGRlbmllZC4gT25seSBzeXN0ZW0gYWRtaW5pc3RyYXRvcnMgY2FuIHBlcmZvcm0gdGhpcyBhY3Rpb24uJyxcbiAgICAgIEVycm9yQ29kZS5JTlNVRkZJQ0lFTlRfUEVSTUlTU0lPTlMsXG4gICAgKTtcbiAgfVxuICBuZXh0KCk7XG59XG50YWdSb3V0ZUdhdGUocmVxdWlyZVN5c3RlbUFkbWluLCB7IGtpbmQ6ICdzeXN0ZW1BZG1pbicgfSk7XG5cbi8qKlxuICogUmVxdWlyZSBhIHNwZWNpZmljIGZlYXR1cmUgZmxhZy4gVXNlIGFmdGVyIHJlcXVpcmVBdXRoLlxuICogQ2hlY2tzIHRoZSBgZmVhdHVyZXNgIGFycmF5IGluIHRoZSBKV1QgcGF5bG9hZCAoc2V0IGF0IHRva2VuIGlzc3VhbmNlKS5cbiAqIFN5c2FkbWlucyAoaXNTdXBlckFkbWluKSBieXBhc3Mg4oCUIHRoZXkgYWx3YXlzIGhhdmUgZXZlcnkgZmVhdHVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlcXVpcmVGZWF0dXJlKGZlYXR1cmU6IHN0cmluZykge1xuICByZXR1cm4gdGFnUm91dGVHYXRlKChyZXE6IFJlcXVlc3QsIHJlczogUmVzcG9uc2UsIG5leHQ6IE5leHRGdW5jdGlvbik6IHZvaWQgPT4ge1xuICAgIGlmICghcmVxdWlyZVVzZXIocmVxLCByZXMpKSByZXR1cm47XG5cbiAgICAvLyBTeXNhZG1pbnMgZ2V0IGFsbCBmZWF0dXJlczsgdGhlIGBmZWF0dXJlc2AgYXJyYXkgb24gdGhlaXIgdG9rZW4gaXNcbiAgICAvLyBwb3B1bGF0ZWQgd2l0aCBldmVyeSBmbGFnIGF0IGlzc3VhbmNlIHRpbWUgYnV0IHRoZSBieXBhc3MgaGVyZSBrZWVwc1xuICAgIC8vIHRoZSBydWxlIGV4cGxpY2l0IGFuZCBzdXJ2aXZlcyBhIHRva2VuIGlzc3VlZCBiZWZvcmUgYSBuZXcgZmVhdHVyZVxuICAgIC8vIGZsYWcgd2FzIGFkZGVkLlxuICAgIGlmIChyZXEudXNlci5pc1N1cGVyQWRtaW4gPT09IHRydWUpIHJldHVybiBuZXh0KCk7XG5cbiAgICBpZiAoIXJlcS51c2VyLmZlYXR1cmVzPy5pbmNsdWRlcyhmZWF0dXJlKSkge1xuICAgICAgLy8gUm91dGUgZmVhdHVyZS1nYXRlIGRlbmlhbHMgdGhyb3VnaCB0aGUgc2FtZSBhdWRpdCBzaW5rIGFzXG4gICAgICAvLyByZXF1aXJlUGVybWlzc2lvbiAvIHJlcXVpcmVTeXN0ZW1BZG1pbiBzbyBhIHByb2JlIGZvciBhblxuICAgICAgLy8gdW5lbnRpdGxlZCBjYXBhYmlsaXR5IGxlYXZlcyBhIHRyYWlsIChzdGF0ZS1jaGFuZ2luZyBtZXRob2RzIG9ubHkpLlxuICAgICAgcmVjb3JkQXV0aHpEZW5pYWwocmVxLCBgZmVhdHVyZToke2ZlYXR1cmV9YCk7XG4gICAgICByZXR1cm4gc2VuZEVycm9yKFxuICAgICAgICByZXMsIEh0dHBTdGF0dXMuRk9SQklEREVOLFxuICAgICAgICBgVGhpcyBmZWF0dXJlIHJlcXVpcmVzIGEgaGlnaGVyIHBsYW4gKCR7ZmVhdHVyZX0pYCxcbiAgICAgICAgRXJyb3JDb2RlLklOU1VGRklDSUVOVF9QRVJNSVNTSU9OUyxcbiAgICAgICk7XG4gICAgfVxuXG4gICAgbmV4dCgpO1xuICB9LCB7IGtpbmQ6ICdmZWF0dXJlJywgZmVhdHVyZSB9KTtcbn1cblxuIl19
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Session invalidation: the token-version revocation store and the
3
+ * impersonation-session liveness check `requireAuth` consults after verifying a token.
4
+ */
5
+ import { type JwtPayload } from '../types/common.js';
6
+ /**
7
+ * A source of the CURRENT `tokenVersion` for a user, backed by a store the
8
+ * stateless services can read (in practice a Redis key the platform publishes
9
+ * to on every privilege change). Registered via {@link setTokenRevocationStore}.
10
+ *
11
+ * `getCurrentVersion` returns the user's current version, or `null` when the
12
+ * store has no entry — a miss is treated as "no known revocation" (fail-open):
13
+ * revocation entries are published with a TTL matching the access-token
14
+ * lifetime, so a missing entry means any token old enough to predate it has
15
+ * already expired on its own.
16
+ */
17
+ export interface TokenRevocationStore {
18
+ getCurrentVersion(userId: string): Promise<number | null>;
19
+ /**
20
+ * Whether one IMPERSONATION session (by token `jti`) has been ended early.
21
+ *
22
+ * Optional so a store that predates it still links — but a store WITHOUT it
23
+ * cannot answer, and an impersonation token is then REJECTED. Unlike
24
+ * `getCurrentVersion`, this is deliberately not fail-open: the platform ends a
25
+ * session when someone withdraws consent, and a withdrawn consent that keeps
26
+ * working in every other service is not withdrawn.
27
+ */
28
+ getSessionRevocation?(jti: string): Promise<SessionRevocationState>;
29
+ /**
30
+ * Whether ONE credential the token rides on has been revoked — its session
31
+ * slot (`sid`) or the access key it was exchanged from / derived from (see
32
+ * {@link credentialRevocationRefs}). Fail-open like `getCurrentVersion`: a
33
+ * store that can't answer returns `false`.
34
+ */
35
+ isCredentialRevoked?(refs: CredentialRevocationRefs): Promise<boolean>;
36
+ }
37
+ /** The single-credential ids a token can be revoked by (`revoke:sid:` / `revoke:key:`). */
38
+ export interface CredentialRevocationRefs {
39
+ /** The refresh-session slot the token was minted with. */
40
+ sid?: string;
41
+ /** Access-key ids: the exchanged key's own `jti`, and any `parentKeyId`. */
42
+ keyIds: string[];
43
+ }
44
+ /**
45
+ * The credential ids a verified token can be revoked by. An exchanged key token
46
+ * (`token_use: 'api_key'`) is named by its `jti`; a token derived from one
47
+ * carries `parentKeyId`; a session-minted token carries `sid`. Returns `null`
48
+ * when the token names none of them.
49
+ */
50
+ export declare function credentialRevocationRefs(claims: Pick<JwtPayload, 'sid' | 'parentKeyId' | 'jti' | 'token_use'>): CredentialRevocationRefs | null;
51
+ /**
52
+ * `revoked` — the session was ended; reject.
53
+ * `live` — no revocation recorded; allow.
54
+ * `unavailable` — the store couldn't be read or can't answer; REJECT. Distinct
55
+ * from `live` precisely so an outage can't read as "not revoked".
56
+ */
57
+ export type SessionRevocationState = 'revoked' | 'live' | 'unavailable';
58
+ /**
59
+ * Register (or clear, with `undefined`) the process-wide token-revocation store.
60
+ * When unset (the default), `requireAuth` performs NO revocation check (it relies
61
+ * on the short access-token TTL); services opt in at boot by wiring their Redis
62
+ * client. Platform keeps its own Mongo-backed check and need not register one.
63
+ */
64
+ export declare function setTokenRevocationStore(store: TokenRevocationStore | undefined): void;
65
+ /**
66
+ * Whether a request is a WRITE under a read-only impersonation token. Fail-closed
67
+ * on a missing method: only a request positively known to be a read passes.
68
+ */
69
+ export declare function isImpersonationWriteBlocked(method: string | undefined, claims: Pick<JwtPayload, 'impersonationReadOnly'>): boolean;
70
+ /**
71
+ * Public revocation check for routes that verify a platform JWT OUTSIDE
72
+ * `requireAuth` (e.g. the image-registry `/token` mint path, which resolves
73
+ * identity itself). Returns true when the token's `tokenVersion` is strictly
74
+ * behind the store's current version. Fail-open (never throws) — a store outage
75
+ * or a token/store without a usable version yields false (allow), matching
76
+ * `requireAuth`. Pass the verified JWT claims (`sub`, `tokenVersion`).
77
+ */
78
+ export declare function isAccessTokenRevoked(claims: {
79
+ sub?: string;
80
+ tokenVersion?: number;
81
+ jti?: string;
82
+ impersonatorId?: string;
83
+ sid?: string;
84
+ parentKeyId?: string;
85
+ token_use?: JwtPayload['token_use'];
86
+ }): Promise<boolean>;
87
+ /** Whether `requireAuth` must consult the revocation stores for this token. */
88
+ export declare function needsRevocationCheck(decoded: JwtPayload): boolean;
89
+ /**
90
+ * The post-verification session-invalidation check. Returns the refusal
91
+ * message, or `null` when the session is live. An impersonation session is
92
+ * checked even when NO store is registered: there, no store ⇒ unavailable ⇒
93
+ * refused (see `TokenRevocationStore.getSessionRevocation`); the tokenVersion
94
+ * check is a no-op without a store and fails open on a store error. Never rejects.
95
+ */
96
+ export declare function checkRevocation(decoded: JwtPayload): Promise<string | null>;
@@ -0,0 +1,162 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Session invalidation: the token-version revocation store and the
5
+ * impersonation-session liveness check `requireAuth` consults after verifying a token.
6
+ */
7
+ import {} from '../types/common.js';
8
+ import { createLogger } from '../utils/logger.js';
9
+ const logger = createLogger('auth-middleware');
10
+ /**
11
+ * The credential ids a verified token can be revoked by. An exchanged key token
12
+ * (`token_use: 'api_key'`) is named by its `jti`; a token derived from one
13
+ * carries `parentKeyId`; a session-minted token carries `sid`. Returns `null`
14
+ * when the token names none of them.
15
+ */
16
+ export function credentialRevocationRefs(claims) {
17
+ const keyIds = new Set();
18
+ if (claims.token_use === 'api_key' && typeof claims.jti === 'string' && claims.jti)
19
+ keyIds.add(claims.jti);
20
+ if (typeof claims.parentKeyId === 'string' && claims.parentKeyId)
21
+ keyIds.add(claims.parentKeyId);
22
+ const sid = typeof claims.sid === 'string' && claims.sid ? claims.sid : undefined;
23
+ if (!sid && keyIds.size === 0)
24
+ return null;
25
+ return { ...(sid ? { sid } : {}), keyIds: [...keyIds] };
26
+ }
27
+ let tokenRevocationStore;
28
+ /**
29
+ * Register (or clear, with `undefined`) the process-wide token-revocation store.
30
+ * When unset (the default), `requireAuth` performs NO revocation check (it relies
31
+ * on the short access-token TTL); services opt in at boot by wiring their Redis
32
+ * client. Platform keeps its own Mongo-backed check and need not register one.
33
+ */
34
+ export function setTokenRevocationStore(store) {
35
+ tokenRevocationStore = store;
36
+ if (store)
37
+ revocationStoreMissingWarned = false; // re-arm if a store is (re)registered
38
+ }
39
+ /** One-shot guard so the "revocation inactive" warning doesn't flood the logs. */
40
+ let revocationStoreMissingWarned = false;
41
+ /**
42
+ * Returns true when `decoded` has been revoked per the registered store: its
43
+ * embedded `tokenVersion` is strictly behind the store's current version.
44
+ * Fail-open — any store error, a missing entry, or a token/store without a
45
+ * usable version yields `false` (allow). Never throws.
46
+ */
47
+ async function isTokenRevoked(decoded) {
48
+ const store = tokenRevocationStore;
49
+ if (!store) {
50
+ // Make the "revocation is OFF" state OBSERVABLE: a service that verifies user
51
+ // tokens but forgot `setTokenRevocationStore()` silently disables the session
52
+ // kill-switch. Warn ONCE (only for a revocable user token) so a misconfigured
53
+ // boot surfaces in the logs instead of failing open invisibly.
54
+ if (!revocationStoreMissingWarned && decoded.sub && typeof decoded.tokenVersion === 'number') {
55
+ revocationStoreMissingWarned = true;
56
+ logger.warn('Token revocation store NOT registered — revocation checks are INACTIVE (setTokenRevocationStore was never called); revoked/compromised sessions stay valid until natural TTL');
57
+ }
58
+ return false;
59
+ }
60
+ if (store.isCredentialRevoked) {
61
+ const refs = credentialRevocationRefs(decoded);
62
+ if (refs) {
63
+ try {
64
+ if (await store.isCredentialRevoked(refs))
65
+ return true;
66
+ }
67
+ catch {
68
+ // Fail-open, as below.
69
+ }
70
+ }
71
+ }
72
+ if (!decoded.sub || typeof decoded.tokenVersion !== 'number')
73
+ return false;
74
+ try {
75
+ const current = await store.getCurrentVersion(decoded.sub);
76
+ return current !== null && decoded.tokenVersion < current;
77
+ }
78
+ catch {
79
+ // Fail-open: a revocation-store outage must not lock every user out.
80
+ return false;
81
+ }
82
+ }
83
+ /** Whether the registered store has anything to say about this token. */
84
+ function hasRevocationCheck(decoded) {
85
+ const store = tokenRevocationStore;
86
+ if (!store)
87
+ return false;
88
+ if (decoded.sub && typeof decoded.tokenVersion === 'number')
89
+ return true;
90
+ return !!store.isCredentialRevoked && credentialRevocationRefs(decoded) !== null;
91
+ }
92
+ /** Methods a read-only impersonation token may use. */
93
+ const IMPERSONATION_READ_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
94
+ /**
95
+ * Whether a request is a WRITE under a read-only impersonation token. Fail-closed
96
+ * on a missing method: only a request positively known to be a read passes.
97
+ */
98
+ export function isImpersonationWriteBlocked(method, claims) {
99
+ return claims.impersonationReadOnly === true && !IMPERSONATION_READ_METHODS.has((method ?? '').toUpperCase());
100
+ }
101
+ /**
102
+ * Resolve an impersonation session's revocation state. Never throws: any failure
103
+ * — no store, a store that can't answer, a read error — is `unavailable`, which
104
+ * callers treat as revoked.
105
+ */
106
+ async function sessionRevocationState(jti) {
107
+ const store = tokenRevocationStore;
108
+ if (!store?.getSessionRevocation)
109
+ return 'unavailable';
110
+ try {
111
+ return await store.getSessionRevocation(jti);
112
+ }
113
+ catch {
114
+ return 'unavailable';
115
+ }
116
+ }
117
+ /** An impersonation session token: it names the operator AND carries its own session id. */
118
+ function isImpersonationSession(decoded) {
119
+ return typeof decoded.impersonatorId === 'string' && typeof decoded.jti === 'string' && decoded.jti.length > 0;
120
+ }
121
+ /**
122
+ * Public revocation check for routes that verify a platform JWT OUTSIDE
123
+ * `requireAuth` (e.g. the image-registry `/token` mint path, which resolves
124
+ * identity itself). Returns true when the token's `tokenVersion` is strictly
125
+ * behind the store's current version. Fail-open (never throws) — a store outage
126
+ * or a token/store without a usable version yields false (allow), matching
127
+ * `requireAuth`. Pass the verified JWT claims (`sub`, `tokenVersion`).
128
+ */
129
+ export async function isAccessTokenRevoked(claims) {
130
+ // An impersonation session is revoked unless the store positively says live —
131
+ // including on this out-of-band path, or a revoked session could still mint
132
+ // registry tokens.
133
+ if (isImpersonationSession(claims) && await sessionRevocationState(claims.jti) !== 'live')
134
+ return true;
135
+ return isTokenRevoked(claims);
136
+ }
137
+ /** Whether `requireAuth` must consult the revocation stores for this token. */
138
+ export function needsRevocationCheck(decoded) {
139
+ return isImpersonationSession(decoded) || hasRevocationCheck(decoded);
140
+ }
141
+ /**
142
+ * The post-verification session-invalidation check. Returns the refusal
143
+ * message, or `null` when the session is live. An impersonation session is
144
+ * checked even when NO store is registered: there, no store ⇒ unavailable ⇒
145
+ * refused (see `TokenRevocationStore.getSessionRevocation`); the tokenVersion
146
+ * check is a no-op without a store and fails open on a store error. Never rejects.
147
+ */
148
+ export async function checkRevocation(decoded) {
149
+ if (isImpersonationSession(decoded)) {
150
+ const state = await sessionRevocationState(decoded.jti);
151
+ if (state !== 'live') {
152
+ return state === 'revoked'
153
+ ? 'This impersonation session has been ended'
154
+ : 'This impersonation session could not be verified';
155
+ }
156
+ }
157
+ if (hasRevocationCheck(decoded) && await isTokenRevoked(decoded)) {
158
+ return 'Session has been revoked; please sign in again';
159
+ }
160
+ return null;
161
+ }
162
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV2b2NhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWRkbGV3YXJlL3Jldm9jYXRpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7O0dBR0c7QUFFSCxPQUFPLEVBQW1CLE1BQU0sb0JBQW9CLENBQUM7QUFDckQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRWxELE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0FBeUMvQzs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSx3QkFBd0IsQ0FDdEMsTUFBcUU7SUFFckUsTUFBTSxNQUFNLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztJQUNqQyxJQUFJLE1BQU0sQ0FBQyxTQUFTLEtBQUssU0FBUyxJQUFJLE9BQU8sTUFBTSxDQUFDLEdBQUcsS0FBSyxRQUFRLElBQUksTUFBTSxDQUFDLEdBQUc7UUFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUMzRyxJQUFJLE9BQU8sTUFBTSxDQUFDLFdBQVcsS0FBSyxRQUFRLElBQUksTUFBTSxDQUFDLFdBQVc7UUFBRSxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUNqRyxNQUFNLEdBQUcsR0FBRyxPQUFPLE1BQU0sQ0FBQyxHQUFHLEtBQUssUUFBUSxJQUFJLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztJQUNsRixJQUFJLENBQUMsR0FBRyxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBQzNDLE9BQU8sRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FB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Q3BDLE1BQU0sS0FBSyxHQUFHLE1BQU0sc0JBQXNCLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3hELElBQUksS0FBSyxLQUFLLE1BQU0sRUFBRSxDQUFDO1lBQ3JCLE9BQU8sS0FBSyxLQUFLLFNBQVM7Z0JBQ3hCLENBQUMsQ0FBQywyQ0FBMkM7Z0JBQzdDLENBQUMsQ0FBQyxrREFBa0QsQ0FBQztRQUN6RCxDQUFDO0lBQ0gsQ0FBQztJQUNELElBQUksa0JBQWtCLENBQUMsT0FBTyxDQUFDLElBQUksTUFBTSxjQUFjLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUNqRSxPQUFPLGdEQUFnRCxDQUFDO0lBQzFELENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQztBQUNkLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBTZXNzaW9uIGludmFsaWRhdGlvbjogdGhlIHRva2VuLXZlcnNpb24gcmV2b2NhdGlvbiBzdG9yZSBhbmQgdGhlXG4gKiBpbXBlcnNvbmF0aW9uLXNlc3Npb24gbGl2ZW5lc3MgY2hlY2sgYHJlcXVpcmVBdXRoYCBjb25zdWx0cyBhZnRlciB2ZXJpZnlpbmcgYSB0b2tlbi5cbiAqL1xuXG5pbXBvcnQgeyB0eXBlIEp3dFBheWxvYWQgfSBmcm9tICcuLi90eXBlcy9jb21tb24uanMnO1xuaW1wb3J0IHsgY3JlYXRlTG9nZ2VyIH0gZnJvbSAnLi4vdXRpbHMvbG9nZ2VyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdhdXRoLW1pZGRsZXdhcmUnKTtcbi8qKlxuICogQSBzb3VyY2Ugb2YgdGhlIENVUlJFTlQgYHRva2VuVmVyc2lvbmAgZm9yIGEgdXNlciwgYmFja2VkIGJ5IGEgc3RvcmUgdGhlXG4gKiBzdGF0ZWxlc3Mgc2VydmljZXMgY2FuIHJlYWQgKGluIHByYWN0aWNlIGEgUmVkaXMga2V5IHRoZSBwbGF0Zm9ybSBwdWJsaXNoZXNcbiAqIHRvIG9uIGV2ZXJ5IHByaXZpbGVnZSBjaGFuZ2UpLiBSZWdpc3RlcmVkIHZpYSB7QGxpbmsgc2V0VG9rZW5SZXZvY2F0aW9uU3RvcmV9LlxuICpcbiAqIGBnZXRDdXJyZW50VmVyc2lvbmAgcmV0dXJucyB0aGUgdXNlcidzIGN1cnJlbnQgdmVyc2lvbiwgb3IgYG51bGxgIHdoZW4gdGhlXG4gKiBzdG9yZSBoYXMgbm8gZW50cnkg4oCUIGEgbWlzcyBpcyB0cmVhdGVkIGFzIFwibm8ga25vd24gcmV2b2NhdGlvblwiIChmYWlsLW9wZW4pOlxuICogcmV2b2NhdGlvbiBlbnRyaWVzIGFyZSBwdWJsaXNoZWQgd2l0aCBhIFRUTCBtYXRjaGluZyB0aGUgYWNjZXNzLXRva2VuXG4gKiBsaWZldGltZSwgc28gYSBtaXNzaW5nIGVudHJ5IG1lYW5zIGFueSB0b2tlbiBvbGQgZW5vdWdoIHRvIHByZWRhdGUgaXQgaGFzXG4gKiBhbHJlYWR5IGV4cGlyZWQgb24gaXRzIG93bi5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBUb2tlblJldm9jYXRpb25TdG9yZSB7XG4gIGdldEN1cnJlbnRWZXJzaW9uKHVzZXJJZDogc3RyaW5nKTogUHJvbWlzZTxudW1iZXIgfCBudWxsPjtcbiAgLyoqXG4gICAqIFdoZXRoZXIgb25lIElNUEVSU09OQVRJT04gc2Vzc2lvbiAoYnkgdG9rZW4gYGp0aWApIGhhcyBiZWVuIGVuZGVkIGVhcmx5LlxuICAgKlxuICAgKiBPcHRpb25hbCBzbyBhIHN0b3JlIHRoYXQgcHJlZGF0ZXMgaXQgc3RpbGwgbGlua3Mg4oCUIGJ1dCBhIHN0b3JlIFdJVEhPVVQgaXRcbiAgICogY2Fubm90IGFuc3dlciwgYW5kIGFuIGltcGVyc29uYXRpb24gdG9rZW4gaXMgdGhlbiBSRUpFQ1RFRC4gVW5saWtlXG4gICAqIGBnZXRDdXJyZW50VmVyc2lvbmAsIHRoaXMgaXMgZGVsaWJlcmF0ZWx5IG5vdCBmYWlsLW9wZW46IHRoZSBwbGF0Zm9ybSBlbmRzIGFcbiAgICogc2Vzc2lvbiB3aGVuIHNvbWVvbmUgd2l0aGRyYXdzIGNvbnNlbnQsIGFuZCBhIHdpdGhkcmF3biBjb25zZW50IHRoYXQga2VlcHNcbiAgICogd29ya2luZyBpbiBldmVyeSBvdGhlciBzZXJ2aWNlIGlzIG5vdCB3aXRoZHJhd24uXG4gICAqL1xuICBnZXRTZXNzaW9uUmV2b2NhdGlvbj8oanRpOiBzdHJpbmcpOiBQcm9taXNlPFNlc3Npb25SZXZvY2F0aW9uU3RhdGU+O1xuICAvKipcbiAgICogV2hldGhlciBPTkUgY3JlZGVudGlhbCB0aGUgdG9rZW4gcmlkZXMgb24gaGFzIGJlZW4gcmV2b2tlZCDigJQgaXRzIHNlc3Npb25cbiAgICogc2xvdCAoYHNpZGApIG9yIHRoZSBhY2Nlc3Mga2V5IGl0IHdhcyBleGNoYW5nZWQgZnJvbSAvIGRlcml2ZWQgZnJvbSAoc2VlXG4gICAqIHtAbGluayBjcmVkZW50aWFsUmV2b2NhdGlvblJlZnN9KS4gRmFpbC1vcGVuIGxpa2UgYGdldEN1cnJlbnRWZXJzaW9uYDogYVxuICAgKiBzdG9yZSB0aGF0IGNhbid0IGFuc3dlciByZXR1cm5zIGBmYWxzZWAuXG4gICAqL1xuICBpc0NyZWRlbnRpYWxSZXZva2VkPyhyZWZzOiBDcmVkZW50aWFsUmV2b2NhdGlvblJlZnMpOiBQcm9taXNlPGJvb2xlYW4+O1xufVxuXG4vKiogVGhlIHNpbmdsZS1jcmVkZW50aWFsIGlkcyBhIHRva2VuIGNhbiBiZSByZXZva2VkIGJ5IChgcmV2b2tlOnNpZDpgIC8gYHJldm9rZTprZXk6YCkuICovXG5leHBvcnQgaW50ZXJmYWNlIENyZWRlbnRpYWxSZXZvY2F0aW9uUmVmcyB7XG4gIC8qKiBUaGUgcmVmcmVzaC1zZXNzaW9uIHNsb3QgdGhlIHRva2VuIHdhcyBtaW50ZWQgd2l0aC4gKi9cbiAgc2lkPzogc3RyaW5nO1xuICAvKiogQWNjZXNzLWtleSBpZHM6IHRoZSBleGNoYW5nZWQga2V5J3Mgb3duIGBqdGlgLCBhbmQgYW55IGBwYXJlbnRLZXlJZGAuICovXG4gIGtleUlkczogc3RyaW5nW107XG59XG5cbi8qKlxuICogVGhlIGNyZWRlbnRpYWwgaWRzIGEgdmVyaWZpZWQgdG9rZW4gY2FuIGJlIHJldm9rZWQgYnkuIEFuIGV4Y2hhbmdlZCBrZXkgdG9rZW5cbiAqIChgdG9rZW5fdXNlOiAnYXBpX2tleSdgKSBpcyBuYW1lZCBieSBpdHMgYGp0aWA7IGEgdG9rZW4gZGVyaXZlZCBmcm9tIG9uZVxuICogY2FycmllcyBgcGFyZW50S2V5SWRgOyBhIHNlc3Npb24tbWludGVkIHRva2VuIGNhcnJpZXMgYHNpZGAuIFJldHVybnMgYG51bGxgXG4gKiB3aGVuIHRoZSB0b2tlbiBuYW1lcyBub25lIG9mIHRoZW0uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjcmVkZW50aWFsUmV2b2NhdGlvblJlZnMoXG4gIGNsYWltczogUGljazxKd3RQYXlsb2FkLCAnc2lkJyB8ICdwYXJlbnRLZXlJZCcgfCAnanRpJyB8ICd0b2tlbl91c2UnPixcbik6IENyZWRlbnRpYWxSZXZvY2F0aW9uUmVmcyB8IG51bGwge1xuICBjb25zdCBrZXlJZHMgPSBuZXcgU2V0PHN0cmluZz4oKTtcbiAgaWYgKGNsYWltcy50b2tlbl91c2UgPT09ICdhcGlfa2V5JyAmJiB0eXBlb2YgY2xhaW1zLmp0aSA9PT0gJ3N0cmluZycgJiYgY2xhaW1zLmp0aSkga2V5SWRzLmFkZChjbGFpbXMuanRpKTtcbiAgaWYgKHR5cGVvZiBjbGFpbXMucGFyZW50S2V5SWQgPT09ICdzdHJpbmcnICYmIGNsYWltcy5wYXJlbnRLZXlJZCkga2V5SWRzLmFkZChjbGFpbXMucGFyZW50S2V5SWQpO1xuICBjb25zdCBzaWQgPSB0eXBlb2YgY2xhaW1zLnNpZCA9PT0gJ3N0cmluZycgJiYgY2xhaW1zLnNpZCA/IGNsYWltcy5zaWQgOiB1bmRlZmluZWQ7XG4gIGlmICghc2lkICYmIGtleUlkcy5zaXplID09PSAwKSByZXR1cm4gbnVsbDtcbiAgcmV0dXJuIHsgLi4uKHNpZCA/IHsgc2lkIH0gOiB7fSksIGtleUlkczogWy4uLmtleUlkc10gfTtcbn1cblxuLyoqXG4gKiBgcmV2b2tlZGAgICAgIOKAlCB0aGUgc2Vzc2lvbiB3YXMgZW5kZWQ7IHJlamVjdC5cbiAqIGBsaXZlYCAgICAgICAg4oCUIG5vIHJldm9jYXRpb24gcmVjb3JkZWQ7IGFsbG93LlxuICogYHVuYXZhaWxhYmxlYCDigJQgdGhlIHN0b3JlIGNvdWxkbid0IGJlIHJlYWQgb3IgY2FuJ3QgYW5zd2VyOyBSRUpFQ1QuIERpc3RpbmN0XG4gKiAgICAgICAgICAgICAgICAgZnJvbSBgbGl2ZWAgcHJlY2lzZWx5IHNvIGFuIG91dGFnZSBjYW4ndCByZWFkIGFzIFwibm90IHJldm9rZWRcIi5cbiAqL1xuZXhwb3J0IHR5cGUgU2Vzc2lvblJldm9jYXRpb25TdGF0ZSA9ICdyZXZva2VkJyB8ICdsaXZlJyB8ICd1bmF2YWlsYWJsZSc7XG5cbmxldCB0b2tlblJldm9jYXRpb25TdG9yZTogVG9rZW5SZXZvY2F0aW9uU3RvcmUgfCB1bmRlZmluZWQ7XG5cbi8qKlxuICogUmVnaXN0ZXIgKG9yIGNsZWFyLCB3aXRoIGB1bmRlZmluZWRgKSB0aGUgcHJvY2Vzcy13aWRlIHRva2VuLXJldm9jYXRpb24gc3RvcmUuXG4gKiBXaGVuIHVuc2V0ICh0aGUgZGVmYXVsdCksIGByZXF1aXJlQXV0aGAgcGVyZm9ybXMgTk8gcmV2b2NhdGlvbiBjaGVjayAoaXQgcmVsaWVzXG4gKiBvbiB0aGUgc2hvcnQgYWNjZXNzLXRva2VuIFRUTCk7IHNlcnZpY2VzIG9wdCBpbiBhdCBib290IGJ5IHdpcmluZyB0aGVpciBSZWRpc1xuICogY2xpZW50LiBQbGF0Zm9ybSBrZWVwcyBpdHMgb3duIE1vbmdvLWJhY2tlZCBjaGVjayBhbmQgbmVlZCBub3QgcmVnaXN0ZXIgb25lLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2V0VG9rZW5SZXZvY2F0aW9uU3RvcmUoc3RvcmU6IFRva2VuUmV2b2NhdGlvblN0b3JlIHwgdW5kZWZpbmVkKTogdm9pZCB7XG4gIHRva2VuUmV2b2NhdGlvblN0b3JlID0gc3RvcmU7XG4gIGlmIChzdG9yZSkgcmV2b2NhdGlvblN0b3JlTWlzc2luZ1dhcm5lZCA9IGZhbHNlOyAvLyByZS1hcm0gaWYgYSBzdG9yZSBpcyAocmUpcmVnaXN0ZXJlZFxufVxuXG4vKiogT25lLXNob3QgZ3VhcmQgc28gdGhlIFwicmV2b2NhdGlvbiBpbmFjdGl2ZVwiIHdhcm5pbmcgZG9lc24ndCBmbG9vZCB0aGUgbG9ncy4gKi9cbmxldCByZXZvY2F0aW9uU3RvcmVNaXNzaW5nV2FybmVkID0gZmFsc2U7XG5cbi8qKlxuICogUmV0dXJucyB0cnVlIHdoZW4gYGRlY29kZWRgIGhhcyBiZWVuIHJldm9rZWQgcGVyIHRoZSByZWdpc3RlcmVkIHN0b3JlOiBpdHNcbiAqIGVtYmVkZGVkIGB0b2tlblZlcnNpb25gIGlzIHN0cmljdGx5IGJlaGluZCB0aGUgc3RvcmUncyBjdXJyZW50IHZlcnNpb24uXG4gKiBGYWlsLW9wZW4g4oCUIGFueSBzdG9yZSBlcnJvciwgYSBtaXNzaW5nIGVudHJ5LCBvciBhIHRva2VuL3N0b3JlIHdpdGhvdXQgYVxuICogdXNhYmxlIHZlcnNpb24geWllbGRzIGBmYWxzZWAgKGFsbG93KS4gTmV2ZXIgdGhyb3dzLlxuICovXG5hc3luYyBmdW5jdGlvbiBpc1Rva2VuUmV2b2tlZChkZWNvZGVkOiBKd3RQYXlsb2FkKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIGNvbnN0IHN0b3JlID0gdG9rZW5SZXZvY2F0aW9uU3RvcmU7XG4gIGlmICghc3RvcmUpIHtcbiAgICAvLyBNYWtlIHRoZSBcInJldm9jYXRpb24gaXMgT0ZGXCIgc3RhdGUgT0JTRVJWQUJMRTogYSBzZXJ2aWNlIHRoYXQgdmVyaWZpZXMgdXNlclxuICAgIC8vIHRva2VucyBidXQgZm9yZ290IGBzZXRUb2tlblJldm9jYXRpb25TdG9yZSgpYCBzaWxlbnRseSBkaXNhYmxlcyB0aGUgc2Vzc2lvblxuICAgIC8vIGtpbGwtc3dpdGNoLiBXYXJuIE9OQ0UgKG9ubHkgZm9yIGEgcmV2b2NhYmxlIHVzZXIgdG9rZW4pIHNvIGEgbWlzY29uZmlndXJlZFxuICAgIC8vIGJvb3Qgc3VyZmFjZXMgaW4gdGhlIGxvZ3MgaW5zdGVhZCBvZiBmYWlsaW5nIG9wZW4gaW52aXNpYmx5LlxuICAgIGlmICghcmV2b2NhdGlvblN0b3JlTWlzc2luZ1dhcm5lZCAmJiBkZWNvZGVkLnN1YiAmJiB0eXBlb2YgZGVjb2RlZC50b2tlblZlcnNpb24gPT09ICdudW1iZXInKSB7XG4gICAgICByZXZvY2F0aW9uU3RvcmVNaXNzaW5nV2FybmVkID0gdHJ1ZTtcbiAgICAgIGxvZ2dlci53YXJuKCdUb2tlbiByZXZvY2F0aW9uIHN0b3JlIE5PVCByZWdpc3RlcmVkIOKAlCByZXZvY2F0aW9uIGNoZWNrcyBhcmUgSU5BQ1RJVkUgKHNldFRva2VuUmV2b2NhdGlvblN0b3JlIHdhcyBuZXZlciBjYWxsZWQpOyByZXZva2VkL2NvbXByb21pc2VkIHNlc3Npb25zIHN0YXkgdmFsaWQgdW50aWwgbmF0dXJhbCBUVEwnKTtcbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIGlmIChzdG9yZS5pc0NyZWRlbnRpYWxSZXZva2VkKSB7XG4gICAgY29uc3QgcmVmcyA9IGNyZWRlbnRpYWxSZXZvY2F0aW9uUmVmcyhkZWNvZGVkKTtcbiAgICBpZiAocmVmcykge1xuICAgICAgdHJ5IHtcbiAgICAgICAgaWYgKGF3YWl0IHN0b3JlLmlzQ3JlZGVudGlhbFJldm9rZWQocmVmcykpIHJldHVybiB0cnVlO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIC8vIEZhaWwtb3BlbiwgYXMgYmVsb3cuXG4gICAgICB9XG4gICAgfVxuICB9XG4gIGlmICghZGVjb2RlZC5zdWIgfHwgdHlwZW9mIGRlY29kZWQudG9rZW5WZXJzaW9uICE9PSAnbnVtYmVyJykgcmV0dXJuIGZhbHNlO1xuICB0cnkge1xuICAgIGNvbnN0IGN1cnJlbnQgPSBhd2FpdCBzdG9yZS5nZXRDdXJyZW50VmVyc2lvbihkZWNvZGVkLnN1Yik7XG4gICAgcmV0dXJuIGN1cnJlbnQgIT09IG51bGwgJiYgZGVjb2RlZC50b2tlblZlcnNpb24gPCBjdXJyZW50O1xuICB9IGNhdGNoIHtcbiAgICAvLyBGYWlsLW9wZW46IGEgcmV2b2NhdGlvbi1zdG9yZSBvdXRhZ2UgbXVzdCBub3QgbG9jayBldmVyeSB1c2VyIG91dC5cbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuLyoqIFdoZXRoZXIgdGhlIHJlZ2lzdGVyZWQgc3RvcmUgaGFzIGFueXRoaW5nIHRvIHNheSBhYm91dCB0aGlzIHRva2VuLiAqL1xuZnVuY3Rpb24gaGFzUmV2b2NhdGlvbkNoZWNrKGRlY29kZWQ6IEp3dFBheWxvYWQpOiBib29sZWFuIHtcbiAgY29uc3Qgc3RvcmUgPSB0b2tlblJldm9jYXRpb25TdG9yZTtcbiAgaWYgKCFzdG9yZSkgcmV0dXJuIGZhbHNlO1xuICBpZiAoZGVjb2RlZC5zdWIgJiYgdHlwZW9mIGRlY29kZWQudG9rZW5WZXJzaW9uID09PSAnbnVtYmVyJykgcmV0dXJuIHRydWU7XG4gIHJldHVybiAhIXN0b3JlLmlzQ3JlZGVudGlhbFJldm9rZWQgJiYgY3JlZGVudGlhbFJldm9jYXRpb25SZWZzKGRlY29kZWQpICE9PSBudWxsO1xufVxuXG4vKiogTWV0aG9kcyBhIHJlYWQtb25seSBpbXBlcnNvbmF0aW9uIHRva2VuIG1heSB1c2UuICovXG5jb25zdCBJTVBFUlNPTkFUSU9OX1JFQURfTUVUSE9EUyA9IG5ldyBTZXQoWydHRVQnLCAnSEVBRCcsICdPUFRJT05TJ10pO1xuXG4vKipcbiAqIFdoZXRoZXIgYSByZXF1ZXN0IGlzIGEgV1JJVEUgdW5kZXIgYSByZWFkLW9ubHkgaW1wZXJzb25hdGlvbiB0b2tlbi4gRmFpbC1jbG9zZWRcbiAqIG9uIGEgbWlzc2luZyBtZXRob2Q6IG9ubHkgYSByZXF1ZXN0IHBvc2l0aXZlbHkga25vd24gdG8gYmUgYSByZWFkIHBhc3Nlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzSW1wZXJzb25hdGlvbldyaXRlQmxvY2tlZChcbiAgbWV0aG9kOiBzdHJpbmcgfCB1bmRlZmluZWQsXG4gIGNsYWltczogUGljazxKd3RQYXlsb2FkLCAnaW1wZXJzb25hdGlvblJlYWRPbmx5Jz4sXG4pOiBib29sZWFuIHtcbiAgcmV0dXJuIGNsYWltcy5pbXBlcnNvbmF0aW9uUmVhZE9ubHkgPT09IHRydWUgJiYgIUlNUEVSU09OQVRJT05fUkVBRF9NRVRIT0RTLmhhcygobWV0aG9kID8/ICcnKS50b1VwcGVyQ2FzZSgpKTtcbn1cblxuLyoqXG4gKiBSZXNvbHZlIGFuIGltcGVyc29uYXRpb24gc2Vzc2lvbidzIHJldm9jYXRpb24gc3RhdGUuIE5ldmVyIHRocm93czogYW55IGZhaWx1cmVcbiAqIOKAlCBubyBzdG9yZSwgYSBzdG9yZSB0aGF0IGNhbid0IGFuc3dlciwgYSByZWFkIGVycm9yIOKAlCBpcyBgdW5hdmFpbGFibGVgLCB3aGljaFxuICogY2FsbGVycyB0cmVhdCBhcyByZXZva2VkLlxuICovXG5hc3luYyBmdW5jdGlvbiBzZXNzaW9uUmV2b2NhdGlvblN0YXRlKGp0aTogc3RyaW5nKTogUHJvbWlzZTxTZXNzaW9uUmV2b2NhdGlvblN0YXRlPiB7XG4gIGNvbnN0IHN0b3JlID0gdG9rZW5SZXZvY2F0aW9uU3RvcmU7XG4gIGlmICghc3RvcmU/LmdldFNlc3Npb25SZXZvY2F0aW9uKSByZXR1cm4gJ3VuYXZhaWxhYmxlJztcbiAgdHJ5IHtcbiAgICByZXR1cm4gYXdhaXQgc3RvcmUuZ2V0U2Vzc2lvblJldm9jYXRpb24oanRpKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuICd1bmF2YWlsYWJsZSc7XG4gIH1cbn1cblxuLyoqIEFuIGltcGVyc29uYXRpb24gc2Vzc2lvbiB0b2tlbjogaXQgbmFtZXMgdGhlIG9wZXJhdG9yIEFORCBjYXJyaWVzIGl0cyBvd24gc2Vzc2lvbiBpZC4gKi9cbmZ1bmN0aW9uIGlzSW1wZXJzb25hdGlvblNlc3Npb24oZGVjb2RlZDogeyBpbXBlcnNvbmF0b3JJZD86IHVua25vd247IGp0aT86IHVua25vd24gfSk6IGRlY29kZWQgaXMgeyBpbXBlcnNvbmF0b3JJZDogc3RyaW5nOyBqdGk6IHN0cmluZyB9IHtcbiAgcmV0dXJuIHR5cGVvZiBkZWNvZGVkLmltcGVyc29uYXRvcklkID09PSAnc3RyaW5nJyAmJiB0eXBlb2YgZGVjb2RlZC5qdGkgPT09ICdzdHJpbmcnICYmIGRlY29kZWQuanRpLmxlbmd0aCA+IDA7XG59XG5cbi8qKlxuICogUHVibGljIHJldm9jYXRpb24gY2hlY2sgZm9yIHJvdXRlcyB0aGF0IHZlcmlmeSBhIHBsYXRmb3JtIEpXVCBPVVRTSURFXG4gKiBgcmVxdWlyZUF1dGhgIChlLmcuIHRoZSBpbWFnZS1yZWdpc3RyeSBgL3Rva2VuYCBtaW50IHBhdGgsIHdoaWNoIHJlc29sdmVzXG4gKiBpZGVudGl0eSBpdHNlbGYpLiBSZXR1cm5zIHRydWUgd2hlbiB0aGUgdG9rZW4ncyBgdG9rZW5WZXJzaW9uYCBpcyBzdHJpY3RseVxuICogYmVoaW5kIHRoZSBzdG9yZSdzIGN1cnJlbnQgdmVyc2lvbi4gRmFpbC1vcGVuIChuZXZlciB0aHJvd3MpIOKAlCBhIHN0b3JlIG91dGFnZVxuICogb3IgYSB0b2tlbi9zdG9yZSB3aXRob3V0IGEgdXNhYmxlIHZlcnNpb24geWllbGRzIGZhbHNlIChhbGxvdyksIG1hdGNoaW5nXG4gKiBgcmVxdWlyZUF1dGhgLiBQYXNzIHRoZSB2ZXJpZmllZCBKV1QgY2xhaW1zIChgc3ViYCwgYHRva2VuVmVyc2lvbmApLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gaXNBY2Nlc3NUb2tlblJldm9rZWQoXG4gIGNsYWltczoge1xuICAgIHN1Yj86IHN0cmluZztcbiAgICB0b2tlblZlcnNpb24/OiBudW1iZXI7XG4gICAganRpPzogc3RyaW5nO1xuICAgIGltcGVyc29uYXRvcklkPzogc3RyaW5nO1xuICAgIHNpZD86IHN0cmluZztcbiAgICBwYXJlbnRLZXlJZD86IHN0cmluZztcbiAgICB0b2tlbl91c2U/OiBKd3RQYXlsb2FkWyd0b2tlbl91c2UnXTtcbiAgfSxcbik6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAvLyBBbiBpbXBlcnNvbmF0aW9uIHNlc3Npb24gaXMgcmV2b2tlZCB1bmxlc3MgdGhlIHN0b3JlIHBvc2l0aXZlbHkgc2F5cyBsaXZlIOKAlFxuICAvLyBpbmNsdWRpbmcgb24gdGhpcyBvdXQtb2YtYmFuZCBwYXRoLCBvciBhIHJldm9rZWQgc2Vzc2lvbiBjb3VsZCBzdGlsbCBtaW50XG4gIC8vIHJlZ2lzdHJ5IHRva2Vucy5cbiAgaWYgKGlzSW1wZXJzb25hdGlvblNlc3Npb24oY2xhaW1zKSAmJiBhd2FpdCBzZXNzaW9uUmV2b2NhdGlvblN0YXRlKGNsYWltcy5qdGkpICE9PSAnbGl2ZScpIHJldHVybiB0cnVlO1xuICByZXR1cm4gaXNUb2tlblJldm9rZWQoY2xhaW1zIGFzIHVua25vd24gYXMgSnd0UGF5bG9hZCk7XG59XG5cblxuLyoqIFdoZXRoZXIgYHJlcXVpcmVBdXRoYCBtdXN0IGNvbnN1bHQgdGhlIHJldm9jYXRpb24gc3RvcmVzIGZvciB0aGlzIHRva2VuLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIG5lZWRzUmV2b2NhdGlvbkNoZWNrKGRlY29kZWQ6IEp3dFBheWxvYWQpOiBib29sZWFuIHtcbiAgcmV0dXJuIGlzSW1wZXJzb25hdGlvblNlc3Npb24oZGVjb2RlZCkgfHwgaGFzUmV2b2NhdGlvbkNoZWNrKGRlY29kZWQpO1xufVxuXG4vKipcbiAqIFRoZSBwb3N0LXZlcmlmaWNhdGlvbiBzZXNzaW9uLWludmFsaWRhdGlvbiBjaGVjay4gUmV0dXJucyB0aGUgcmVmdXNhbFxuICogbWVzc2FnZSwgb3IgYG51bGxgIHdoZW4gdGhlIHNlc3Npb24gaXMgbGl2ZS4gQW4gaW1wZXJzb25hdGlvbiBzZXNzaW9uIGlzXG4gKiBjaGVja2VkIGV2ZW4gd2hlbiBOTyBzdG9yZSBpcyByZWdpc3RlcmVkOiB0aGVyZSwgbm8gc3RvcmUg4oeSIHVuYXZhaWxhYmxlIOKHklxuICogcmVmdXNlZCAoc2VlIGBUb2tlblJldm9jYXRpb25TdG9yZS5nZXRTZXNzaW9uUmV2b2NhdGlvbmApOyB0aGUgdG9rZW5WZXJzaW9uXG4gKiBjaGVjayBpcyBhIG5vLW9wIHdpdGhvdXQgYSBzdG9yZSBhbmQgZmFpbHMgb3BlbiBvbiBhIHN0b3JlIGVycm9yLiBOZXZlciByZWplY3RzLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2hlY2tSZXZvY2F0aW9uKGRlY29kZWQ6IEp3dFBheWxvYWQpOiBQcm9taXNlPHN0cmluZyB8IG51bGw+IHtcbiAgaWYgKGlzSW1wZXJzb25hdGlvblNlc3Npb24oZGVjb2RlZCkpIHtcbiAgICBjb25zdCBzdGF0ZSA9IGF3YWl0IHNlc3Npb25SZXZvY2F0aW9uU3RhdGUoZGVjb2RlZC5qdGkpO1xuICAgIGlmIChzdGF0ZSAhPT0gJ2xpdmUnKSB7XG4gICAgICByZXR1cm4gc3RhdGUgPT09ICdyZXZva2VkJ1xuICAgICAgICA/ICdUaGlzIGltcGVyc29uYXRpb24gc2Vzc2lvbiBoYXMgYmVlbiBlbmRlZCdcbiAgICAgICAgOiAnVGhpcyBpbXBlcnNvbmF0aW9uIHNlc3Npb24gY291bGQgbm90IGJlIHZlcmlmaWVkJztcbiAgICB9XG4gIH1cbiAgaWYgKGhhc1Jldm9jYXRpb25DaGVjayhkZWNvZGVkKSAmJiBhd2FpdCBpc1Rva2VuUmV2b2tlZChkZWNvZGVkKSkge1xuICAgIHJldHVybiAnU2Vzc2lvbiBoYXMgYmVlbiByZXZva2VkOyBwbGVhc2Ugc2lnbiBpbiBhZ2Fpbic7XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59XG4iXX0=
@@ -46,6 +46,11 @@ export type RouteGate = {
46
46
  | {
47
47
  kind: 'orgAdminAssurance';
48
48
  machines: OrgAdminAssuranceMachines;
49
+ }
50
+ /** The caller's ACTIVE org must be the system org (`requireSystemOrg`) — the
51
+ * plugin-ecosystem governance boundary (docs/runbooks/ecosystem-moderation.md). */
52
+ | {
53
+ kind: 'systemOrg';
49
54
  } | {
50
55
  kind: 'feature';
51
56
  feature: string;
@@ -118,6 +123,12 @@ export interface RouteTableEntry {
118
123
  orgAdminAssurance?: {
119
124
  machines: OrgAdminAssuranceMachines;
120
125
  };
126
+ /**
127
+ * Present (always `true`) when the chain runs `requireSystemOrg`: only a
128
+ * caller whose active org is the system org gets through. Absent otherwise,
129
+ * so the table of a service with no ecosystem-governance route is unchanged.
130
+ */
131
+ systemOrg?: true;
121
132
  features: string[];
122
133
  scopes: string[];
123
134
  audit: string[];