@pipeline-builder/api-core 4.1.12 → 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.
- package/README.md +3 -2
- package/lib/constants/index.d.ts +0 -1
- package/lib/constants/index.js +1 -2
- package/lib/errors/app-errors.d.ts +8 -3
- package/lib/errors/app-errors.js +10 -5
- package/lib/helpers/access-helpers.d.ts +4 -6
- package/lib/helpers/access-helpers.js +6 -8
- package/lib/helpers/index.d.ts +3 -3
- package/lib/helpers/index.js +4 -4
- package/lib/helpers/org-hierarchy-http.d.ts +11 -10
- package/lib/helpers/org-hierarchy-http.js +24 -35
- package/lib/helpers/org-hierarchy.d.ts +37 -3
- package/lib/helpers/org-hierarchy.js +45 -4
- package/lib/helpers/restore-helpers.d.ts +44 -24
- package/lib/helpers/restore-helpers.js +41 -52
- package/lib/helpers/sse-helpers.d.ts +29 -7
- package/lib/helpers/sse-helpers.js +36 -9
- package/lib/index.d.ts +16 -14
- package/lib/index.js +17 -15
- package/lib/middleware/assurance.d.ts +116 -0
- package/lib/middleware/assurance.js +176 -0
- package/lib/middleware/auth.d.ts +5 -427
- package/lib/middleware/auth.js +36 -789
- package/lib/middleware/ecosystem-guard.d.ts +47 -0
- package/lib/middleware/ecosystem-guard.js +60 -0
- package/lib/middleware/index.d.ts +11 -4
- package/lib/middleware/index.js +12 -5
- package/lib/middleware/jwt-verify.d.ts +52 -0
- package/lib/middleware/jwt-verify.js +105 -0
- package/lib/middleware/permission-gates.d.ts +133 -0
- package/lib/middleware/permission-gates.js +260 -0
- package/lib/middleware/revocation.d.ts +96 -0
- package/lib/middleware/revocation.js +162 -0
- package/lib/middleware/route-table.d.ts +11 -0
- package/lib/middleware/route-table.js +4 -1
- package/lib/middleware/service-tokens.d.ts +83 -0
- package/lib/middleware/service-tokens.js +158 -0
- package/lib/middleware/step-up.d.ts +2 -2
- package/lib/middleware/step-up.js +8 -7
- package/lib/middleware/system-org.d.ts +20 -0
- package/lib/middleware/system-org.js +25 -0
- package/lib/openapi/routes/plugin-routes.js +133 -10
- package/lib/openapi/schema-registry.js +2 -5
- package/lib/routes/index.d.ts +1 -1
- package/lib/routes/index.js +2 -2
- package/lib/services/api-key-exchange.js +7 -5
- package/lib/services/audit-spool.d.ts +34 -10
- package/lib/services/audit-spool.js +75 -34
- package/lib/services/cache-service.d.ts +7 -0
- package/lib/services/cache-service.js +1 -1
- package/lib/services/circuit-breaker.d.ts +14 -1
- package/lib/services/circuit-breaker.js +36 -10
- package/lib/services/compliance-client.d.ts +10 -2
- package/lib/services/compliance-client.js +17 -10
- package/lib/services/compliance-event-subscriber.js +6 -6
- package/lib/services/durable-event-bus.js +8 -11
- package/lib/services/ecosystem-notify-client.d.ts +32 -0
- package/lib/services/ecosystem-notify-client.js +71 -0
- package/lib/services/env-redis.d.ts +4 -1
- package/lib/services/env-redis.js +32 -3
- package/lib/services/http-client.d.ts +19 -0
- package/lib/services/http-client.js +98 -19
- package/lib/services/index.d.ts +25 -24
- package/lib/services/index.js +25 -22
- package/lib/services/jwks-cache.js +5 -4
- package/lib/services/leader-lock.d.ts +22 -5
- package/lib/services/leader-lock.js +61 -6
- package/lib/services/quota.d.ts +9 -4
- package/lib/services/quota.js +34 -40
- package/lib/services/remote-audit-client.d.ts +28 -32
- package/lib/services/remote-audit-client.js +166 -49
- package/lib/services/retry-forever.js +5 -11
- package/lib/services/retry-strategy.d.ts +1 -1
- package/lib/services/retry-strategy.js +5 -4
- package/lib/services/scheduler.d.ts +8 -3
- package/lib/services/scheduler.js +17 -5
- package/lib/services/service-boot.d.ts +14 -13
- package/lib/services/service-boot.js +18 -15
- package/lib/services/service-keys.d.ts +1 -1
- package/lib/services/service-keys.js +12 -18
- package/lib/services/sse-ticket-store.d.ts +9 -1
- package/lib/services/sse-ticket-store.js +24 -9
- package/lib/services/system-notification.d.ts +27 -0
- package/lib/services/system-notification.js +50 -0
- package/lib/services/token-revocation.d.ts +50 -1
- package/lib/services/token-revocation.js +103 -3
- package/lib/types/audit-events.d.ts +6 -6
- package/lib/types/audit-events.js +1 -1
- package/lib/types/common.d.ts +32 -13
- package/lib/types/common.js +2 -1
- package/lib/types/ecosystem-notifications.d.ts +142 -0
- package/lib/types/ecosystem-notifications.js +210 -0
- package/lib/types/ecosystem.d.ts +77 -0
- package/lib/types/ecosystem.js +119 -0
- package/lib/types/error-codes.d.ts +81 -0
- package/lib/types/error-codes.js +108 -1
- package/lib/types/feature-flags.d.ts +21 -1
- package/lib/types/feature-flags.js +36 -2
- package/lib/types/index.d.ts +14 -9
- package/lib/types/index.js +15 -10
- package/lib/types/permissions.d.ts +46 -8
- package/lib/types/permissions.js +75 -12
- package/lib/types/plugin-catalog.d.ts +35 -0
- package/lib/types/plugin-catalog.js +39 -0
- package/lib/types/plugin-health.d.ts +102 -0
- package/lib/types/plugin-health.js +184 -0
- package/lib/types/quota-tiers.d.ts +15 -0
- package/lib/types/quota-tiers.js +23 -3
- package/lib/types/wire-vocabulary.d.ts +102 -0
- package/lib/types/wire-vocabulary.js +94 -0
- package/lib/utils/alias-resolver.js +2 -2
- package/lib/utils/audit.d.ts +5 -2
- package/lib/utils/audit.js +6 -3
- package/lib/utils/compliance-attributes.js +3 -4
- package/lib/utils/concurrency.d.ts +7 -0
- package/lib/utils/concurrency.js +13 -1
- package/lib/utils/env.d.ts +3 -4
- package/lib/utils/env.js +4 -5
- package/lib/utils/identity.d.ts +13 -4
- package/lib/utils/identity.js +18 -10
- package/lib/utils/index.d.ts +13 -10
- package/lib/utils/index.js +13 -10
- package/lib/utils/jwk.d.ts +4 -0
- package/lib/utils/jwk.js +13 -1
- package/lib/utils/logger.d.ts +1 -1
- package/lib/utils/logger.js +1 -1
- package/lib/utils/object.d.ts +11 -0
- package/lib/utils/object.js +24 -1
- package/lib/utils/params.d.ts +8 -22
- package/lib/utils/params.js +15 -43
- package/lib/utils/plugin-compliance.d.ts +98 -0
- package/lib/utils/plugin-compliance.js +78 -0
- package/lib/utils/proof-of-work.d.ts +69 -0
- package/lib/utils/proof-of-work.js +139 -0
- package/lib/utils/response.d.ts +16 -6
- package/lib/utils/response.js +18 -15
- package/lib/utils/retention.d.ts +17 -0
- package/lib/utils/retention.js +30 -0
- package/lib/utils/secret-encryption.d.ts +7 -7
- package/lib/utils/secret-encryption.js +9 -10
- package/lib/utils/secret-rotation.js +2 -2
- package/lib/utils/sensitive-patterns.js +4 -5
- package/lib/utils/service-registry.d.ts +15 -0
- package/lib/utils/service-registry.js +24 -0
- package/lib/utils/ssrf.d.ts +11 -2
- package/lib/utils/ssrf.js +112 -33
- package/lib/validation/ai-schemas.js +3 -2
- package/lib/validation/common-schemas.d.ts +0 -9
- package/lib/validation/common-schemas.js +1 -7
- package/lib/validation/dockerfile-static.d.ts +26 -0
- package/lib/validation/dockerfile-static.js +210 -0
- package/lib/validation/index.d.ts +13 -4
- package/lib/validation/index.js +14 -5
- package/lib/validation/message-schemas.js +6 -4
- package/lib/validation/pipeline-schemas.d.ts +8 -2
- package/lib/validation/pipeline-schemas.js +30 -4
- package/lib/validation/plugin-base-images.d.ts +20 -0
- package/lib/validation/plugin-base-images.js +17 -0
- package/lib/validation/plugin-catalog-detect.d.ts +86 -0
- package/lib/validation/plugin-catalog-detect.js +237 -0
- package/lib/validation/plugin-catalog-metadata.d.ts +141 -0
- package/lib/validation/plugin-catalog-metadata.js +162 -0
- package/lib/validation/plugin-heuristics.d.ts +41 -0
- package/lib/validation/plugin-heuristics.js +211 -0
- package/lib/validation/plugin-lint.d.ts +18 -0
- package/lib/validation/plugin-lint.js +136 -0
- package/lib/validation/plugin-name-confusable.d.ts +15 -0
- package/lib/validation/plugin-name-confusable.js +81 -0
- package/lib/validation/plugin-schemas.d.ts +40 -26
- package/lib/validation/plugin-schemas.js +26 -23
- package/lib/validation/plugin-spec-schema.d.ts +112 -0
- package/lib/validation/plugin-spec-schema.js +163 -0
- package/lib/validation/plugin-template-contract.d.ts +64 -0
- package/lib/validation/plugin-template-contract.js +113 -0
- package/package.json +69 -4
- package/lib/testing/mock-api-core.d.ts +0 -150
- package/lib/testing/mock-api-core.js +0 -246
- package/lib/testing/mock-drizzle.d.ts +0 -6
- package/lib/testing/mock-drizzle.js +0 -57
- package/lib/testing/route-coverage.d.ts +0 -64
- package/lib/testing/route-coverage.js +0 -183
- package/lib/testing/service-tokens.d.ts +0 -66
- package/lib/testing/service-tokens.js +0 -137
- package/lib/testing/tier-mock.d.ts +0 -44
- package/lib/testing/tier-mock.js +0 -65
- package/lib/testing/user-tokens.d.ts +0 -75
- 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
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
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';
|
package/lib/middleware/index.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
// Copyright 2026 Pipeline Builder Contributors
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
|
|
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;
|