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