@pipeline-builder/api-core 4.1.13 → 4.1.14

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,47 @@
1
+ /**
2
+ * The plugin-ecosystem governance gate (docs/runbooks/ecosystem-moderation.md).
3
+ *
4
+ * Only the system org manages or approves the ecosystem. A route that exercises
5
+ * a system-org-only permission (`plugins:moderate`, `publishers:verify`) must
6
+ * therefore check three things, and {@link requireEcosystemPermission} composes
7
+ * them so no route can forget one:
8
+ *
9
+ * 1. {@link requireSystemOrg} — the caller's ACTIVE org is the system org. A
10
+ * token minted in a tenant org is refused even for the same person (and even
11
+ * for a superadmin: they switch to the system org to act on the ecosystem,
12
+ * so every governance decision is recorded with the system org's id).
13
+ * 2. the permission itself (`requirePermission`, any-of);
14
+ * 3. an MFA-grade session (`requireAssurance({ minAssurance: 2 })`), which also
15
+ * refuses every machine credential.
16
+ *
17
+ * The route-coverage governance check (`findSystemOrgGuardViolations` in
18
+ * `testing/route-coverage.ts`) fails any route that gates on a system-org-only
19
+ * permission without 1 and 3, so a hand-rolled chain cannot slip through.
20
+ */
21
+ import type { NextFunction, Request, RequestHandler, Response } from 'express';
22
+ import { type Permission } from '../types/permissions.js';
23
+ /** Whether the request's ACTIVE org (the token's `organizationId`) is the system
24
+ * org. Compared by id only — an org merely NAMED "system" never qualifies. */
25
+ export declare function isSystemOrgRequest(req: Request): boolean;
26
+ /**
27
+ * Require that the caller's active org is the system org. Compose after
28
+ * `requireAuth`. Refusals are 403 `SYSTEM_ORG_REQUIRED`, counted
29
+ * (`system_org_guard_refused_total`) and audited through the shared
30
+ * `authz.denied` sink (`required: 'system-org'`).
31
+ */
32
+ export declare const requireSystemOrg: (req: Request, res: Response, next: NextFunction) => void;
33
+ /**
34
+ * THE gate for an ecosystem-governance route: system org + (any of) the given
35
+ * system-org-only permission(s) + an MFA-grade human session. Returns the chain
36
+ * as an array, which Express flattens:
37
+ *
38
+ * ```ts
39
+ * router.post('/requests/:id/approve', requireAuth, requireEcosystemPermission('plugins:moderate'),
40
+ * requireStepUp, audited('plugin.request.approve'), handler);
41
+ * ```
42
+ *
43
+ * Throws at route-definition time when handed a permission that is NOT
44
+ * system-org-only — a tenant permission behind this gate would be unreachable
45
+ * for every tenant, which is always a wiring mistake.
46
+ */
47
+ export declare function requireEcosystemPermission(...permissions: Permission[]): RequestHandler[];
@@ -0,0 +1,60 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { isSystemOrgId } from './system-org.js';
4
+ import { recordAuthzDenial, requirePermission } from './permission-gates.js';
5
+ import { requireAssurance } from './assurance.js';
6
+ import { tagRouteGate } from './route-table.js';
7
+ import { HttpStatus } from '../constants/http-status.js';
8
+ import { ErrorCode } from '../types/error-codes.js';
9
+ import { isSystemOrgOnlyPermission } from '../types/permissions.js';
10
+ import { emitCounter } from '../utils/metric-emitter.js';
11
+ import { sendError } from '../utils/response.js';
12
+ /** Whether the request's ACTIVE org (the token's `organizationId`) is the system
13
+ * org. Compared by id only — an org merely NAMED "system" never qualifies. */
14
+ export function isSystemOrgRequest(req) {
15
+ return isSystemOrgId(req.user?.organizationId);
16
+ }
17
+ /**
18
+ * Require that the caller's active org is the system org. Compose after
19
+ * `requireAuth`. Refusals are 403 `SYSTEM_ORG_REQUIRED`, counted
20
+ * (`system_org_guard_refused_total`) and audited through the shared
21
+ * `authz.denied` sink (`required: 'system-org'`).
22
+ */
23
+ export const requireSystemOrg = tagRouteGate(function requireSystemOrg(req, res, next) {
24
+ if (!req.user) {
25
+ return sendError(res, HttpStatus.UNAUTHORIZED, 'Authentication required', ErrorCode.UNAUTHORIZED);
26
+ }
27
+ if (isSystemOrgRequest(req))
28
+ return next();
29
+ emitCounter('system_org_guard_refused_total', { principal: req.user.principalType ?? 'unknown' });
30
+ recordAuthzDenial(req, 'system-org');
31
+ return sendError(res, HttpStatus.FORBIDDEN, 'This action is reserved for the system organization — switch to it to manage the plugin ecosystem', ErrorCode.SYSTEM_ORG_REQUIRED);
32
+ }, { kind: 'systemOrg' });
33
+ /**
34
+ * THE gate for an ecosystem-governance route: system org + (any of) the given
35
+ * system-org-only permission(s) + an MFA-grade human session. Returns the chain
36
+ * as an array, which Express flattens:
37
+ *
38
+ * ```ts
39
+ * router.post('/requests/:id/approve', requireAuth, requireEcosystemPermission('plugins:moderate'),
40
+ * requireStepUp, audited('plugin.request.approve'), handler);
41
+ * ```
42
+ *
43
+ * Throws at route-definition time when handed a permission that is NOT
44
+ * system-org-only — a tenant permission behind this gate would be unreachable
45
+ * for every tenant, which is always a wiring mistake.
46
+ */
47
+ export function requireEcosystemPermission(...permissions) {
48
+ if (permissions.length === 0)
49
+ throw new Error('requireEcosystemPermission needs at least one permission');
50
+ const tenant = permissions.filter((p) => !isSystemOrgOnlyPermission(p));
51
+ if (tenant.length > 0) {
52
+ throw new Error(`requireEcosystemPermission only takes system-org-only permissions; got ${tenant.join(', ')}`);
53
+ }
54
+ return [
55
+ requireSystemOrg,
56
+ requirePermission(...permissions),
57
+ requireAssurance({ minAssurance: 2 }),
58
+ ];
59
+ }
60
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWNvc3lzdGVtLWd1YXJkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL21pZGRsZXdhcmUvZWNvc3lzdGVtLWd1YXJkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUF3QnRDLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNoRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUM3RSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDaEQsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3pELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNwRCxPQUFPLEVBQUUseUJBQXlCLEVBQW1CLE1BQU0seUJBQXlCLENBQUM7QUFDckYsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ3pELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUVqRDsrRUFDK0U7QUFDL0UsTUFBTSxVQUFVLGtCQUFrQixDQUFDLEdBQVk7SUFDN0MsT0FBTyxhQUFhLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxjQUFjLENBQUMsQ0FBQztBQUNqRCxDQUFDO0FBRUQ7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsTUFBTSxnQkFBZ0IsR0FBRyxZQUFZLENBQUMsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFZLEVBQUUsR0FBYSxFQUFFLElBQWtCO0lBQ3BILElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDZCxPQUFPLFNBQVMsQ0FBQyxHQUFHLEVBQUUsVUFBVSxDQUFDLFlBQVksRUFBRSx5QkFBeUIsRUFBRSxTQUFTLENBQUMsWUFBWSxDQUFDLENBQUM7SUFDcEcsQ0FBQztJQUNELElBQUksa0JBQWtCLENBQUMsR0FBRyxDQUFDO1FBQUUsT0FBTyxJQUFJLEVBQUUsQ0FBQztJQUMzQyxXQUFXLENBQUMsZ0NBQWdDLEVBQUUsRUFBRSxTQUFTLEVBQUUsR0FBRyxDQUFDLElBQUksQ0FBQyxhQUFhLElBQUksU0FBUyxFQUFFLENBQUMsQ0FBQztJQUNsRyxpQkFBaUIsQ0FBQyxHQUFHLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDckMsT0FBTyxTQUFTLENBQ2QsR0FBRyxFQUFFLFVBQVUsQ0FBQyxTQUFTLEVBQ3pCLG1HQUFtRyxFQUNuRyxTQUFTLENBQUMsbUJBQW1CLENBQzlCLENBQUM7QUFDSixDQUFDLEVBQUUsRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLENBQUMsQ0FBQztBQUUxQjs7Ozs7Ozs7Ozs7OztHQWFHO0FBQ0gsTUFBTSxVQUFVLDBCQUEwQixDQUFDLEdBQUcsV0FBeUI7SUFDckUsSUFBSSxXQUFXLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxNQUFNLElBQUksS0FBSyxDQUFDLDBEQUEwRCxDQUFDLENBQUM7SUFDMUcsTUFBTSxNQUFNLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ3hFLElBQUksTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN0QixNQUFNLElBQUksS0FBSyxDQUFDLDBFQUEwRSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUNqSCxDQUFDO0lBQ0QsT0FBTztRQUNMLGdCQUFnQjtRQUNoQixpQkFBaUIsQ0FBQyxHQUFHLFdBQVcsQ0FBQztRQUNqQyxnQkFBZ0IsQ0FBQyxFQUFFLFlBQVksRUFBRSxDQUFDLEVBQUUsQ0FBQztLQUNsQixDQUFDO0FBQ3hCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBUaGUgcGx1Z2luLWVjb3N5c3RlbSBnb3Zlcm5hbmNlIGdhdGUgKGRvY3MvcnVuYm9va3MvZWNvc3lzdGVtLW1vZGVyYXRpb24ubWQpLlxuICpcbiAqIE9ubHkgdGhlIHN5c3RlbSBvcmcgbWFuYWdlcyBvciBhcHByb3ZlcyB0aGUgZWNvc3lzdGVtLiBBIHJvdXRlIHRoYXQgZXhlcmNpc2VzXG4gKiBhIHN5c3RlbS1vcmctb25seSBwZXJtaXNzaW9uIChgcGx1Z2luczptb2RlcmF0ZWAsIGBwdWJsaXNoZXJzOnZlcmlmeWApIG11c3RcbiAqIHRoZXJlZm9yZSBjaGVjayB0aHJlZSB0aGluZ3MsIGFuZCB7QGxpbmsgcmVxdWlyZUVjb3N5c3RlbVBlcm1pc3Npb259IGNvbXBvc2VzXG4gKiB0aGVtIHNvIG5vIHJvdXRlIGNhbiBmb3JnZXQgb25lOlxuICpcbiAqICAxLiB7QGxpbmsgcmVxdWlyZVN5c3RlbU9yZ30g4oCUIHRoZSBjYWxsZXIncyBBQ1RJVkUgb3JnIGlzIHRoZSBzeXN0ZW0gb3JnLiBBXG4gKiAgICAgdG9rZW4gbWludGVkIGluIGEgdGVuYW50IG9yZyBpcyByZWZ1c2VkIGV2ZW4gZm9yIHRoZSBzYW1lIHBlcnNvbiAoYW5kIGV2ZW5cbiAqICAgICBmb3IgYSBzdXBlcmFkbWluOiB0aGV5IHN3aXRjaCB0byB0aGUgc3lzdGVtIG9yZyB0byBhY3Qgb24gdGhlIGVjb3N5c3RlbSxcbiAqICAgICBzbyBldmVyeSBnb3Zlcm5hbmNlIGRlY2lzaW9uIGlzIHJlY29yZGVkIHdpdGggdGhlIHN5c3RlbSBvcmcncyBpZCkuXG4gKiAgMi4gdGhlIHBlcm1pc3Npb24gaXRzZWxmIChgcmVxdWlyZVBlcm1pc3Npb25gLCBhbnktb2YpO1xuICogIDMuIGFuIE1GQS1ncmFkZSBzZXNzaW9uIChgcmVxdWlyZUFzc3VyYW5jZSh7IG1pbkFzc3VyYW5jZTogMiB9KWApLCB3aGljaCBhbHNvXG4gKiAgICAgcmVmdXNlcyBldmVyeSBtYWNoaW5lIGNyZWRlbnRpYWwuXG4gKlxuICogVGhlIHJvdXRlLWNvdmVyYWdlIGdvdmVybmFuY2UgY2hlY2sgKGBmaW5kU3lzdGVtT3JnR3VhcmRWaW9sYXRpb25zYCBpblxuICogYHRlc3Rpbmcvcm91dGUtY292ZXJhZ2UudHNgKSBmYWlscyBhbnkgcm91dGUgdGhhdCBnYXRlcyBvbiBhIHN5c3RlbS1vcmctb25seVxuICogcGVybWlzc2lvbiB3aXRob3V0IDEgYW5kIDMsIHNvIGEgaGFuZC1yb2xsZWQgY2hhaW4gY2Fubm90IHNsaXAgdGhyb3VnaC5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IE5leHRGdW5jdGlvbiwgUmVxdWVzdCwgUmVxdWVzdEhhbmRsZXIsIFJlc3BvbnNlIH0gZnJvbSAnZXhwcmVzcyc7XG5pbXBvcnQgeyBpc1N5c3RlbU9yZ0lkIH0gZnJvbSAnLi9zeXN0ZW0tb3JnLmpzJztcbmltcG9ydCB7IHJlY29yZEF1dGh6RGVuaWFsLCByZXF1aXJlUGVybWlzc2lvbiB9IGZyb20gJy4vcGVybWlzc2lvbi1nYXRlcy5qcyc7XG5pbXBvcnQgeyByZXF1aXJlQXNzdXJhbmNlIH0gZnJvbSAnLi9hc3N1cmFuY2UuanMnO1xuaW1wb3J0IHsgdGFnUm91dGVHYXRlIH0gZnJvbSAnLi9yb3V0ZS10YWJsZS5qcyc7XG5pbXBvcnQgeyBIdHRwU3RhdHVzIH0gZnJvbSAnLi4vY29uc3RhbnRzL2h0dHAtc3RhdHVzLmpzJztcbmltcG9ydCB7IEVycm9yQ29kZSB9IGZyb20gJy4uL3R5cGVzL2Vycm9yLWNvZGVzLmpzJztcbmltcG9ydCB7IGlzU3lzdGVtT3JnT25seVBlcm1pc3Npb24sIHR5cGUgUGVybWlzc2lvbiB9IGZyb20gJy4uL3R5cGVzL3Blcm1pc3Npb25zLmpzJztcbmltcG9ydCB7IGVtaXRDb3VudGVyIH0gZnJvbSAnLi4vdXRpbHMvbWV0cmljLWVtaXR0ZXIuanMnO1xuaW1wb3J0IHsgc2VuZEVycm9yIH0gZnJvbSAnLi4vdXRpbHMvcmVzcG9uc2UuanMnO1xuXG4vKiogV2hldGhlciB0aGUgcmVxdWVzdCdzIEFDVElWRSBvcmcgKHRoZSB0b2tlbidzIGBvcmdhbml6YXRpb25JZGApIGlzIHRoZSBzeXN0ZW1cbiAqICBvcmcuIENvbXBhcmVkIGJ5IGlkIG9ubHkg4oCUIGFuIG9yZyBtZXJlbHkgTkFNRUQgXCJzeXN0ZW1cIiBuZXZlciBxdWFsaWZpZXMuICovXG5leHBvcnQgZnVuY3Rpb24gaXNTeXN0ZW1PcmdSZXF1ZXN0KHJlcTogUmVxdWVzdCk6IGJvb2xlYW4ge1xuICByZXR1cm4gaXNTeXN0ZW1PcmdJZChyZXEudXNlcj8ub3JnYW5pemF0aW9uSWQpO1xufVxuXG4vKipcbiAqIFJlcXVpcmUgdGhhdCB0aGUgY2FsbGVyJ3MgYWN0aXZlIG9yZyBpcyB0aGUgc3lzdGVtIG9yZy4gQ29tcG9zZSBhZnRlclxuICogYHJlcXVpcmVBdXRoYC4gUmVmdXNhbHMgYXJlIDQwMyBgU1lTVEVNX09SR19SRVFVSVJFRGAsIGNvdW50ZWRcbiAqIChgc3lzdGVtX29yZ19ndWFyZF9yZWZ1c2VkX3RvdGFsYCkgYW5kIGF1ZGl0ZWQgdGhyb3VnaCB0aGUgc2hhcmVkXG4gKiBgYXV0aHouZGVuaWVkYCBzaW5rIChgcmVxdWlyZWQ6ICdzeXN0ZW0tb3JnJ2ApLlxuICovXG5leHBvcnQgY29uc3QgcmVxdWlyZVN5c3RlbU9yZyA9IHRhZ1JvdXRlR2F0ZShmdW5jdGlvbiByZXF1aXJlU3lzdGVtT3JnKHJlcTogUmVxdWVzdCwgcmVzOiBSZXNwb25zZSwgbmV4dDogTmV4dEZ1bmN0aW9uKTogdm9pZCB7XG4gIGlmICghcmVxLnVzZXIpIHtcbiAgICByZXR1cm4gc2VuZEVycm9yKHJlcywgSHR0cFN0YXR1cy5VTkFVVEhPUklaRUQsICdBdXRoZW50aWNhdGlvbiByZXF1aXJlZCcsIEVycm9yQ29kZS5VTkFVVEhPUklaRUQpO1xuICB9XG4gIGlmIChpc1N5c3RlbU9yZ1JlcXVlc3QocmVxKSkgcmV0dXJuIG5leHQoKTtcbiAgZW1pdENvdW50ZXIoJ3N5c3RlbV9vcmdfZ3VhcmRfcmVmdXNlZF90b3RhbCcsIHsgcHJpbmNpcGFsOiByZXEudXNlci5wcmluY2lwYWxUeXBlID8/ICd1bmtub3duJyB9KTtcbiAgcmVjb3JkQXV0aHpEZW5pYWwocmVxLCAnc3lzdGVtLW9yZycpO1xuICByZXR1cm4gc2VuZEVycm9yKFxuICAgIHJlcywgSHR0cFN0YXR1cy5GT1JCSURERU4sXG4gICAgJ1RoaXMgYWN0aW9uIGlzIHJlc2VydmVkIGZvciB0aGUgc3lzdGVtIG9yZ2FuaXphdGlvbiDigJQgc3dpdGNoIHRvIGl0IHRvIG1hbmFnZSB0aGUgcGx1Z2luIGVjb3N5c3RlbScsXG4gICAgRXJyb3JDb2RlLlNZU1RFTV9PUkdfUkVRVUlSRUQsXG4gICk7XG59LCB7IGtpbmQ6ICdzeXN0ZW1PcmcnIH0pO1xuXG4vKipcbiAqIFRIRSBnYXRlIGZvciBhbiBlY29zeXN0ZW0tZ292ZXJuYW5jZSByb3V0ZTogc3lzdGVtIG9yZyArIChhbnkgb2YpIHRoZSBnaXZlblxuICogc3lzdGVtLW9yZy1vbmx5IHBlcm1pc3Npb24ocykgKyBhbiBNRkEtZ3JhZGUgaHVtYW4gc2Vzc2lvbi4gUmV0dXJucyB0aGUgY2hhaW5cbiAqIGFzIGFuIGFycmF5LCB3aGljaCBFeHByZXNzIGZsYXR0ZW5zOlxuICpcbiAqIGBgYHRzXG4gKiByb3V0ZXIucG9zdCgnL3JlcXVlc3RzLzppZC9hcHByb3ZlJywgcmVxdWlyZUF1dGgsIHJlcXVpcmVFY29zeXN0ZW1QZXJtaXNzaW9uKCdwbHVnaW5zOm1vZGVyYXRlJyksXG4gKiAgIHJlcXVpcmVTdGVwVXAsIGF1ZGl0ZWQoJ3BsdWdpbi5yZXF1ZXN0LmFwcHJvdmUnKSwgaGFuZGxlcik7XG4gKiBgYGBcbiAqXG4gKiBUaHJvd3MgYXQgcm91dGUtZGVmaW5pdGlvbiB0aW1lIHdoZW4gaGFuZGVkIGEgcGVybWlzc2lvbiB0aGF0IGlzIE5PVFxuICogc3lzdGVtLW9yZy1vbmx5IOKAlCBhIHRlbmFudCBwZXJtaXNzaW9uIGJlaGluZCB0aGlzIGdhdGUgd291bGQgYmUgdW5yZWFjaGFibGVcbiAqIGZvciBldmVyeSB0ZW5hbnQsIHdoaWNoIGlzIGFsd2F5cyBhIHdpcmluZyBtaXN0YWtlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVxdWlyZUVjb3N5c3RlbVBlcm1pc3Npb24oLi4ucGVybWlzc2lvbnM6IFBlcm1pc3Npb25bXSk6IFJlcXVlc3RIYW5kbGVyW10ge1xuICBpZiAocGVybWlzc2lvbnMubGVuZ3RoID09PSAwKSB0aHJvdyBuZXcgRXJyb3IoJ3JlcXVpcmVFY29zeXN0ZW1QZXJtaXNzaW9uIG5lZWRzIGF0IGxlYXN0IG9uZSBwZXJtaXNzaW9uJyk7XG4gIGNvbnN0IHRlbmFudCA9IHBlcm1pc3Npb25zLmZpbHRlcigocCkgPT4gIWlzU3lzdGVtT3JnT25seVBlcm1pc3Npb24ocCkpO1xuICBpZiAodGVuYW50Lmxlbmd0aCA+IDApIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoYHJlcXVpcmVFY29zeXN0ZW1QZXJtaXNzaW9uIG9ubHkgdGFrZXMgc3lzdGVtLW9yZy1vbmx5IHBlcm1pc3Npb25zOyBnb3QgJHt0ZW5hbnQuam9pbignLCAnKX1gKTtcbiAgfVxuICByZXR1cm4gW1xuICAgIHJlcXVpcmVTeXN0ZW1PcmcsXG4gICAgcmVxdWlyZVBlcm1pc3Npb24oLi4ucGVybWlzc2lvbnMpLFxuICAgIHJlcXVpcmVBc3N1cmFuY2UoeyBtaW5Bc3N1cmFuY2U6IDIgfSksXG4gIF0gYXMgUmVxdWVzdEhhbmRsZXJbXTtcbn1cbiJdfQ==
@@ -1,4 +1,11 @@
1
- export * from './auth.js';
2
- export * from './mongo-sanitize.js';
3
- export * from './step-up.js';
4
- export * from './route-table.js';
1
+ export { verifyUserJwt, verifyBearerToken, hasValidIdentityClaims, } from './jwt-verify.js';
2
+ export { requireAuth, } from './auth.js';
3
+ export { setTokenRevocationStore, isAccessTokenRevoked, } from './revocation.js';
4
+ export { requireAssurance, refuseWeakSession, refuseForOrgAdminAssurance, requireOrgAdminAssurance, type AssuranceOptions, } from './assurance.js';
5
+ export { userHasPermission, setAuthzDenialAuditor, recordAuthzDenial, requirePermission, requireAllPermissions, requirePermissionOrService, requireServicePrincipal, requireInternalService, isSystemAdmin, hasScope, requireSystemAdmin, requireFeature, type AuthzDenialInfo, } from './permission-gates.js';
6
+ export { isSystemOrgId, SYSTEM_ORG_ID, SYSTEM_ORG_SLUG, } from './system-org.js';
7
+ export { serviceNameOf, isServiceTokenDenied, signServiceToken, getServiceAuthHeader, isServicePrincipal, isServiceAccountPrincipal, isServicePrincipalNamed, verifyServicePrincipal, } from './service-tokens.js';
8
+ export { requireSystemOrg, requireEcosystemPermission } from './ecosystem-guard.js';
9
+ export { mongoSanitize, MAX_SANITIZE_DEPTH, } from './mongo-sanitize.js';
10
+ export { requireStepUp, type StepUpMethod, STRONG_STEP_UP_METHODS, } from './step-up.js';
11
+ export { tagRouteGate, getRouteGates, audited, buildRouteTable, isWriteMethod, summarizeRouteTable, type RouteTableEntry, } from './route-table.js';
@@ -1,7 +1,14 @@
1
1
  // Copyright 2026 Pipeline Builder Contributors
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- export * from './auth.js';
4
- export * from './mongo-sanitize.js';
5
- export * from './step-up.js';
6
- export * from './route-table.js';
7
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbWlkZGxld2FyZS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLGNBQWMsV0FBVyxDQUFDO0FBQzFCLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsY0FBYyxjQUFjLENBQUM7QUFDN0IsY0FBYyxrQkFBa0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5leHBvcnQgKiBmcm9tICcuL2F1dGguanMnO1xuZXhwb3J0ICogZnJvbSAnLi9tb25nby1zYW5pdGl6ZS5qcyc7XG5leHBvcnQgKiBmcm9tICcuL3N0ZXAtdXAuanMnO1xuZXhwb3J0ICogZnJvbSAnLi9yb3V0ZS10YWJsZS5qcyc7XG4iXX0=
3
+ export { verifyUserJwt, verifyBearerToken, hasValidIdentityClaims, } from './jwt-verify.js';
4
+ export { requireAuth, } from './auth.js';
5
+ export { setTokenRevocationStore, isAccessTokenRevoked, } from './revocation.js';
6
+ export { requireAssurance, refuseWeakSession, refuseForOrgAdminAssurance, requireOrgAdminAssurance, } from './assurance.js';
7
+ export { userHasPermission, setAuthzDenialAuditor, recordAuthzDenial, requirePermission, requireAllPermissions, requirePermissionOrService, requireServicePrincipal, requireInternalService, isSystemAdmin, hasScope, requireSystemAdmin, requireFeature, } from './permission-gates.js';
8
+ export { isSystemOrgId, SYSTEM_ORG_ID, SYSTEM_ORG_SLUG, } from './system-org.js';
9
+ export { serviceNameOf, isServiceTokenDenied, signServiceToken, getServiceAuthHeader, isServicePrincipal, isServiceAccountPrincipal, isServicePrincipalNamed, verifyServicePrincipal, } from './service-tokens.js';
10
+ export { requireSystemOrg, requireEcosystemPermission } from './ecosystem-guard.js';
11
+ export { mongoSanitize, MAX_SANITIZE_DEPTH, } from './mongo-sanitize.js';
12
+ export { requireStepUp, STRONG_STEP_UP_METHODS, } from './step-up.js';
13
+ export { tagRouteGate, getRouteGates, audited, buildRouteTable, isWriteMethod, summarizeRouteTable, } from './route-table.js';
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbWlkZGxld2FyZS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFDTCxhQUFhLEVBQ2IsaUJBQWlCLEVBQ2pCLHNCQUFzQixHQUN2QixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFDTCxXQUFXLEdBQ1osTUFBTSxXQUFXLENBQUM7QUFDbkIsT0FBTyxFQUNMLHVCQUF1QixFQUN2QixvQkFBb0IsR0FDckIsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QixPQUFPLEVBQ0wsZ0JBQWdCLEVBQ2hCLGlCQUFpQixFQUNqQiwwQkFBMEIsRUFDMUIsd0JBQXdCLEdBRXpCLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEIsT0FBTyxFQUNMLGlCQUFpQixFQUNqQixxQkFBcUIsRUFDckIsaUJBQWlCLEVBQ2pCLGlCQUFpQixFQUNqQixxQkFBcUIsRUFDckIsMEJBQTBCLEVBQzFCLHVCQUF1QixFQUN2QixzQkFBc0IsRUFDdEIsYUFBYSxFQUNiLFFBQVEsRUFDUixrQkFBa0IsRUFDbEIsY0FBYyxHQUVmLE1BQU0sdUJBQXVCLENBQUM7QUFDL0IsT0FBTyxFQUNMLGFBQWEsRUFDYixhQUFhLEVBQ2IsZUFBZSxHQUNoQixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFDTCxhQUFhLEVBQ2Isb0JBQW9CLEVBQ3BCLGdCQUFnQixFQUNoQixvQkFBb0IsRUFDcEIsa0JBQWtCLEVBQ2xCLHlCQUF5QixFQUN6Qix1QkFBdUIsRUFDdkIsc0JBQXNCLEdBQ3ZCLE1BQU0scUJBQXFCLENBQUM7QUFDN0IsT0FBTyxFQUFFLGdCQUFnQixFQUFFLDBCQUEwQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDcEYsT0FBTyxFQUNMLGFBQWEsRUFDYixrQkFBa0IsR0FDbkIsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEVBQ0wsYUFBYSxFQUViLHNCQUFzQixHQUN2QixNQUFNLGNBQWMsQ0FBQztBQUN0QixPQUFPLEVBQ0wsWUFBWSxFQUNaLGFBQWEsRUFDYixPQUFPLEVBQ1AsZUFBZSxFQUNmLGFBQWEsRUFDYixtQkFBbUIsR0FFcEIsTUFBTSxrQkFBa0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5leHBvcnQge1xuICB2ZXJpZnlVc2VySnd0LFxuICB2ZXJpZnlCZWFyZXJUb2tlbixcbiAgaGFzVmFsaWRJZGVudGl0eUNsYWltcyxcbn0gZnJvbSAnLi9qd3QtdmVyaWZ5LmpzJztcbmV4cG9ydCB7XG4gIHJlcXVpcmVBdXRoLFxufSBmcm9tICcuL2F1dGguanMnO1xuZXhwb3J0IHtcbiAgc2V0VG9rZW5SZXZvY2F0aW9uU3RvcmUsXG4gIGlzQWNjZXNzVG9rZW5SZXZva2VkLFxufSBmcm9tICcuL3Jldm9jYXRpb24uanMnO1xuZXhwb3J0IHtcbiAgcmVxdWlyZUFzc3VyYW5jZSxcbiAgcmVmdXNlV2Vha1Nlc3Npb24sXG4gIHJlZnVzZUZvck9yZ0FkbWluQXNzdXJhbmNlLFxuICByZXF1aXJlT3JnQWRtaW5Bc3N1cmFuY2UsXG4gIHR5cGUgQXNzdXJhbmNlT3B0aW9ucyxcbn0gZnJvbSAnLi9hc3N1cmFuY2UuanMnO1xuZXhwb3J0IHtcbiAgdXNlckhhc1Blcm1pc3Npb24sXG4gIHNldEF1dGh6RGVuaWFsQXVkaXRvcixcbiAgcmVjb3JkQXV0aHpEZW5pYWwsXG4gIHJlcXVpcmVQZXJtaXNzaW9uLFxuICByZXF1aXJlQWxsUGVybWlzc2lvbnMsXG4gIHJlcXVpcmVQZXJtaXNzaW9uT3JTZXJ2aWNlLFxuICByZXF1aXJlU2VydmljZVByaW5jaXBhbCxcbiAgcmVxdWlyZUludGVybmFsU2VydmljZSxcbiAgaXNTeXN0ZW1BZG1pbixcbiAgaGFzU2NvcGUsXG4gIHJlcXVpcmVTeXN0ZW1BZG1pbixcbiAgcmVxdWlyZUZlYXR1cmUsXG4gIHR5cGUgQXV0aHpEZW5pYWxJbmZvLFxufSBmcm9tICcuL3Blcm1pc3Npb24tZ2F0ZXMuanMnO1xuZXhwb3J0IHtcbiAgaXNTeXN0ZW1PcmdJZCxcbiAgU1lTVEVNX09SR19JRCxcbiAgU1lTVEVNX09SR19TTFVHLFxufSBmcm9tICcuL3N5c3RlbS1vcmcuanMnO1xuZXhwb3J0IHtcbiAgc2VydmljZU5hbWVPZixcbiAgaXNTZXJ2aWNlVG9rZW5EZW5pZWQsXG4gIHNpZ25TZXJ2aWNlVG9rZW4sXG4gIGdldFNlcnZpY2VBdXRoSGVhZGVyLFxuICBpc1NlcnZpY2VQcmluY2lwYWwsXG4gIGlzU2VydmljZUFjY291bnRQcmluY2lwYWwsXG4gIGlzU2VydmljZVByaW5jaXBhbE5hbWVkLFxuICB2ZXJpZnlTZXJ2aWNlUHJpbmNpcGFsLFxufSBmcm9tICcuL3NlcnZpY2UtdG9rZW5zLmpzJztcbmV4cG9ydCB7IHJlcXVpcmVTeXN0ZW1PcmcsIHJlcXVpcmVFY29zeXN0ZW1QZXJtaXNzaW9uIH0gZnJvbSAnLi9lY29zeXN0ZW0tZ3VhcmQuanMnO1xuZXhwb3J0IHtcbiAgbW9uZ29TYW5pdGl6ZSxcbiAgTUFYX1NBTklUSVpFX0RFUFRILFxufSBmcm9tICcuL21vbmdvLXNhbml0aXplLmpzJztcbmV4cG9ydCB7XG4gIHJlcXVpcmVTdGVwVXAsXG4gIHR5cGUgU3RlcFVwTWV0aG9kLFxuICBTVFJPTkdfU1RFUF9VUF9NRVRIT0RTLFxufSBmcm9tICcuL3N0ZXAtdXAuanMnO1xuZXhwb3J0IHtcbiAgdGFnUm91dGVHYXRlLFxuICBnZXRSb3V0ZUdhdGVzLFxuICBhdWRpdGVkLFxuICBidWlsZFJvdXRlVGFibGUsXG4gIGlzV3JpdGVNZXRob2QsXG4gIHN1bW1hcml6ZVJvdXRlVGFibGUsXG4gIHR5cGUgUm91dGVUYWJsZUVudHJ5LFxufSBmcm9tICcuL3JvdXRlLXRhYmxlLmpzJztcbiJdfQ==
@@ -0,0 +1,52 @@
1
+ import { type JwtPayload } from '../types/common.js';
2
+ /** Issuer/audience pinning, when the deployment configures them. Shared by both chains. */
3
+ export declare function issuerAudienceOptions(): {
4
+ issuer?: string;
5
+ audience?: string;
6
+ };
7
+ /**
8
+ * Verify a token that speaks for a PERSON — access, step-up, and the token an
9
+ * opaque access key is exchanged for — against platform's published signing
10
+ * keys.
11
+ *
12
+ * ES256 and a `kid` are both mandatory: no `kid` means no way to pick the right
13
+ * key across a rotation, and any other algorithm is refused outright, which is
14
+ * what makes "no service accepts an HS256 user token" a property of the code
15
+ * rather than a convention. Key lookup goes through the shared JWKS cache
16
+ * (10-minute refresh, one refetch on an unknown `kid`, brief negative cache).
17
+ *
18
+ * @throws {UnknownKidError} the key set is current and has no such key → 401.
19
+ * @throws {JwksUnavailableError} the key set could not be obtained → 503; an
20
+ * unverifiable token is never treated as valid.
21
+ */
22
+ export declare function verifyUserJwt<T = JwtPayload>(token: string): Promise<T>;
23
+ /**
24
+ * Verify ANY bearer token and return its claims.
25
+ *
26
+ * Since #14 there is no shared secret left: BOTH chains are ES256 with a `kid`,
27
+ * so the dispatch is by **who owns the `kid`** rather than by algorithm — and
28
+ * because a `kid` is an RFC 7638 thumbprint, ownership is a fact about the key,
29
+ * not a claim the token makes about itself:
30
+ *
31
+ * - a `kid` published in the per-service key bundle → an INTERNAL SERVICE token
32
+ * ({@link signServiceToken}), verified with that service's key; its `sub` must
33
+ * name the same service, so one service can never speak for another.
34
+ * - anything else → a platform-signed USER token, verified against platform's
35
+ * published JWKS. A token claiming `principalType: 'service'` is refused
36
+ * here: an internal identity may not ride the user signing key.
37
+ *
38
+ * Anything that is not ES256 with a `kid` fails in both chains, which is what
39
+ * makes "no HS256 token is accepted anywhere" a property of the code.
40
+ */
41
+ export declare function verifyBearerToken(token: string): Promise<JwtPayload>;
42
+ /**
43
+ * Whether a verified token carries a well-formed identity: a known
44
+ * `principalType` and `token_use`, and the claims that kind of principal must
45
+ * have. Fails closed — a token minted before these claims existed, or one with a
46
+ * contradictory combination, is not an identity any gate should reason about.
47
+ *
48
+ * - `service`: `token_use: 'access'` and a `service:<name>` subject (the name
49
+ * feeds the denylist and audit attribution).
50
+ * - `user` / `service_account`: `amr` (known methods), `aal` and `auth_time`.
51
+ */
52
+ export declare function hasValidIdentityClaims(decoded: Partial<JwtPayload>): boolean;
@@ -0,0 +1,105 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Bearer-token verification: the user chain (ES256 against platform's JWKS) and
5
+ * the identity-claim shape every gate reasons about.
6
+ */
7
+ import jwt from 'jsonwebtoken';
8
+ import { platformJwksCache } from '../services/jwks-cache.js';
9
+ import { SERVICE_SUBJECT_PREFIX, isServiceKid, verifyServiceJwt } from '../services/service-keys.js';
10
+ import { AUTH_METHODS, PRINCIPAL_TYPES, TOKEN_USES } from '../types/common.js';
11
+ import { USER_TOKEN_ALGORITHM, decodeJwtHeader } from '../utils/jwk.js';
12
+ import { emitCounter } from '../utils/metric-emitter.js';
13
+ /** Issuer/audience pinning, when the deployment configures them. Shared by both chains. */
14
+ export function issuerAudienceOptions() {
15
+ return {
16
+ ...(process.env.JWT_ISSUER ? { issuer: process.env.JWT_ISSUER } : {}),
17
+ ...(process.env.JWT_AUDIENCE ? { audience: process.env.JWT_AUDIENCE } : {}),
18
+ };
19
+ }
20
+ /**
21
+ * Verify a token that speaks for a PERSON — access, step-up, and the token an
22
+ * opaque access key is exchanged for — against platform's published signing
23
+ * keys.
24
+ *
25
+ * ES256 and a `kid` are both mandatory: no `kid` means no way to pick the right
26
+ * key across a rotation, and any other algorithm is refused outright, which is
27
+ * what makes "no service accepts an HS256 user token" a property of the code
28
+ * rather than a convention. Key lookup goes through the shared JWKS cache
29
+ * (10-minute refresh, one refetch on an unknown `kid`, brief negative cache).
30
+ *
31
+ * @throws {UnknownKidError} the key set is current and has no such key → 401.
32
+ * @throws {JwksUnavailableError} the key set could not be obtained → 503; an
33
+ * unverifiable token is never treated as valid.
34
+ */
35
+ export async function verifyUserJwt(token) {
36
+ const header = decodeJwtHeader(token);
37
+ if (header?.alg !== USER_TOKEN_ALGORITHM || !header.kid) {
38
+ throw new jwt.JsonWebTokenError(`User tokens must be signed with ${USER_TOKEN_ALGORITHM} and carry a kid`);
39
+ }
40
+ const key = await platformJwksCache().getKey(header.kid);
41
+ return jwt.verify(token, key, { algorithms: [USER_TOKEN_ALGORITHM], ...issuerAudienceOptions() });
42
+ }
43
+ /**
44
+ * Verify ANY bearer token and return its claims.
45
+ *
46
+ * Since #14 there is no shared secret left: BOTH chains are ES256 with a `kid`,
47
+ * so the dispatch is by **who owns the `kid`** rather than by algorithm — and
48
+ * because a `kid` is an RFC 7638 thumbprint, ownership is a fact about the key,
49
+ * not a claim the token makes about itself:
50
+ *
51
+ * - a `kid` published in the per-service key bundle → an INTERNAL SERVICE token
52
+ * ({@link signServiceToken}), verified with that service's key; its `sub` must
53
+ * name the same service, so one service can never speak for another.
54
+ * - anything else → a platform-signed USER token, verified against platform's
55
+ * published JWKS. A token claiming `principalType: 'service'` is refused
56
+ * here: an internal identity may not ride the user signing key.
57
+ *
58
+ * Anything that is not ES256 with a `kid` fails in both chains, which is what
59
+ * makes "no HS256 token is accepted anywhere" a property of the code.
60
+ */
61
+ export async function verifyBearerToken(token) {
62
+ const header = decodeJwtHeader(token);
63
+ if (!header?.alg)
64
+ throw new jwt.JsonWebTokenError('Malformed token header');
65
+ if (header.kid && isServiceKid(header.kid)) {
66
+ const claims = verifyServiceJwt(token, { kid: header.kid, ...issuerAudienceOptions() });
67
+ if (claims.principalType !== 'service') {
68
+ emitCounter('service_key_non_service_token_rejected_total', { alg: header.alg });
69
+ throw new jwt.JsonWebTokenError('A service signing key may only mint a service principal');
70
+ }
71
+ return claims;
72
+ }
73
+ const claims = await verifyUserJwt(token);
74
+ if (claims.principalType === 'service') {
75
+ throw new jwt.JsonWebTokenError('Service principals may not be signed with the user signing key');
76
+ }
77
+ return claims;
78
+ }
79
+ /**
80
+ * Whether a verified token carries a well-formed identity: a known
81
+ * `principalType` and `token_use`, and the claims that kind of principal must
82
+ * have. Fails closed — a token minted before these claims existed, or one with a
83
+ * contradictory combination, is not an identity any gate should reason about.
84
+ *
85
+ * - `service`: `token_use: 'access'` and a `service:<name>` subject (the name
86
+ * feeds the denylist and audit attribution).
87
+ * - `user` / `service_account`: `amr` (known methods), `aal` and `auth_time`.
88
+ */
89
+ export function hasValidIdentityClaims(decoded) {
90
+ if (!decoded.principalType || !PRINCIPAL_TYPES.includes(decoded.principalType))
91
+ return false;
92
+ if (!decoded.token_use || !TOKEN_USES.includes(decoded.token_use))
93
+ return false;
94
+ if (decoded.principalType === 'service') {
95
+ return decoded.token_use === 'access'
96
+ && typeof decoded.sub === 'string'
97
+ && decoded.sub.startsWith(SERVICE_SUBJECT_PREFIX)
98
+ && decoded.sub.length > SERVICE_SUBJECT_PREFIX.length;
99
+ }
100
+ return Array.isArray(decoded.amr)
101
+ && decoded.amr.every((m) => AUTH_METHODS.includes(m))
102
+ && (decoded.aal === 1 || decoded.aal === 2)
103
+ && typeof decoded.auth_time === 'number';
104
+ }
105
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiand0LXZlcmlmeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9taWRkbGV3YXJlL2p3dC12ZXJpZnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0Qzs7O0dBR0c7QUFFSCxPQUFPLEdBQUcsTUFBTSxjQUFjLENBQUM7QUFDL0IsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDOUQsT0FBTyxFQUFFLHNCQUFzQixFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ3JHLE9BQU8sRUFBRSxZQUFZLEVBQUUsZUFBZSxFQUFFLFVBQVUsRUFBbUIsTUFBTSxvQkFBb0IsQ0FBQztBQUNoRyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsZUFBZSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDeEUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ3pELDJGQUEyRjtBQUMzRixNQUFNLFVBQVUscUJBQXFCO0lBQ25DLE9BQU87UUFDTCxHQUFHLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEVBQUUsTUFBTSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUNyRSxHQUFHLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLEVBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztLQUM1RSxDQUFDO0FBQ0osQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxhQUFhLENBQWlCLEtBQWE7SUFDL0QsTUFBTSxNQUFNLEdBQUcsZUFBZSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3RD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LEdBQUcsS0FBSyxDQUFDLElBQUksT0FBTyxDQUFDLEdBQUcsS0FBSyxDQUFDLENBQUM7V0FDeEMsT0FBTyxPQUFPLENBQUMsU0FBUyxLQUFLLFFBQVEsQ0FBQztBQUM3QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogQmVhcmVyLXRva2VuIHZlcmlmaWNhdGlvbjogdGhlIHVzZXIgY2hhaW4gKEVTMjU2IGFnYWluc3QgcGxhdGZvcm0ncyBKV0tTKSBhbmRcbiAqIHRoZSBpZGVudGl0eS1jbGFpbSBzaGFwZSBldmVyeSBnYXRlIHJlYXNvbnMgYWJvdXQuXG4gKi9cblxuaW1wb3J0IGp3dCBmcm9tICdqc29ud2VidG9rZW4nO1xuaW1wb3J0IHsgcGxhdGZvcm1Kd2tzQ2FjaGUgfSBmcm9tICcuLi9zZXJ2aWNlcy9qd2tzLWNhY2hlLmpzJztcbmltcG9ydCB7IFNFUlZJQ0VfU1VCSkVDVF9QUkVGSVgsIGlzU2VydmljZUtpZCwgdmVyaWZ5U2VydmljZUp3dCB9IGZyb20gJy4uL3NlcnZpY2VzL3NlcnZpY2Uta2V5cy5qcyc7XG5pbXBvcnQgeyBBVVRIX01FVEhPRFMsIFBSSU5DSVBBTF9UWVBFUywgVE9LRU5fVVNFUywgdHlwZSBKd3RQYXlsb2FkIH0gZnJvbSAnLi4vdHlwZXMvY29tbW9uLmpzJztcbmltcG9ydCB7IFVTRVJfVE9LRU5fQUxHT1JJVEhNLCBkZWNvZGVKd3RIZWFkZXIgfSBmcm9tICcuLi91dGlscy9qd2suanMnO1xuaW1wb3J0IHsgZW1pdENvdW50ZXIgfSBmcm9tICcuLi91dGlscy9tZXRyaWMtZW1pdHRlci5qcyc7XG4vKiogSXNzdWVyL2F1ZGllbmNlIHBpbm5pbmcsIHdoZW4gdGhlIGRlcGxveW1lbnQgY29uZmlndXJlcyB0aGVtLiBTaGFyZWQgYnkgYm90aCBjaGFpbnMuICovXG5leHBvcnQgZnVuY3Rpb24gaXNzdWVyQXVkaWVuY2VPcHRpb25zKCk6IHsgaXNzdWVyPzogc3RyaW5nOyBhdWRpZW5jZT86IHN0cmluZyB9IHtcbiAgcmV0dXJuIHtcbiAgICAuLi4ocHJvY2Vzcy5lbnYuSldUX0lTU1VFUiA/IHsgaXNzdWVyOiBwcm9jZXNzLmVudi5KV1RfSVNTVUVSIH0gOiB7fSksXG4gICAgLi4uKHByb2Nlc3MuZW52LkpXVF9BVURJRU5DRSA/IHsgYXVkaWVuY2U6IHByb2Nlc3MuZW52LkpXVF9BVURJRU5DRSB9IDoge30pLFxuICB9O1xufVxuXG4vKipcbiAqIFZlcmlmeSBhIHRva2VuIHRoYXQgc3BlYWtzIGZvciBhIFBFUlNPTiDigJQgYWNjZXNzLCBzdGVwLXVwLCBhbmQgdGhlIHRva2VuIGFuXG4gKiBvcGFxdWUgYWNjZXNzIGtleSBpcyBleGNoYW5nZWQgZm9yIOKAlCBhZ2FpbnN0IHBsYXRmb3JtJ3MgcHVibGlzaGVkIHNpZ25pbmdcbiAqIGtleXMuXG4gKlxuICogRVMyNTYgYW5kIGEgYGtpZGAgYXJlIGJvdGggbWFuZGF0b3J5OiBubyBga2lkYCBtZWFucyBubyB3YXkgdG8gcGljayB0aGUgcmlnaHRcbiAqIGtleSBhY3Jvc3MgYSByb3RhdGlvbiwgYW5kIGFueSBvdGhlciBhbGdvcml0aG0gaXMgcmVmdXNlZCBvdXRyaWdodCwgd2hpY2ggaXNcbiAqIHdoYXQgbWFrZXMgXCJubyBzZXJ2aWNlIGFjY2VwdHMgYW4gSFMyNTYgdXNlciB0b2tlblwiIGEgcHJvcGVydHkgb2YgdGhlIGNvZGVcbiAqIHJhdGhlciB0aGFuIGEgY29udmVudGlvbi4gS2V5IGxvb2t1cCBnb2VzIHRocm91Z2ggdGhlIHNoYXJlZCBKV0tTIGNhY2hlXG4gKiAoMTAtbWludXRlIHJlZnJlc2gsIG9uZSByZWZldGNoIG9uIGFuIHVua25vd24gYGtpZGAsIGJyaWVmIG5lZ2F0aXZlIGNhY2hlKS5cbiAqXG4gKiBAdGhyb3dzIHtVbmtub3duS2lkRXJyb3J9IHRoZSBrZXkgc2V0IGlzIGN1cnJlbnQgYW5kIGhhcyBubyBzdWNoIGtleSDihpIgNDAxLlxuICogQHRocm93cyB7Sndrc1VuYXZhaWxhYmxlRXJyb3J9IHRoZSBrZXkgc2V0IGNvdWxkIG5vdCBiZSBvYnRhaW5lZCDihpIgNTAzOyBhblxuICogICAgICAgICB1bnZlcmlmaWFibGUgdG9rZW4gaXMgbmV2ZXIgdHJlYXRlZCBhcyB2YWxpZC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHZlcmlmeVVzZXJKd3Q8VCA9IEp3dFBheWxvYWQ+KHRva2VuOiBzdHJpbmcpOiBQcm9taXNlPFQ+IHtcbiAgY29uc3QgaGVhZGVyID0gZGVjb2RlSnd0SGVhZGVyKHRva2VuKTtcbiAgaWYgKGhlYWRlcj8uYWxnICE9PSBVU0VSX1RPS0VOX0FMR09SSVRITSB8fCAhaGVhZGVyLmtpZCkge1xuICAgIHRocm93IG5ldyBqd3QuSnNvbldlYlRva2VuRXJyb3IoYFVzZXIgdG9rZW5zIG11c3QgYmUgc2lnbmVkIHdpdGggJHtVU0VSX1RPS0VOX0FMR09SSVRITX0gYW5kIGNhcnJ5IGEga2lkYCk7XG4gIH1cbiAgY29uc3Qga2V5ID0gYXdhaXQgcGxhdGZvcm1Kd2tzQ2FjaGUoKS5nZXRLZXkoaGVhZGVyLmtpZCk7XG4gIHJldHVybiBqd3QudmVyaWZ5KHRva2VuLCBrZXksIHsgYWxnb3JpdGhtczogW1VTRVJfVE9LRU5fQUxHT1JJVEhNXSwgLi4uaXNzdWVyQXVkaWVuY2VPcHRpb25zKCkgfSkgYXMgVDtcbn1cblxuLyoqXG4gKiBWZXJpZnkgQU5ZIGJlYXJlciB0b2tlbiBhbmQgcmV0dXJuIGl0cyBjbGFpbXMuXG4gKlxuICogU2luY2UgIzE0IHRoZXJlIGlzIG5vIHNoYXJlZCBzZWNyZXQgbGVmdDogQk9USCBjaGFpbnMgYXJlIEVTMjU2IHdpdGggYSBga2lkYCxcbiAqIHNvIHRoZSBkaXNwYXRjaCBpcyBieSAqKndobyBvd25zIHRoZSBga2lkYCoqIHJhdGhlciB0aGFuIGJ5IGFsZ29yaXRobSDigJQgYW5kXG4gKiBiZWNhdXNlIGEgYGtpZGAgaXMgYW4gUkZDIDc2MzggdGh1bWJwcmludCwgb3duZXJzaGlwIGlzIGEgZmFjdCBhYm91dCB0aGUga2V5LFxuICogbm90IGEgY2xhaW0gdGhlIHRva2VuIG1ha2VzIGFib3V0IGl0c2VsZjpcbiAqXG4gKiAtIGEgYGtpZGAgcHVibGlzaGVkIGluIHRoZSBwZXItc2VydmljZSBrZXkgYnVuZGxlIOKGkiBhbiBJTlRFUk5BTCBTRVJWSUNFIHRva2VuXG4gKiAgICh7QGxpbmsgc2lnblNlcnZpY2VUb2tlbn0pLCB2ZXJpZmllZCB3aXRoIHRoYXQgc2VydmljZSdzIGtleTsgaXRzIGBzdWJgIG11c3RcbiAqICAgbmFtZSB0aGUgc2FtZSBzZXJ2aWNlLCBzbyBvbmUgc2VydmljZSBjYW4gbmV2ZXIgc3BlYWsgZm9yIGFub3RoZXIuXG4gKiAtIGFueXRoaW5nIGVsc2Ug4oaSIGEgcGxhdGZvcm0tc2lnbmVkIFVTRVIgdG9rZW4sIHZlcmlmaWVkIGFnYWluc3QgcGxhdGZvcm0nc1xuICogICBwdWJsaXNoZWQgSldLUy4gQSB0b2tlbiBjbGFpbWluZyBgcHJpbmNpcGFsVHlwZTogJ3NlcnZpY2UnYCBpcyByZWZ1c2VkXG4gKiAgIGhlcmU6IGFuIGludGVybmFsIGlkZW50aXR5IG1heSBub3QgcmlkZSB0aGUgdXNlciBzaWduaW5nIGtleS5cbiAqXG4gKiBBbnl0aGluZyB0aGF0IGlzIG5vdCBFUzI1NiB3aXRoIGEgYGtpZGAgZmFpbHMgaW4gYm90aCBjaGFpbnMsIHdoaWNoIGlzIHdoYXRcbiAqIG1ha2VzIFwibm8gSFMyNTYgdG9rZW4gaXMgYWNjZXB0ZWQgYW55d2hlcmVcIiBhIHByb3BlcnR5IG9mIHRoZSBjb2RlLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gdmVyaWZ5QmVhcmVyVG9rZW4odG9rZW46IHN0cmluZyk6IFByb21pc2U8Snd0UGF5bG9hZD4ge1xuICBjb25zdCBoZWFkZXIgPSBkZWNvZGVKd3RIZWFkZXIodG9rZW4pO1xuICBpZiAoIWhlYWRlcj8uYWxnKSB0aHJvdyBuZXcgand0Lkpzb25XZWJUb2tlbkVycm9yKCdNYWxmb3JtZWQgdG9rZW4gaGVhZGVyJyk7XG5cbiAgaWYgKGhlYWRlci5raWQgJiYgaXNTZXJ2aWNlS2lkKGhlYWRlci5raWQpKSB7XG4gICAgY29uc3QgY2xhaW1zID0gdmVyaWZ5U2VydmljZUp3dDxKd3RQYXlsb2FkPih0b2tlbiwgeyBraWQ6IGhlYWRlci5raWQsIC4uLmlzc3VlckF1ZGllbmNlT3B0aW9ucygpIH0pO1xuICAgIGlmIChjbGFpbXMucHJpbmNpcGFsVHlwZSAhPT0gJ3NlcnZpY2UnKSB7XG4gICAgICBlbWl0Q291bnRlcignc2VydmljZV9rZXlfbm9uX3NlcnZpY2VfdG9rZW5fcmVqZWN0ZWRfdG90YWwnLCB7IGFsZzogaGVhZGVyLmFsZyB9KTtcbiAgICAgIHRocm93IG5ldyBqd3QuSnNvbldlYlRva2VuRXJyb3IoJ0Egc2VydmljZSBzaWduaW5nIGtleSBtYXkgb25seSBtaW50IGEgc2VydmljZSBwcmluY2lwYWwnKTtcbiAgICB9XG4gICAgcmV0dXJuIGNsYWltcztcbiAgfVxuXG4gIGNvbnN0IGNsYWltcyA9IGF3YWl0IHZlcmlmeVVzZXJKd3QodG9rZW4pO1xuICBpZiAoY2xhaW1zLnByaW5jaXBhbFR5cGUgPT09ICdzZXJ2aWNlJykge1xuICAgIHRocm93IG5ldyBqd3QuSnNvbldlYlRva2VuRXJyb3IoJ1NlcnZpY2UgcHJpbmNpcGFscyBtYXkgbm90IGJlIHNpZ25lZCB3aXRoIHRoZSB1c2VyIHNpZ25pbmcga2V5Jyk7XG4gIH1cbiAgcmV0dXJuIGNsYWltcztcbn1cblxuLyoqXG4gKiBXaGV0aGVyIGEgdmVyaWZpZWQgdG9rZW4gY2FycmllcyBhIHdlbGwtZm9ybWVkIGlkZW50aXR5OiBhIGtub3duXG4gKiBgcHJpbmNpcGFsVHlwZWAgYW5kIGB0b2tlbl91c2VgLCBhbmQgdGhlIGNsYWltcyB0aGF0IGtpbmQgb2YgcHJpbmNpcGFsIG11c3RcbiAqIGhhdmUuIEZhaWxzIGNsb3NlZCDigJQgYSB0b2tlbiBtaW50ZWQgYmVmb3JlIHRoZXNlIGNsYWltcyBleGlzdGVkLCBvciBvbmUgd2l0aCBhXG4gKiBjb250cmFkaWN0b3J5IGNvbWJpbmF0aW9uLCBpcyBub3QgYW4gaWRlbnRpdHkgYW55IGdhdGUgc2hvdWxkIHJlYXNvbiBhYm91dC5cbiAqXG4gKiAtIGBzZXJ2aWNlYDogYHRva2VuX3VzZTogJ2FjY2VzcydgIGFuZCBhIGBzZXJ2aWNlOjxuYW1lPmAgc3ViamVjdCAodGhlIG5hbWVcbiAqICAgZmVlZHMgdGhlIGRlbnlsaXN0IGFuZCBhdWRpdCBhdHRyaWJ1dGlvbikuXG4gKiAtIGB1c2VyYCAvIGBzZXJ2aWNlX2FjY291bnRgOiBgYW1yYCAoa25vd24gbWV0aG9kcyksIGBhYWxgIGFuZCBgYXV0aF90aW1lYC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGhhc1ZhbGlkSWRlbnRpdHlDbGFpbXMoZGVjb2RlZDogUGFydGlhbDxKd3RQYXlsb2FkPik6IGJvb2xlYW4ge1xuICBpZiAoIWRlY29kZWQucHJpbmNpcGFsVHlwZSB8fCAhUFJJTkNJUEFMX1RZUEVTLmluY2x1ZGVzKGRlY29kZWQucHJpbmNpcGFsVHlwZSkpIHJldHVybiBmYWxzZTtcbiAgaWYgKCFkZWNvZGVkLnRva2VuX3VzZSB8fCAhVE9LRU5fVVNFUy5pbmNsdWRlcyhkZWNvZGVkLnRva2VuX3VzZSkpIHJldHVybiBmYWxzZTtcbiAgaWYgKGRlY29kZWQucHJpbmNpcGFsVHlwZSA9PT0gJ3NlcnZpY2UnKSB7XG4gICAgcmV0dXJuIGRlY29kZWQudG9rZW5fdXNlID09PSAnYWNjZXNzJ1xuICAgICAgJiYgdHlwZW9mIGRlY29kZWQuc3ViID09PSAnc3RyaW5nJ1xuICAgICAgJiYgZGVjb2RlZC5zdWIuc3RhcnRzV2l0aChTRVJWSUNFX1NVQkpFQ1RfUFJFRklYKVxuICAgICAgJiYgZGVjb2RlZC5zdWIubGVuZ3RoID4gU0VSVklDRV9TVUJKRUNUX1BSRUZJWC5sZW5ndGg7XG4gIH1cbiAgcmV0dXJuIEFycmF5LmlzQXJyYXkoZGVjb2RlZC5hbXIpXG4gICAgJiYgZGVjb2RlZC5hbXIuZXZlcnkoKG0pID0+IEFVVEhfTUVUSE9EUy5pbmNsdWRlcyhtKSlcbiAgICAmJiAoZGVjb2RlZC5hYWwgPT09IDEgfHwgZGVjb2RlZC5hYWwgPT09IDIpXG4gICAgJiYgdHlwZW9mIGRlY29kZWQuYXV0aF90aW1lID09PSAnbnVtYmVyJztcbn1cblxuIl19
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Authorization gates — permissions, system admin, features — and the
3
+ * `authz.denied` audit sink they report refusals to.
4
+ */
5
+ import type { Request, Response, NextFunction } from 'express';
6
+ import { type Permission } from '../types/permissions.js';
7
+ /**
8
+ * Whether the request's user holds `permission`. Superadmins implicitly hold
9
+ * every permission. Reads the resolved `permissions` claim (set at token issue,
10
+ * or re-derived per request by the platform).
11
+ */
12
+ export declare function userHasPermission(req: Request, permission: Permission): boolean;
13
+ /**
14
+ * Context passed to a registered authorization-denial auditor when a
15
+ * state-changing request is rejected by `requirePermission` /
16
+ * `requireSystemAdmin`.
17
+ */
18
+ export interface AuthzDenialInfo {
19
+ /** The denied user's id (`sub`), if authenticated. */
20
+ actorId?: string;
21
+ actorEmail?: string;
22
+ /** The user's active org at denial time. */
23
+ orgId?: string;
24
+ /** HTTP method + path of the rejected request. */
25
+ method: string;
26
+ path: string;
27
+ /** What was required: the missing permission(s), or 'system-admin'. */
28
+ required: string;
29
+ }
30
+ /**
31
+ * Register (or clear, with `undefined`) the process-wide authorization-denial
32
+ * auditor. Idempotent; the last registration wins.
33
+ */
34
+ export declare function setAuthzDenialAuditor(fn: ((info: AuthzDenialInfo) => void) | undefined): void;
35
+ /**
36
+ * Emit a denial event, best-effort. Only fires for state-changing (non-GET,
37
+ * non-HEAD/OPTIONS) requests — a rejected GET is low-signal probing noise and
38
+ * would amplify audit volume under a scan. Never throws.
39
+ *
40
+ * The permission gates here call it themselves. Export it for data-driven checks
41
+ * a route makes inline (cross-org access, service-only endpoints), so those 403s
42
+ * reach the same `authz.denied` trail. `required` names what was missing.
43
+ */
44
+ export declare function recordAuthzDenial(req: Request, required: string): void;
45
+ /**
46
+ * Requires that the user hold AT LEAST ONE of the given permissions (or be a
47
+ * superadmin). Use after requireAuth. Mirrors `requireRole`'s any-of semantics;
48
+ * pass a single permission for a specific action, or several when any one of
49
+ * them should grant access.
50
+ */
51
+ export declare function requirePermission(...permissions: Permission[]): (req: Request, res: Response, next: NextFunction) => void;
52
+ /**
53
+ * Requires that the user hold EVERY one of the given permissions (or be a
54
+ * superadmin) — the AND counterpart to `requirePermission`'s any-of. Use for a
55
+ * sensitive action that legitimately demands two distinct capabilities at once
56
+ * (e.g. a cross-surface operation), so the guarantee is explicit rather than
57
+ * approximated by chaining two `requirePermission` gates. Superadmins bypass
58
+ * (they implicitly hold every permission).
59
+ */
60
+ export declare function requireAllPermissions(...permissions: Permission[]): (req: Request, res: Response, next: NextFunction) => void;
61
+ /**
62
+ * Like {@link requirePermission} (any-of) but ALSO admits an internal service
63
+ * principal (`principalType: 'service'`). For READ endpoints that BOTH
64
+ * interactive users — who must hold one of the `:read` capabilities — AND
65
+ * service-to-service callers legitimately hit; service tokens carry no
66
+ * permission claims (they're least-privilege `role:member`), so a plain
67
+ * `requirePermission` would wrongly 403 them. Superadmins pass via
68
+ * `userHasPermission`'s implicit-all. Use only where a service caller is a known,
69
+ * intended consumer of the route (verify the callers); prefer plain
70
+ * `requirePermission` for purely user-facing reads.
71
+ */
72
+ export declare function requirePermissionOrService(...permissions: Permission[]): (req: Request, res: Response, next: NextFunction) => void;
73
+ /**
74
+ * Gate an endpoint to internal service-to-service callers only (a
75
+ * `principalType: 'service'` principal minted via {@link signServiceToken} / `getServiceAuthHeader`).
76
+ * Rejects any user token. For NON-user-facing endpoints — entity-event ingest,
77
+ * auto-subscribe, and similar peer-service hooks — that a browser must never
78
+ * reach. Compose after `requireAuth` so `req.user` is populated.
79
+ */
80
+ export declare function requireServicePrincipal(req: Request, res: Response, next: NextFunction): void;
81
+ /**
82
+ * THE gate for an INTERNAL route — the one every `/internal/*` path, the
83
+ * quota usage counters, the entity-event ingest and the audit ingest go through.
84
+ *
85
+ * Two rules, both fail-closed:
86
+ *
87
+ * 1. **No user token, ever.** Not a member's, not a superadmin's, not a
88
+ * service-account key's. An internal route is a peer-service API surface, so
89
+ * "a sufficiently privileged human" is not an acceptable caller — that
90
+ * equivalence is exactly how a browser-reachable path becomes a tenant
91
+ * boundary bug. `requireServicePrincipal` already refused users; this also
92
+ * closes the `|| isSystemAdmin(req)` escape hatches that had grown around
93
+ * the individual internal routes.
94
+ * 2. **Only the NAMED callers.** `callers` lists the services that legitimately
95
+ * call this route, and the name is cryptographically bound to the signing
96
+ * key (`services/service-keys.ts`), so this is an identity check rather than
97
+ * a claim check. It is the same allow-list the mesh policy names, at the
98
+ * layer that also holds in docker compose, where there is no mesh at all —
99
+ * the Istio `AuthorizationPolicy` is defence in depth on top, never the
100
+ * enforcement.
101
+ *
102
+ * Compose after `requireAuth` so `req.user` is populated. Refusals are counted
103
+ * (`internal_route_refused_total`) and audited through the shared `authz.denied`
104
+ * sink.
105
+ */
106
+ export declare function requireInternalService(options: {
107
+ callers: readonly string[];
108
+ }): (req: Request, res: Response, next: NextFunction) => void;
109
+ /**
110
+ * Check if the request is from a system admin.
111
+ *
112
+ * Authority is granted solely by the user-level `isSuperAdmin` flag carried
113
+ * in the JWT — never by membership in the well-known 'system' org, which would
114
+ * conflate a Pipeline Builder operator with a customer tenant and let any
115
+ * write that created a 'system'-named org quietly grant cross-org authority. The `system` org still exists as a *content holder* for shared
116
+ * sample data; it confers no privilege.
117
+ */
118
+ export declare function isSystemAdmin(req: Request): boolean;
119
+ /**
120
+ * Whether the request's token carries a specific capability `scope` (e.g.
121
+ * `'reporting:ingest'`). Used to gate endpoints that accept a narrow machine
122
+ * identity — a normal interactive user token has no `scope` and returns false.
123
+ */
124
+ export declare function hasScope(req: Request, scope: string): boolean;
125
+ /** Requires a system admin — granted solely by the `isSuperAdmin` token claim.
126
+ * Use after requireAuth. */
127
+ export declare function requireSystemAdmin(req: Request, res: Response, next: NextFunction): void;
128
+ /**
129
+ * Require a specific feature flag. Use after requireAuth.
130
+ * Checks the `features` array in the JWT payload (set at token issuance).
131
+ * Sysadmins (isSuperAdmin) bypass — they always have every feature.
132
+ */
133
+ export declare function requireFeature(feature: string): (req: Request, res: Response, next: NextFunction) => void;