@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.
- 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
package/lib/middleware/auth.d.ts
CHANGED
|
@@ -1,43 +1,9 @@
|
|
|
1
|
-
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
-
import { type OrgAdminAssuranceMachines } from './route-table.js';
|
|
3
|
-
import { type AssuranceLevel, type JwtPayload } from '../types/common.js';
|
|
4
|
-
import { ErrorCode } from '../types/error-codes.js';
|
|
5
|
-
import { type Permission } from '../types/permissions.js';
|
|
6
|
-
/**
|
|
7
|
-
* Verify a token that speaks for a PERSON — access, step-up, and the token an
|
|
8
|
-
* opaque access key is exchanged for — against platform's published signing
|
|
9
|
-
* keys.
|
|
10
|
-
*
|
|
11
|
-
* ES256 and a `kid` are both mandatory: no `kid` means no way to pick the right
|
|
12
|
-
* key across a rotation, and any other algorithm is refused outright, which is
|
|
13
|
-
* what makes "no service accepts an HS256 user token" a property of the code
|
|
14
|
-
* rather than a convention. Key lookup goes through the shared JWKS cache
|
|
15
|
-
* (10-minute refresh, one refetch on an unknown `kid`, brief negative cache).
|
|
16
|
-
*
|
|
17
|
-
* @throws {UnknownKidError} the key set is current and has no such key → 401.
|
|
18
|
-
* @throws {JwksUnavailableError} the key set could not be obtained → 503; an
|
|
19
|
-
* unverifiable token is never treated as valid.
|
|
20
|
-
*/
|
|
21
|
-
export declare function verifyUserJwt<T = JwtPayload>(token: string): Promise<T>;
|
|
22
1
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Since #14 there is no shared secret left: BOTH chains are ES256 with a `kid`,
|
|
26
|
-
* so the dispatch is by **who owns the `kid`** rather than by algorithm — and
|
|
27
|
-
* because a `kid` is an RFC 7638 thumbprint, ownership is a fact about the key,
|
|
28
|
-
* not a claim the token makes about itself:
|
|
29
|
-
*
|
|
30
|
-
* - a `kid` published in the per-service key bundle → an INTERNAL SERVICE token
|
|
31
|
-
* ({@link signServiceToken}), verified with that service's key; its `sub` must
|
|
32
|
-
* name the same service, so one service can never speak for another.
|
|
33
|
-
* - anything else → a platform-signed USER token, verified against platform's
|
|
34
|
-
* published JWKS. A token claiming `principalType: 'service'` is refused
|
|
35
|
-
* here: an internal identity may not ride the user signing key.
|
|
36
|
-
*
|
|
37
|
-
* Anything that is not ES256 with a `kid` fails in both chains, which is what
|
|
38
|
-
* makes "no HS256 token is accepted anywhere" a property of the code.
|
|
2
|
+
* `requireAuth`: bearer / access-key authentication, the checks every
|
|
3
|
+
* verified request passes, and the tenant identity it attaches.
|
|
39
4
|
*/
|
|
40
|
-
|
|
5
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
6
|
+
import { type AssuranceLevel } from '../types/common.js';
|
|
41
7
|
export interface RequireAuthOptions {
|
|
42
8
|
/**
|
|
43
9
|
* Allow x-org-id/x-org-name headers to override the JWT's organization fields.
|
|
@@ -50,7 +16,7 @@ export interface RequireAuthOptions {
|
|
|
50
16
|
*/
|
|
51
17
|
allowOrgHeaderOverride?: boolean;
|
|
52
18
|
/**
|
|
53
|
-
* Demand that the whole SESSION be at least this assurance level
|
|
19
|
+
* Demand that the whole SESSION be at least this assurance level.
|
|
54
20
|
*
|
|
55
21
|
* `minAssurance: 2` means the person authenticated with a second factor — a
|
|
56
22
|
* passkey with user verification, a password plus an authenticator code, or
|
|
@@ -82,391 +48,3 @@ export interface RequireAuthOptions {
|
|
|
82
48
|
/** JWT auth middleware. Use directly or call with options. */
|
|
83
49
|
export declare function requireAuth(req: Request, res: Response, next: NextFunction): void;
|
|
84
50
|
export declare function requireAuth(options?: RequireAuthOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
85
|
-
/**
|
|
86
|
-
* A source of the CURRENT `tokenVersion` for a user, backed by a store the
|
|
87
|
-
* stateless services can read (in practice a Redis key the platform publishes
|
|
88
|
-
* to on every privilege change). Registered via {@link setTokenRevocationStore}.
|
|
89
|
-
*
|
|
90
|
-
* `getCurrentVersion` returns the user's current version, or `null` when the
|
|
91
|
-
* store has no entry — a miss is treated as "no known revocation" (fail-open):
|
|
92
|
-
* revocation entries are published with a TTL matching the access-token
|
|
93
|
-
* lifetime, so a missing entry means any token old enough to predate it has
|
|
94
|
-
* already expired on its own.
|
|
95
|
-
*/
|
|
96
|
-
export interface TokenRevocationStore {
|
|
97
|
-
getCurrentVersion(userId: string): Promise<number | null>;
|
|
98
|
-
/**
|
|
99
|
-
* Whether one IMPERSONATION session (by token `jti`) has been ended early.
|
|
100
|
-
*
|
|
101
|
-
* Optional so a store that predates it still links — but a store WITHOUT it
|
|
102
|
-
* cannot answer, and an impersonation token is then REJECTED. Unlike
|
|
103
|
-
* `getCurrentVersion`, this is deliberately not fail-open: the platform ends a
|
|
104
|
-
* session when someone withdraws consent, and a withdrawn consent that keeps
|
|
105
|
-
* working in every other service is not withdrawn.
|
|
106
|
-
*/
|
|
107
|
-
getSessionRevocation?(jti: string): Promise<SessionRevocationState>;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* `revoked` — the session was ended; reject.
|
|
111
|
-
* `live` — no revocation recorded; allow.
|
|
112
|
-
* `unavailable` — the store couldn't be read or can't answer; REJECT. Distinct
|
|
113
|
-
* from `live` precisely so an outage can't read as "not revoked".
|
|
114
|
-
*/
|
|
115
|
-
export type SessionRevocationState = 'revoked' | 'live' | 'unavailable';
|
|
116
|
-
/**
|
|
117
|
-
* Register (or clear, with `undefined`) the process-wide token-revocation store.
|
|
118
|
-
* When unset (the default), `requireAuth` performs NO revocation check (it relies
|
|
119
|
-
* on the short access-token TTL); services opt in at boot by wiring their Redis
|
|
120
|
-
* client. Platform keeps its own Mongo-backed check and need not register one.
|
|
121
|
-
*/
|
|
122
|
-
export declare function setTokenRevocationStore(store: TokenRevocationStore | undefined): void;
|
|
123
|
-
/**
|
|
124
|
-
* Public revocation check for routes that verify a platform JWT OUTSIDE
|
|
125
|
-
* `requireAuth` (e.g. the image-registry `/token` mint path, which resolves
|
|
126
|
-
* identity itself). Returns true when the token's `tokenVersion` is strictly
|
|
127
|
-
* behind the store's current version. Fail-open (never throws) — a store outage
|
|
128
|
-
* or a token/store without a usable version yields false (allow), matching
|
|
129
|
-
* `requireAuth`. Pass the verified JWT claims (`sub`, `tokenVersion`).
|
|
130
|
-
*/
|
|
131
|
-
export declare function isAccessTokenRevoked(claims: {
|
|
132
|
-
sub?: string;
|
|
133
|
-
tokenVersion?: number;
|
|
134
|
-
jti?: string;
|
|
135
|
-
impersonatorId?: string;
|
|
136
|
-
}): Promise<boolean>;
|
|
137
|
-
/**
|
|
138
|
-
* Whether a set of verified claims speaks for a PERSON's own session — the only
|
|
139
|
-
* kind of credential an assurance level can be asserted about.
|
|
140
|
-
*
|
|
141
|
-
* Excludes all three machine shapes: an internal service principal, an org
|
|
142
|
-
* service account, and any exchanged access key (`token_use: 'api_key'`, i.e. a
|
|
143
|
-
* PAT or a service-account key), each of which authenticates a program holding a
|
|
144
|
-
* secret rather than a person presenting factors.
|
|
145
|
-
*/
|
|
146
|
-
export declare function isHumanPrincipal(claims: Pick<JwtPayload, 'principalType' | 'token_use'> | undefined): boolean;
|
|
147
|
-
/** What an assurance gate demands. */
|
|
148
|
-
export interface AssuranceOptions {
|
|
149
|
-
minAssurance: AssuranceLevel;
|
|
150
|
-
maxAge?: number;
|
|
151
|
-
/**
|
|
152
|
-
* A NAMED carve-out for requests that cannot possibly satisfy the level and
|
|
153
|
-
* must still be served — today only one exists: the bootstrap-administrator
|
|
154
|
-
* window, where a fresh install's single admin has no factor yet and the setup
|
|
155
|
-
* calls that create the install's automation credential would otherwise be
|
|
156
|
-
* unreachable (see platform's `isBootstrapSetupRequest`).
|
|
157
|
-
*
|
|
158
|
-
* It is deliberately shaped as `{ reason, when }` rather than a bare predicate:
|
|
159
|
-
* the reason is counted (`assurance_exempted_total{reason}`) and published on
|
|
160
|
-
* the route table as `assuranceExempt`, so an exemption cannot be added without
|
|
161
|
-
* showing up in the generated table a reviewer reads. The gate keeps its
|
|
162
|
-
* `minAssurance` tag either way — the route still requires MFA-grade for
|
|
163
|
-
* everyone the carve-out does not name.
|
|
164
|
-
*/
|
|
165
|
-
exempt?: {
|
|
166
|
-
reason: string;
|
|
167
|
-
when: (req: Request) => boolean;
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Standalone assurance gate, for a service whose own `requireAuth` is not
|
|
172
|
-
* api-core's (platform's is its own, because it reads Mongo). Compose it AFTER
|
|
173
|
-
* authentication: `router.get('/x', requireAuth, requireAssurance({ minAssurance: 2 }), h)`.
|
|
174
|
-
*
|
|
175
|
-
* Identical rules and identical refusals to `requireAuth({ minAssurance })` —
|
|
176
|
-
* that option simply calls the same check inline, so there is one implementation
|
|
177
|
-
* of "is this session MFA-grade" in the codebase.
|
|
178
|
-
*/
|
|
179
|
-
export declare function requireAssurance(options: AssuranceOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
180
|
-
/**
|
|
181
|
-
* Handler-level twin of {@link requireAssurance}, for a route where only SOME
|
|
182
|
-
* requests must be MFA-grade — e.g. a policy route on which TIGHTENING a setting
|
|
183
|
-
* stays open to a single-factor session (so an admin without MFA can adopt it)
|
|
184
|
-
* but LOOSENING it does not. Same rules and the same three refusals. Returns
|
|
185
|
-
* true when the request was REFUSED (a response has been sent).
|
|
186
|
-
*/
|
|
187
|
-
export declare function refuseWeakSession(req: Request, res: Response, options: AssuranceOptions): boolean;
|
|
188
|
-
/** What {@link requireOrgAdminAssurance} demands of a route. */
|
|
189
|
-
export interface OrgAdminAssuranceOptions {
|
|
190
|
-
/** What a machine credential (PAT, service account, machine session) meets on
|
|
191
|
-
* this route while the policy is on — see {@link OrgAdminAssuranceMachines}.
|
|
192
|
-
* Required, so every route states its decision rather than inheriting one. */
|
|
193
|
-
machines: OrgAdminAssuranceMachines;
|
|
194
|
-
}
|
|
195
|
-
/** `details.reason` on a refusal by {@link requireOrgAdminAssurance}, so the UI
|
|
196
|
-
* can say it is the ORG's policy asking, not the route's own requirement. */
|
|
197
|
-
export declare const ORG_ADMIN_MFA_REASON = "org_admin_policy";
|
|
198
|
-
/**
|
|
199
|
-
* The refusal {@link requireOrgAdminAssurance} would send for these claims, or
|
|
200
|
-
* `undefined` when the request may proceed. Exported so a handler whose policy
|
|
201
|
-
* applies to only PART of a route (e.g. `generate-token`, where opening a new
|
|
202
|
-
* machine credential is gated but renewing an existing one is not) applies the
|
|
203
|
-
* exact same rule rather than a copy of it.
|
|
204
|
-
*/
|
|
205
|
-
export declare function orgAdminAssuranceRefusal(claims: JwtPayload | undefined, options: OrgAdminAssuranceOptions): {
|
|
206
|
-
status: number;
|
|
207
|
-
code: ErrorCode;
|
|
208
|
-
message: string;
|
|
209
|
-
reason: 'machine_principal' | 'weak_session';
|
|
210
|
-
} | undefined;
|
|
211
|
-
/**
|
|
212
|
-
* Apply {@link orgAdminAssuranceRefusal} and send its refusal. Returns true when
|
|
213
|
-
* the request was REFUSED (a response has been sent). For handler-level use;
|
|
214
|
-
* routes use {@link requireOrgAdminAssurance}.
|
|
215
|
-
*/
|
|
216
|
-
export declare function refuseForOrgAdminAssurance(req: Request, res: Response, options: OrgAdminAssuranceOptions): boolean;
|
|
217
|
-
/**
|
|
218
|
-
* The org policy "administrative actions require MFA" (`adminActionsRequireMfa`).
|
|
219
|
-
*
|
|
220
|
-
* Unlike {@link requireAssurance}, the requirement is the ORG's choice, not the
|
|
221
|
-
* route's: the gate reads the `org_admin_aal` claim, set at token issue from the
|
|
222
|
-
* active org's policy (strictest across its ancestors), so a service that cannot
|
|
223
|
-
* read the org document still enforces it. With the policy off the gate is a
|
|
224
|
-
* no-op; with it on, a single-factor session is refused 401 `MFA_REQUIRED`
|
|
225
|
-
* (with `details.reason: 'org_admin_policy'`) — the same code the client already
|
|
226
|
-
* turns into "enrol a factor / sign in with it", never a sign-out.
|
|
227
|
-
*
|
|
228
|
-
* `machines` is decided per route (see {@link OrgAdminAssuranceMachines}): the
|
|
229
|
-
* policy is about how strongly a PERSON's session was opened, so automation that
|
|
230
|
-
* legitimately drives a route keeps working (`allow`), while a route that mints
|
|
231
|
-
* a further credential refuses machines outright (`refuse`, 403
|
|
232
|
-
* `HUMAN_SESSION_REQUIRED` — the same refusal `requireAssurance` gives them).
|
|
233
|
-
*/
|
|
234
|
-
export declare function requireOrgAdminAssurance(options: OrgAdminAssuranceOptions): (req: Request, res: Response, next: NextFunction) => void;
|
|
235
|
-
/**
|
|
236
|
-
* Whether a verified token carries a well-formed identity: a known
|
|
237
|
-
* `principalType` and `token_use`, and the claims that kind of principal must
|
|
238
|
-
* have. Fails closed — a token minted before these claims existed, or one with a
|
|
239
|
-
* contradictory combination, is not an identity any gate should reason about.
|
|
240
|
-
*
|
|
241
|
-
* - `service`: `token_use: 'access'` and a `service:<name>` subject (the name
|
|
242
|
-
* feeds the denylist and audit attribution).
|
|
243
|
-
* - `user` / `service_account`: `amr` (known methods), `aal` and `auth_time`.
|
|
244
|
-
*/
|
|
245
|
-
export declare function hasValidIdentityClaims(decoded: Partial<JwtPayload>): boolean;
|
|
246
|
-
/**
|
|
247
|
-
* Whether the request's user holds `permission`. Superadmins implicitly hold
|
|
248
|
-
* every permission. Reads the resolved `permissions` claim (set at token issue,
|
|
249
|
-
* or re-derived per request by the platform).
|
|
250
|
-
*/
|
|
251
|
-
export declare function userHasPermission(req: Request, permission: Permission): boolean;
|
|
252
|
-
/**
|
|
253
|
-
* Context passed to a registered authorization-denial auditor when a
|
|
254
|
-
* state-changing request is rejected by `requirePermission` /
|
|
255
|
-
* `requireSystemAdmin`.
|
|
256
|
-
*/
|
|
257
|
-
export interface AuthzDenialInfo {
|
|
258
|
-
/** The denied user's id (`sub`), if authenticated. */
|
|
259
|
-
actorId?: string;
|
|
260
|
-
actorEmail?: string;
|
|
261
|
-
/** The user's active org at denial time. */
|
|
262
|
-
orgId?: string;
|
|
263
|
-
/** HTTP method + path of the rejected request. */
|
|
264
|
-
method: string;
|
|
265
|
-
path: string;
|
|
266
|
-
/** What was required: the missing permission(s), or 'system-admin'. */
|
|
267
|
-
required: string;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Register (or clear, with `undefined`) the process-wide authorization-denial
|
|
271
|
-
* auditor. Idempotent; the last registration wins.
|
|
272
|
-
*/
|
|
273
|
-
export declare function setAuthzDenialAuditor(fn: ((info: AuthzDenialInfo) => void) | undefined): void;
|
|
274
|
-
/**
|
|
275
|
-
* Emit a denial event, best-effort. Only fires for state-changing (non-GET,
|
|
276
|
-
* non-HEAD/OPTIONS) requests — a rejected GET is low-signal probing noise and
|
|
277
|
-
* would amplify audit volume under a scan. Never throws.
|
|
278
|
-
*
|
|
279
|
-
* The permission gates here call it themselves. Export it for data-driven checks
|
|
280
|
-
* a route makes inline (cross-org access, service-only endpoints), so those 403s
|
|
281
|
-
* reach the same `authz.denied` trail. `required` names what was missing.
|
|
282
|
-
*/
|
|
283
|
-
export declare function recordAuthzDenial(req: Request, required: string): void;
|
|
284
|
-
/**
|
|
285
|
-
* Requires that the user hold AT LEAST ONE of the given permissions (or be a
|
|
286
|
-
* superadmin). Use after requireAuth. Mirrors `requireRole`'s any-of semantics;
|
|
287
|
-
* pass a single permission for a specific action, or several when any one of
|
|
288
|
-
* them should grant access.
|
|
289
|
-
*/
|
|
290
|
-
export declare function requirePermission(...permissions: Permission[]): (req: Request, res: Response, next: NextFunction) => void;
|
|
291
|
-
/**
|
|
292
|
-
* Requires that the user hold EVERY one of the given permissions (or be a
|
|
293
|
-
* superadmin) — the AND counterpart to `requirePermission`'s any-of. Use for a
|
|
294
|
-
* sensitive action that legitimately demands two distinct capabilities at once
|
|
295
|
-
* (e.g. a cross-surface operation), so the guarantee is explicit rather than
|
|
296
|
-
* approximated by chaining two `requirePermission` gates. Superadmins bypass
|
|
297
|
-
* (they implicitly hold every permission).
|
|
298
|
-
*/
|
|
299
|
-
export declare function requireAllPermissions(...permissions: Permission[]): (req: Request, res: Response, next: NextFunction) => void;
|
|
300
|
-
/**
|
|
301
|
-
* Like {@link requirePermission} (any-of) but ALSO admits an internal service
|
|
302
|
-
* principal (`principalType: 'service'`). For READ endpoints that BOTH
|
|
303
|
-
* interactive users — who must hold one of the `:read` capabilities — AND
|
|
304
|
-
* service-to-service callers legitimately hit; service tokens carry no
|
|
305
|
-
* permission claims (they're least-privilege `role:member`), so a plain
|
|
306
|
-
* `requirePermission` would wrongly 403 them. Superadmins pass via
|
|
307
|
-
* `userHasPermission`'s implicit-all. Use only where a service caller is a known,
|
|
308
|
-
* intended consumer of the route (verify the callers); prefer plain
|
|
309
|
-
* `requirePermission` for purely user-facing reads.
|
|
310
|
-
*/
|
|
311
|
-
export declare function requirePermissionOrService(...permissions: Permission[]): (req: Request, res: Response, next: NextFunction) => void;
|
|
312
|
-
/**
|
|
313
|
-
* Gate an endpoint to internal service-to-service callers only (a
|
|
314
|
-
* `principalType: 'service'` principal minted via {@link signServiceToken} / `getServiceAuthHeader`).
|
|
315
|
-
* Rejects any user token. For NON-user-facing endpoints — entity-event ingest,
|
|
316
|
-
* auto-subscribe, and similar peer-service hooks — that a browser must never
|
|
317
|
-
* reach. Compose after `requireAuth` so `req.user` is populated.
|
|
318
|
-
*/
|
|
319
|
-
export declare function requireServicePrincipal(req: Request, res: Response, next: NextFunction): void;
|
|
320
|
-
/**
|
|
321
|
-
* THE gate for an INTERNAL route (#14) — the one every `/internal/*` path, the
|
|
322
|
-
* quota usage counters, the entity-event ingest and the audit ingest go through.
|
|
323
|
-
*
|
|
324
|
-
* Two rules, both fail-closed:
|
|
325
|
-
*
|
|
326
|
-
* 1. **No user token, ever.** Not a member's, not a superadmin's, not a
|
|
327
|
-
* service-account key's. An internal route is a peer-service API surface, so
|
|
328
|
-
* "a sufficiently privileged human" is not an acceptable caller — that
|
|
329
|
-
* equivalence is exactly how a browser-reachable path becomes a tenant
|
|
330
|
-
* boundary bug. `requireServicePrincipal` already refused users; this also
|
|
331
|
-
* closes the `|| isSystemAdmin(req)` escape hatches that had grown around
|
|
332
|
-
* the individual internal routes.
|
|
333
|
-
* 2. **Only the NAMED callers.** `callers` lists the services that legitimately
|
|
334
|
-
* call this route, and the name is cryptographically bound to the signing
|
|
335
|
-
* key (`services/service-keys.ts`), so this is an identity check rather than
|
|
336
|
-
* a claim check. It is the same allow-list the mesh policy names, at the
|
|
337
|
-
* layer that also holds in docker compose, where there is no mesh at all —
|
|
338
|
-
* the Istio `AuthorizationPolicy` is defence in depth on top, never the
|
|
339
|
-
* enforcement.
|
|
340
|
-
*
|
|
341
|
-
* Compose after `requireAuth` so `req.user` is populated. Refusals are counted
|
|
342
|
-
* (`internal_route_refused_total`) and audited through the shared `authz.denied`
|
|
343
|
-
* sink.
|
|
344
|
-
*/
|
|
345
|
-
export declare function requireInternalService(options: {
|
|
346
|
-
callers: readonly string[];
|
|
347
|
-
}): (req: Request, res: Response, next: NextFunction) => void;
|
|
348
|
-
/**
|
|
349
|
-
* The system tenant's canonical org **id** — a fixed, well-known ObjectId (NOT
|
|
350
|
-
* the string 'system'). This is the single knob every service resolves the
|
|
351
|
-
* system tenant through; override via the `SYSTEM_ORG_ID` env for alternate
|
|
352
|
-
* installs. The system org's human identifier stays the slug/name 'system'
|
|
353
|
-
* (see {@link SYSTEM_ORG_SLUG}); only its `_id` is this ObjectId.
|
|
354
|
-
*/
|
|
355
|
-
export declare const SYSTEM_ORG_ID: string;
|
|
356
|
-
/** The system org's well-known slug/name (the human identifier; its `_id` is {@link SYSTEM_ORG_ID}). */
|
|
357
|
-
export declare const SYSTEM_ORG_SLUG = "system";
|
|
358
|
-
/**
|
|
359
|
-
* Check if an orgId or orgName/slug matches the system org. Use this instead of
|
|
360
|
-
* comparing directly: the id is now an ObjectId ({@link SYSTEM_ORG_ID}) while the
|
|
361
|
-
* name/slug is 'system' ({@link SYSTEM_ORG_SLUG}), so the two are compared against
|
|
362
|
-
* their respective canonical values.
|
|
363
|
-
*/
|
|
364
|
-
export declare function isSystemOrgId(orgId?: string, orgName?: string): boolean;
|
|
365
|
-
/**
|
|
366
|
-
* Check if the request is from a system admin.
|
|
367
|
-
*
|
|
368
|
-
* Authority is granted solely by the user-level `isSuperAdmin` flag carried
|
|
369
|
-
* in the JWT. The "membership in the well-known 'system' org with role
|
|
370
|
-
* admin/owner" path was removed — it conflated a Pipeline Builder operator
|
|
371
|
-
* with a real customer tenant in the data model, and meant any unintended
|
|
372
|
-
* write that created a 'system'-named org could quietly grant cross-org
|
|
373
|
-
* authority. The `system` org still exists as a *content holder* for shared
|
|
374
|
-
* sample data; it just no longer confers privilege.
|
|
375
|
-
*/
|
|
376
|
-
export declare function isSystemAdmin(req: Request): boolean;
|
|
377
|
-
/**
|
|
378
|
-
* Whether the request's token carries a specific capability `scope` (e.g.
|
|
379
|
-
* `'reporting:ingest'`). Used to gate endpoints that accept a narrow machine
|
|
380
|
-
* identity — a normal interactive user token has no `scope` and returns false.
|
|
381
|
-
*/
|
|
382
|
-
export declare function hasScope(req: Request, scope: string): boolean;
|
|
383
|
-
/** Requires a system admin — granted solely by the `isSuperAdmin` token claim
|
|
384
|
-
* (the org-membership path was removed). Use after requireAuth. */
|
|
385
|
-
export declare function requireSystemAdmin(req: Request, res: Response, next: NextFunction): void;
|
|
386
|
-
/**
|
|
387
|
-
* Require a specific feature flag. Use after requireAuth.
|
|
388
|
-
* Checks the `features` array in the JWT payload (set at token issuance).
|
|
389
|
-
* Sysadmins (isSuperAdmin) bypass — they always have every feature.
|
|
390
|
-
*/
|
|
391
|
-
export declare function requireFeature(feature: string): (req: Request, res: Response, next: NextFunction) => void;
|
|
392
|
-
/**
|
|
393
|
-
* The calling service's name for a SERVICE principal (`principalType: 'service'`),
|
|
394
|
-
* else `undefined`. A user token never yields a name, whatever its `sub` says.
|
|
395
|
-
*/
|
|
396
|
-
export declare function serviceNameOf(claims: {
|
|
397
|
-
principalType?: string;
|
|
398
|
-
sub?: string;
|
|
399
|
-
} | undefined): string | undefined;
|
|
400
|
-
/** True when the claims are a service principal whose service is on the denylist. */
|
|
401
|
-
export declare function isServiceTokenDenied(claims: {
|
|
402
|
-
principalType?: string;
|
|
403
|
-
sub?: string;
|
|
404
|
-
}): boolean;
|
|
405
|
-
export interface ServiceTokenOptions {
|
|
406
|
-
/** Calling service identifier (e.g. 'billing', 'platform'). Embedded as `sub: service:<name>`. */
|
|
407
|
-
serviceName: string;
|
|
408
|
-
/** Active org context for the call. Use the target tenant's org id, or the
|
|
409
|
-
* well-known `SYSTEM_ORG_ID` (an ObjectId) for system-wide ops — NOT the string
|
|
410
|
-
* 'system', which id-based system-org checks won't recognize. */
|
|
411
|
-
orgId?: string;
|
|
412
|
-
/** Active org name. Defaults to orgId. */
|
|
413
|
-
orgName?: string;
|
|
414
|
-
/** TTL in seconds (default 300). */
|
|
415
|
-
ttlSeconds?: number;
|
|
416
|
-
/**
|
|
417
|
-
* Role the token carries (required — no implicit default). **Pass the LOWEST
|
|
418
|
-
* role the call actually needs** (`'member'` for read / data-plane calls) so a
|
|
419
|
-
* leaked service token can't perform admin actions in the target org.
|
|
420
|
-
* `isAdmin` is derived from this (admin|owner → true).
|
|
421
|
-
*/
|
|
422
|
-
role: 'owner' | 'admin' | 'member';
|
|
423
|
-
/**
|
|
424
|
-
* Optional fine-grained permission claim. Prefer this + `role: 'member'` over a
|
|
425
|
-
* high role when a call needs ONE specific capability (e.g. `['plugins:write']`
|
|
426
|
-
* to push a plugin image) — the token then satisfies that permission gate
|
|
427
|
-
* WITHOUT carrying `isAdmin`, so a leaked token can't perform admin actions.
|
|
428
|
-
*/
|
|
429
|
-
permissions?: Permission[];
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Mint a JWT identifying the calling service. Used for inter-service HTTP calls.
|
|
433
|
-
* The token satisfies `requireAuth` and (when orgId is present) `requireOrganization`.
|
|
434
|
-
* Scope it with `opts.role` — least privilege keeps a leaked token low-value.
|
|
435
|
-
*
|
|
436
|
-
* `opts.serviceName` must be THIS process's own identity (`SERVICE_NAME`): a
|
|
437
|
-
* service holds only its own signing key, so minting a token that names another
|
|
438
|
-
* service throws rather than producing one every peer would reject. That is the
|
|
439
|
-
* one behaviour change #14 forces on callers — see `services/service-keys.ts`.
|
|
440
|
-
*/
|
|
441
|
-
export declare function signServiceToken(opts: ServiceTokenOptions): string;
|
|
442
|
-
/** Convenience: returns a `Bearer <token>` header value for fetch/axios calls. */
|
|
443
|
-
export declare function getServiceAuthHeader(opts: ServiceTokenOptions): string;
|
|
444
|
-
/** True when `req.user` is a service principal (`principalType: 'service'`, minted by `signServiceToken`). */
|
|
445
|
-
export declare function isServicePrincipal(req: Request): boolean;
|
|
446
|
-
/**
|
|
447
|
-
* True when `req.user` is an ORG SERVICE ACCOUNT (`principalType:
|
|
448
|
-
* 'service_account'`) — a non-human principal owned by one org, authenticating
|
|
449
|
-
* with a `pb_sa_…` key exchanged at platform.
|
|
450
|
-
*
|
|
451
|
-
* A service account is NOT an internal service principal: it holds org Roles and
|
|
452
|
-
* is subject to every permission gate a member is. What it can never do is
|
|
453
|
-
* satisfy a HUMAN-presence requirement — step-up (and any later assurance gate)
|
|
454
|
-
* refuses it outright, because there is no person behind it to re-verify.
|
|
455
|
-
*/
|
|
456
|
-
export declare function isServiceAccountPrincipal(req: Request): boolean;
|
|
457
|
-
/** True when `req.user` is the named internal service (e.g. `'billing'`). */
|
|
458
|
-
export declare function isServicePrincipalNamed(req: Request, serviceName: string): boolean;
|
|
459
|
-
/**
|
|
460
|
-
* PRE-auth check: cryptographically verify the request carries a valid, signed
|
|
461
|
-
* SERVICE token (`principalType: 'service'`). Unlike {@link isServicePrincipal}
|
|
462
|
-
* (which reads the already-populated `req.user`), this verifies the bearer token
|
|
463
|
-
* itself, so it is safe to call BEFORE `requireAuth` runs — e.g. the global rate
|
|
464
|
-
* limiter's `skip`, which must not trust the spoofable `x-internal-service`
|
|
465
|
-
* header. Mirrors `requireAuth`'s SERVICE chain exactly (the signer's own key,
|
|
466
|
-
* resolved by `kid`, with the `sub`/signer agreement check and the optional
|
|
467
|
-
* issuer/audience) and returns `false` on any missing/invalid/non-service token
|
|
468
|
-
* — including any platform-signed USER token, whose `kid` is not in the service
|
|
469
|
-
* bundle at all. Stays SYNCHRONOUS, which is why the per-service public keys are
|
|
470
|
-
* distributed as a mounted bundle rather than fetched over HTTP.
|
|
471
|
-
*/
|
|
472
|
-
export declare function verifyServicePrincipal(req: Request): boolean;
|