@pipeline-builder/api-core 4.1.3 → 4.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +3 -3
  2. package/lib/errors/app-errors.d.ts +0 -4
  3. package/lib/errors/app-errors.js +1 -8
  4. package/lib/helpers/restore-helpers.d.ts +3 -3
  5. package/lib/helpers/restore-helpers.js +4 -4
  6. package/lib/helpers/sse-helpers.d.ts +8 -1
  7. package/lib/helpers/sse-helpers.js +11 -3
  8. package/lib/middleware/auth.d.ts +174 -43
  9. package/lib/middleware/auth.js +384 -179
  10. package/lib/middleware/index.d.ts +1 -0
  11. package/lib/middleware/index.js +2 -1
  12. package/lib/middleware/route-table.d.ts +104 -0
  13. package/lib/middleware/route-table.js +230 -0
  14. package/lib/middleware/step-up.d.ts +48 -16
  15. package/lib/middleware/step-up.js +74 -36
  16. package/lib/services/api-key-exchange.d.ts +25 -0
  17. package/lib/services/api-key-exchange.js +219 -0
  18. package/lib/services/cache-service.d.ts +11 -26
  19. package/lib/services/cache-service.js +20 -74
  20. package/lib/services/compliance-event-subscriber.d.ts +5 -4
  21. package/lib/services/compliance-event-subscriber.js +8 -6
  22. package/lib/services/durable-event-bus.d.ts +22 -39
  23. package/lib/services/durable-event-bus.js +95 -43
  24. package/lib/services/env-redis.d.ts +27 -0
  25. package/lib/services/env-redis.js +63 -1
  26. package/lib/services/http-client.d.ts +10 -15
  27. package/lib/services/http-client.js +32 -27
  28. package/lib/services/index.d.ts +3 -0
  29. package/lib/services/index.js +4 -1
  30. package/lib/services/jwks-cache.d.ts +102 -0
  31. package/lib/services/jwks-cache.js +222 -0
  32. package/lib/services/quota.js +12 -4
  33. package/lib/services/remote-audit-client.d.ts +1 -1
  34. package/lib/services/remote-audit-client.js +32 -1
  35. package/lib/services/scheduler.d.ts +2 -3
  36. package/lib/services/scheduler.js +2 -2
  37. package/lib/services/service-boot.d.ts +4 -2
  38. package/lib/services/service-boot.js +7 -3
  39. package/lib/services/service-keys.d.ts +83 -0
  40. package/lib/services/service-keys.js +354 -0
  41. package/lib/services/sse-ticket-store.d.ts +55 -22
  42. package/lib/services/sse-ticket-store.js +122 -79
  43. package/lib/services/token-revocation.js +30 -18
  44. package/lib/testing/route-coverage.d.ts +64 -0
  45. package/lib/testing/route-coverage.js +183 -0
  46. package/lib/testing/service-tokens.d.ts +66 -0
  47. package/lib/testing/service-tokens.js +137 -0
  48. package/lib/testing/tier-mock.d.ts +2 -2
  49. package/lib/testing/tier-mock.js +3 -3
  50. package/lib/testing/user-tokens.d.ts +75 -0
  51. package/lib/testing/user-tokens.js +115 -0
  52. package/lib/types/catalog-metadata.d.ts +0 -2
  53. package/lib/types/catalog-metadata.js +1 -3
  54. package/lib/types/common.d.ts +103 -4
  55. package/lib/types/common.js +11 -1
  56. package/lib/types/error-codes.d.ts +15 -0
  57. package/lib/types/error-codes.js +22 -1
  58. package/lib/types/permissions.d.ts +35 -1
  59. package/lib/types/permissions.js +70 -2
  60. package/lib/utils/api-key.d.ts +34 -0
  61. package/lib/utils/api-key.js +75 -0
  62. package/lib/utils/env.d.ts +0 -2
  63. package/lib/utils/env.js +1 -6
  64. package/lib/utils/index.d.ts +3 -0
  65. package/lib/utils/index.js +4 -1
  66. package/lib/utils/jwk.d.ts +98 -0
  67. package/lib/utils/jwk.js +139 -0
  68. package/lib/utils/logger.d.ts +4 -1
  69. package/lib/utils/logger.js +1 -22
  70. package/lib/utils/secret-encryption.d.ts +7 -2
  71. package/lib/utils/secret-encryption.js +47 -4
  72. package/lib/utils/secret-rotation.d.ts +17 -0
  73. package/lib/utils/secret-rotation.js +54 -0
  74. package/lib/validation/common-schemas.js +5 -4
  75. package/package.json +20 -1
@@ -0,0 +1,354 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Per-service signing keys for INTERNAL service-to-service tokens (#14).
5
+ *
6
+ * Before this, every service held one shared `JWT_SECRET`, so any service could
7
+ * mint a token naming any OTHER service — "billing said so" was unfalsifiable,
8
+ * and a single compromised workload could speak for the whole fleet. Now each
9
+ * service signs with its OWN ES256 (EC P-256) key and consumers verify against
10
+ * the public halves. The `kid` is bound to the signer's NAME, so a token whose
11
+ * `sub` says `service:billing` but was signed with compliance's key is a
12
+ * forgery, not a valid token with a confusing subject.
13
+ *
14
+ * ## Why public keys are distributed by CONFIG, not by per-service JWKS endpoints
15
+ *
16
+ * The user chain (#5) fetches platform's JWKS over HTTP because there is exactly
17
+ * one signer and it is the one service that must be up for anybody to sign in.
18
+ * The service chain is the opposite shape, and three properties decided it:
19
+ *
20
+ * 1. **`verifyServicePrincipal` must stay synchronous.** The global rate
21
+ * limiter's `skip` and app-factory's `/warmup` guard verify a service token
22
+ * BEFORE `requireAuth` runs, in synchronous Express callbacks. An HTTP JWKS
23
+ * fetch would force those async — a change that reaches into every
24
+ * service's limiter wiring.
25
+ * 2. **No availability coupling.** With per-service JWKS endpoints, verifying a
26
+ * token minted by `billing` needs `billing` to be reachable — so billing
27
+ * being down would stop platform from accepting billing's in-flight calls,
28
+ * turning one outage into several. A mounted file has no such edge.
29
+ * 3. **No N×N address config.** 10 services would each need every peer's
30
+ * host/port (and compose, which has no mesh, would need them too).
31
+ *
32
+ * So the deploy generates one keypair per service, mounts each private key ONLY
33
+ * into its own service, and mounts ONE public bundle (read-only) everywhere:
34
+ *
35
+ * `SERVICE_SIGNING_KEY_FILE` this service's EC P-256 private key (PKCS#8 PEM)
36
+ * `SERVICE_KEY_BUNDLE_FILE` `{ "services": { "<name>": { "keys": [<jwk>…] } } }`
37
+ *
38
+ * Rotation is by `kid`, in the same shape as the user chain: add the new public
39
+ * key to the bundle (the service now publishes two), roll the bundle out, swap
40
+ * that service's private key, then drop the retired public key. The bundle is
41
+ * re-read on an interval AND on an mtime change, so a rotation needs no restart.
42
+ * `deploy/bin/service-signing-keys.sh` generates and rotates both halves.
43
+ *
44
+ * ## Ephemeral mode
45
+ *
46
+ * With no `SERVICE_SIGNING_KEY_FILE` configured, a keypair is generated
47
+ * IN-PROCESS per requested service name and registered in the in-memory bundle.
48
+ * That keeps single-process suites and local one-off scripts working with no key
49
+ * material, and it can never widen trust: the only verifier that accepts such a
50
+ * token is the process that minted it. It is logged once at warn level, and
51
+ * counted, because in a real deployment it means this service's peers will
52
+ * reject everything it sends.
53
+ */
54
+ import crypto, {} from 'crypto';
55
+ import { readFileSync, statSync } from 'fs';
56
+ import jwt from 'jsonwebtoken';
57
+ import { publicJwkFrom, publicKeyFromJwk } from '../utils/jwk.js';
58
+ import { createLogger } from '../utils/logger.js';
59
+ import { emitCounter } from '../utils/metric-emitter.js';
60
+ const logger = createLogger('service-keys');
61
+ /** The ONLY algorithm an internal service token may be signed with. */
62
+ export const SERVICE_TOKEN_ALGORITHM = 'ES256';
63
+ /** Subject prefix naming the calling service on a service token (`service:<name>`). */
64
+ export const SERVICE_SUBJECT_PREFIX = 'service:';
65
+ /** How long a loaded bundle is served before it is re-read (rotation pickup). */
66
+ const BUNDLE_REFRESH_INTERVAL_MS = 300_000; // 5 minutes
67
+ /** The bundle could not be loaded or does not know the key a token names. */
68
+ export class ServiceKeyError extends Error {
69
+ constructor(message) {
70
+ super(message);
71
+ this.name = 'ServiceKeyError';
72
+ }
73
+ }
74
+ /**
75
+ * This process's service identity — the `<name>` in `service:<name>` and the
76
+ * name its key is published under. `SERVICE_NAME` is already set per service in
77
+ * every deploy target (compose, minikube, ec2, eks), and is the same value the
78
+ * audit and metric labels use, so there is one source of truth for "who am I".
79
+ */
80
+ export function serviceIdentity() {
81
+ return process.env.SERVICE_NAME || 'api';
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // Local signing key
85
+ // ---------------------------------------------------------------------------
86
+ let localKey;
87
+ let ephemeralKeys;
88
+ let ephemeralWarned = false;
89
+ function loadConfiguredSigningKey(file) {
90
+ const serviceName = serviceIdentity();
91
+ let privateKey;
92
+ try {
93
+ privateKey = crypto.createPrivateKey(readFileSync(file, 'utf8'));
94
+ }
95
+ catch (error) {
96
+ throw new ServiceKeyError(`Could not read the service signing key at ${file}: ${error instanceof Error ? error.message : String(error)}`);
97
+ }
98
+ // publicJwkFrom rejects anything but an EC P-256 key, so a service configured
99
+ // with (say) an RSA key fails HERE rather than minting tokens nobody verifies.
100
+ const publicJwk = publicJwkFrom(crypto.createPublicKey(privateKey));
101
+ return { serviceName, kid: publicJwk.kid, privateKey, publicJwk };
102
+ }
103
+ /**
104
+ * The in-process key for `serviceName` in ephemeral mode, generated on first
105
+ * use and registered in the in-memory bundle so this process can verify its own
106
+ * tokens. Keyed by NAME (not just one key) so a single-process suite can mint
107
+ * tokens for several services, as the real fleet does across processes.
108
+ */
109
+ function ephemeralSigningKey(serviceName) {
110
+ ephemeralKeys ??= new Map();
111
+ const existing = ephemeralKeys.get(serviceName);
112
+ if (existing)
113
+ return existing;
114
+ if (!ephemeralWarned) {
115
+ ephemeralWarned = true;
116
+ emitCounter('service_signing_ephemeral_key_total', { service: serviceIdentity() });
117
+ logger.warn('SERVICE_SIGNING_KEY_FILE is not set — minting internal service tokens with an EPHEMERAL key. Only this process will accept them; every peer will reject them. Set SERVICE_SIGNING_KEY_FILE + SERVICE_KEY_BUNDLE_FILE (deploy/bin/service-signing-keys.sh).');
118
+ }
119
+ const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
120
+ const publicJwk = publicJwkFrom(publicKey);
121
+ const generated = { serviceName, kid: publicJwk.kid, privateKey, publicJwk };
122
+ ephemeralKeys.set(serviceName, generated);
123
+ return generated;
124
+ }
125
+ /** Whether this process signs with a mounted key (`configured`) or an in-process one. */
126
+ export function serviceKeyMode() {
127
+ return process.env.SERVICE_SIGNING_KEY_FILE ? 'configured' : 'ephemeral';
128
+ }
129
+ /**
130
+ * The key `serviceName` must be signed with.
131
+ *
132
+ * In `configured` mode there is exactly ONE key — this service's — so a request
133
+ * to sign as some OTHER service is a bug and throws. That matters: before #14 a
134
+ * service could legitimately mint `service:platform` with the shared secret
135
+ * (api/plugin did, for its registry pushes), and under per-service keys such a
136
+ * token would be silently unverifiable everywhere. Failing at the mint turns
137
+ * that into an immediate, obvious error instead of a runtime 401.
138
+ */
139
+ function signingKeyFor(serviceName) {
140
+ const file = process.env.SERVICE_SIGNING_KEY_FILE;
141
+ if (!file)
142
+ return ephemeralSigningKey(serviceName);
143
+ if (!localKey || localKey.serviceName !== serviceIdentity()) {
144
+ localKey = loadConfiguredSigningKey(file);
145
+ }
146
+ if (serviceName !== localKey.serviceName) {
147
+ throw new ServiceKeyError(`This process is '${localKey.serviceName}' and holds only its own signing key, so it cannot mint a token for '${serviceName}'. `
148
+ + 'Name the calling service correctly, or give the caller its own key.');
149
+ }
150
+ return localKey;
151
+ }
152
+ let bundle;
153
+ function parseBundle(raw, file) {
154
+ let document;
155
+ try {
156
+ document = JSON.parse(raw);
157
+ }
158
+ catch (error) {
159
+ throw new ServiceKeyError(`${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
160
+ }
161
+ const services = document?.services;
162
+ if (!services || typeof services !== 'object') {
163
+ throw new ServiceKeyError(`${file} has no "services" object`);
164
+ }
165
+ const byKid = new Map();
166
+ for (const [serviceName, entry] of Object.entries(services)) {
167
+ for (const jwk of entry?.keys ?? []) {
168
+ if (!jwk?.kid)
169
+ continue;
170
+ try {
171
+ byKid.set(jwk.kid, { serviceName, key: publicKeyFromJwk(jwk) });
172
+ }
173
+ catch (error) {
174
+ // One unusable entry must not discard the rest of the bundle — the same
175
+ // rule the JWKS cache applies to platform's published set.
176
+ logger.warn('Skipping unusable service key', { serviceName, kid: jwk.kid, error: error instanceof Error ? error.message : String(error) });
177
+ }
178
+ }
179
+ }
180
+ if (byKid.size === 0)
181
+ throw new ServiceKeyError(`${file} contained no usable service keys`);
182
+ return byKid;
183
+ }
184
+ /**
185
+ * The `kid` → service map, re-read when the refresh interval has passed or the
186
+ * file's mtime changed (so a rotation is picked up without a restart).
187
+ *
188
+ * In ephemeral mode the "bundle" is whatever this process generated for itself.
189
+ * Never throws for a missing file in that mode — it just holds those keys.
190
+ */
191
+ function verificationKeys() {
192
+ const file = process.env.SERVICE_KEY_BUNDLE_FILE;
193
+ if (!file) {
194
+ const local = new Map();
195
+ for (const key of ephemeralKeys?.values() ?? []) {
196
+ local.set(key.kid, { serviceName: key.serviceName, key: crypto.createPublicKey(key.privateKey) });
197
+ }
198
+ return local;
199
+ }
200
+ const now = Date.now();
201
+ let mtimeMs = 0;
202
+ try {
203
+ mtimeMs = statSync(file).mtimeMs;
204
+ }
205
+ catch {
206
+ // Unreadable now: keep serving a bundle already in hand (public keys don't
207
+ // expire, and refusing every internal call because a mount blipped would be
208
+ // a self-inflicted outage). With nothing in hand, fail closed below.
209
+ if (bundle)
210
+ return bundle.byKid;
211
+ }
212
+ if (bundle && bundle.mtimeMs === mtimeMs && now - bundle.loadedAt < BUNDLE_REFRESH_INTERVAL_MS) {
213
+ return bundle.byKid;
214
+ }
215
+ try {
216
+ const byKid = parseBundle(readFileSync(file, 'utf8'), file);
217
+ bundle = { byKid, loadedAt: now, mtimeMs };
218
+ return byKid;
219
+ }
220
+ catch (error) {
221
+ if (bundle) {
222
+ logger.warn('Service key bundle reload failed; continuing on the loaded copy', { file, error: error instanceof Error ? error.message : String(error) });
223
+ return bundle.byKid;
224
+ }
225
+ throw error instanceof ServiceKeyError ? error : new ServiceKeyError(String(error));
226
+ }
227
+ }
228
+ /**
229
+ * Whether `kid` belongs to an internal SERVICE key. This is how a bearer token
230
+ * is routed between the two ES256 chains: both user and service tokens are
231
+ * ES256 with a `kid`, and `kid` is an RFC 7638 thumbprint, so ownership of the
232
+ * id — not a claim inside the token — decides which key set may verify it.
233
+ * Returns `false` (never throws) when no bundle can be read; the token then
234
+ * takes the user chain and is rejected there.
235
+ */
236
+ export function isServiceKid(kid) {
237
+ try {
238
+ return verificationKeys().has(kid);
239
+ }
240
+ catch {
241
+ return false;
242
+ }
243
+ }
244
+ /** Every service name the bundle publishes a key for. Diagnostics and tests. */
245
+ export function knownServiceNames() {
246
+ try {
247
+ return [...new Set([...verificationKeys().values()].map((v) => v.serviceName))].sort();
248
+ }
249
+ catch {
250
+ return [];
251
+ }
252
+ }
253
+ // ---------------------------------------------------------------------------
254
+ // Sign / verify
255
+ // ---------------------------------------------------------------------------
256
+ /** base64url of a JSON value, the JWS way (no padding, URL alphabet). */
257
+ function b64uJson(value) {
258
+ return Buffer.from(JSON.stringify(value), 'utf-8').toString('base64url');
259
+ }
260
+ /**
261
+ * Sign `claims` as this service's internal token: ES256, `kid` = the signer's
262
+ * thumbprint, `sub` = `service:<serviceName>`. `iat`/`exp` and the optional
263
+ * issuer/audience are stamped here so there is one place that decides what a
264
+ * service token says.
265
+ *
266
+ * Hand-rolled (rather than `jwt.sign`) only so the header carries the `kid` in
267
+ * the same shape platform's signer produces — `crypto.sign` is synchronous, so
268
+ * unlike the KMS path this costs nothing.
269
+ */
270
+ export function signServiceJwt(claims, options) {
271
+ const key = signingKeyFor(options.serviceName);
272
+ const now = Math.floor(Date.now() / 1000);
273
+ const body = {
274
+ ...claims,
275
+ sub: `${SERVICE_SUBJECT_PREFIX}${options.serviceName}`,
276
+ iat: now,
277
+ exp: now + options.expiresInSeconds,
278
+ ...(options.issuer ? { iss: options.issuer } : {}),
279
+ ...(options.audience ? { aud: options.audience } : {}),
280
+ };
281
+ const signingInput = `${b64uJson({ alg: SERVICE_TOKEN_ALGORITHM, typ: 'JWT', kid: key.kid })}.${b64uJson(body)}`;
282
+ const signature = crypto.sign('sha256', Buffer.from(signingInput, 'utf-8'), {
283
+ key: key.privateKey,
284
+ dsaEncoding: 'ieee-p1363',
285
+ });
286
+ return `${signingInput}.${signature.toString('base64url')}`;
287
+ }
288
+ /**
289
+ * Verify an internal service token, SYNCHRONOUSLY, and return its claims.
290
+ *
291
+ * The security property this function exists for: the `kid` selects the key
292
+ * AND names the signer, and the token's `sub` must agree. So `billing` cannot
293
+ * be impersonated by any other service — not even one that holds a valid key of
294
+ * its own — which is what the shared secret could never guarantee.
295
+ *
296
+ * @throws {jwt.JsonWebTokenError} unknown `kid`, wrong algorithm, bad
297
+ * signature, expired, or a `sub` that disagrees with the signing key.
298
+ * @throws {ServiceKeyError} no verification bundle could be read at all — the
299
+ * token's validity is UNKNOWN and the caller must not treat it as valid.
300
+ */
301
+ export function verifyServiceJwt(token, options) {
302
+ const entry = verificationKeys().get(options.kid);
303
+ if (!entry) {
304
+ emitCounter('service_token_verify_total', { result: 'unknown_kid' });
305
+ throw new jwt.JsonWebTokenError(`No published service key for kid ${options.kid}`);
306
+ }
307
+ let claims;
308
+ try {
309
+ claims = jwt.verify(token, entry.key, {
310
+ algorithms: [SERVICE_TOKEN_ALGORITHM],
311
+ ...(options.issuer ? { issuer: options.issuer } : {}),
312
+ ...(options.audience ? { audience: options.audience } : {}),
313
+ });
314
+ }
315
+ catch (error) {
316
+ emitCounter('service_token_verify_total', { result: 'invalid' });
317
+ throw error;
318
+ }
319
+ if (claims.sub !== `${SERVICE_SUBJECT_PREFIX}${entry.serviceName}`) {
320
+ // A valid signature by the WRONG service: the key belongs to `entry.serviceName`
321
+ // but the token speaks for someone else. This is the cross-service forgery
322
+ // the shared secret made undetectable.
323
+ emitCounter('service_token_verify_total', { result: 'subject_mismatch', service: entry.serviceName });
324
+ throw new jwt.JsonWebTokenError(`Service token subject ${String(claims.sub)} was signed by ${entry.serviceName}`);
325
+ }
326
+ emitCounter('service_token_verify_total', { result: 'ok', service: entry.serviceName });
327
+ return claims;
328
+ }
329
+ /**
330
+ * True while this service still publishes more than one verification key — i.e.
331
+ * a signing-key rotation is open and the retiring key is still trusted. Feeds
332
+ * the `secret_rotation_previous_set{secret="SERVICE_SIGNING_KEY"}` gauge.
333
+ */
334
+ export function isRetiringServiceKeyPublished() {
335
+ try {
336
+ const me = serviceIdentity();
337
+ return [...verificationKeys().values()].filter((v) => v.serviceName === me).length > 1;
338
+ }
339
+ catch {
340
+ return false;
341
+ }
342
+ }
343
+ /**
344
+ * Test seam: drop the loaded signing key, the bundle and any ephemeral keys so
345
+ * the next sign/verify re-reads the environment. Don't call from production code
346
+ * — rotation is picked up by the refresh interval and the mtime check.
347
+ */
348
+ export function _resetServiceKeysForTests() {
349
+ localKey = undefined;
350
+ bundle = undefined;
351
+ ephemeralKeys = undefined;
352
+ ephemeralWarned = false;
353
+ }
354
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS1rZXlzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpY2VzL3NlcnZpY2Uta2V5cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtERztBQUVILE9BQU8sTUFBTSxFQUFFLEVBQWtCLE1BQU0sUUFBUSxDQUFDO0FBQ2hELE9BQU8sRUFBRSxZQUFZLEVBQUUsUUFBUSxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQzVDLE9BQU8sR0FBRyxNQUFNLGNBQWMsQ0FBQztBQUMvQixPQUFPLEVBQUUsYUFBYSxFQUFFLGdCQUFnQixFQUFrQixNQUFNLGlCQUFpQixDQUFDO0FBQ2xGLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFekQsTUFBTSxNQUFNLEdBQUcsWUFBWSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0FBRTVDLHVFQUF1RTtBQUN2RSxNQUFNLENBQUMsTUFBTSx1QkFBdUIsR0FBRyxPQUFPLENBQUM7QUFFL0MsdUZBQXVGO0FBQ3ZGLE1BQU0sQ0FBQyxNQUFNLHNCQUFzQixHQUFHLFVBQVUsQ0FBQztBQUVqRCxpRkFBaUY7QUFDakYsTUFBTSwwQkFBMEIsR0FBRyxPQUFPLENBQUMsQ0FBQyxZQUFZO0FBb0J4RCw2RUFBNkU7QUFDN0UsTUFBTSxPQUFPLGVBQWdCLFNBQVEsS0FBSztJQUN4QyxZQUFZLE9BQWU7UUFDekIsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ2YsSUFBSSxDQUFDLElBQUksR0FBRyxpQkFBaUIsQ0FBQztJQUNoQyxDQUFDO0NBQ0Y7QUFFRDs7Ozs7R0FLRztBQUNILE1BQU0sVUFBVSxlQUFlO0lBQzdCLE9BQU8sT0FBTyxDQUFDLEdBQUcsQ0FB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V0FBVyxFQUFFLENBQUMsQ0FBQztRQUN0RyxNQUFNLElBQUksR0FBRyxDQUFDLGlCQUFpQixDQUFDLHlCQUF5QixNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsS0FBSyxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7SUFDcEgsQ0FBQztJQUNELFdBQVcsQ0FBQyw0QkFBNEIsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLEtBQUssQ0FBQyxXQUFXLEVBQUUsQ0FBQyxDQUFDO0lBQ3hGLE9BQU8sTUFBTSxDQUFDO0FBQ2hCLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLDZCQUE2QjtJQUMzQyxJQUFJLENBQUM7UUFDSCxNQUFNLEVBQUUsR0FBRyxlQUFlLEVBQUUsQ0FBQztRQUM3QixPQUFPLENBQUMsR0FBRyxnQkFBZ0IsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsV0FBVyxLQUFLLEVBQUUsQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUM7SUFDekYsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztBQUNILENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLHlCQUF5QjtJQUN2QyxRQUFRLEdBQUcsU0FBUyxDQUFDO0lBQ3JCLE1BQU0sR0FBRyxTQUFTLENBQUM7SUFDbkIsYUFBYSxHQUFHLFNBQVMsQ0FBQztJQUMxQixlQUFlLEdBQUcsS0FBSyxDQUFDO0FBQzFCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBQZXItc2VydmljZSBzaWduaW5nIGtleXMgZm9yIElOVEVSTkFMIHNlcnZpY2UtdG8tc2VydmljZSB0b2tlbnMgKCMxNCkuXG4gKlxuICogQmVmb3JlIHRoaXMsIGV2ZXJ5IHNlcnZpY2UgaGVsZCBvbmUgc2hhcmVkIGBKV1RfU0VDUkVUYCwgc28gYW55IHNlcnZpY2UgY291bGRcbiAqIG1pbnQgYSB0b2tlbiBuYW1pbmcgYW55IE9USEVSIHNlcnZpY2Ug4oCUIFwiYmlsbGluZyBzYWlkIHNvXCIgd2FzIHVuZmFsc2lmaWFibGUsXG4gKiBhbmQgYSBzaW5nbGUgY29tcHJvbWlzZWQgd29ya2xvYWQgY291bGQgc3BlYWsgZm9yIHRoZSB3aG9sZSBmbGVldC4gTm93IGVhY2hcbiAqIHNlcnZpY2Ugc2lnbnMgd2l0aCBpdHMgT1dOIEVTMjU2IChFQyBQLTI1Nikga2V5IGFuZCBjb25zdW1lcnMgdmVyaWZ5IGFnYWluc3RcbiAqIHRoZSBwdWJsaWMgaGFsdmVzLiBUaGUgYGtpZGAgaXMgYm91bmQgdG8gdGhlIHNpZ25lcidzIE5BTUUsIHNvIGEgdG9rZW4gd2hvc2VcbiAqIGBzdWJgIHNheXMgYHNlcnZpY2U6YmlsbGluZ2AgYnV0IHdhcyBzaWduZWQgd2l0aCBjb21wbGlhbmNlJ3Mga2V5IGlzIGFcbiAqIGZvcmdlcnksIG5vdCBhIHZhbGlkIHRva2VuIHdpdGggYSBjb25mdXNpbmcgc3ViamVjdC5cbiAqXG4gKiAjIyBXaHkgcHVibGljIGtleXMgYXJlIGRpc3RyaWJ1dGVkIGJ5IENPTkZJRywgbm90IGJ5IHBlci1zZXJ2aWNlIEpXS1MgZW5kcG9pbnRzXG4gKlxuICogVGhlIHVzZXIgY2hhaW4gKCM1KSBmZXRjaGVzIHBsYXRmb3JtJ3MgSldLUyBvdmVyIEhUVFAgYmVjYXVzZSB0aGVyZSBpcyBleGFjdGx5XG4gKiBvbmUgc2lnbmVyIGFuZCBpdCBpcyB0aGUgb25lIHNlcnZpY2UgdGhhdCBtdXN0IGJlIHVwIGZvciBhbnlib2R5IHRvIHNpZ24gaW4uXG4gKiBUaGUgc2VydmljZSBjaGFpbiBpcyB0aGUgb3Bwb3NpdGUgc2hhcGUsIGFuZCB0aHJlZSBwcm9wZXJ0aWVzIGRlY2lkZWQgaXQ6XG4gKlxuICogIDEuICoqYHZlcmlmeVNlcnZpY2VQcmluY2lwYWxgIG11c3Qgc3RheSBzeW5jaHJvbm91cy4qKiBUaGUgZ2xvYmFsIHJhdGVcbiAqICAgICBsaW1pdGVyJ3MgYHNraXBgIGFuZCBhcHAtZmFjdG9yeSdzIGAvd2FybXVwYCBndWFyZCB2ZXJpZnkgYSBzZXJ2aWNlIHRva2VuXG4gKiAgICAgQkVGT1JFIGByZXF1aXJlQXV0aGAgcnVucywgaW4gc3luY2hyb25vdXMgRXhwcmVzcyBjYWxsYmFja3MuIEFuIEhUVFAgSldLU1xuICogICAgIGZldGNoIHdvdWxkIGZvcmNlIHRob3NlIGFzeW5jIOKAlCBhIGNoYW5nZSB0aGF0IHJlYWNoZXMgaW50byBldmVyeVxuICogICAgIHNlcnZpY2UncyBsaW1pdGVyIHdpcmluZy5cbiAqICAyLiAqKk5vIGF2YWlsYWJpbGl0eSBjb3VwbGluZy4qKiBXaXRoIHBlci1zZXJ2aWNlIEpXS1MgZW5kcG9pbnRzLCB2ZXJpZnlpbmcgYVxuICogICAgIHRva2VuIG1pbnRlZCBieSBgYmlsbGluZ2AgbmVlZHMgYGJpbGxpbmdgIHRvIGJlIHJlYWNoYWJsZSDigJQgc28gYmlsbGluZ1xuICogICAgIGJlaW5nIGRvd24gd291bGQgc3RvcCBwbGF0Zm9ybSBmcm9tIGFjY2VwdGluZyBiaWxsaW5nJ3MgaW4tZmxpZ2h0IGNhbGxzLFxuICogICAgIHR1cm5pbmcgb25lIG91dGFnZSBpbnRvIHNldmVyYWwuIEEgbW91bnRlZCBmaWxlIGhhcyBubyBzdWNoIGVkZ2UuXG4gKiAgMy4gKipObyBOw5dOIGFkZHJlc3MgY29uZmlnLioqIDEwIHNlcnZpY2VzIHdvdWxkIGVhY2ggbmVlZCBldmVyeSBwZWVyJ3NcbiAqICAgICBob3N0L3BvcnQgKGFuZCBjb21wb3NlLCB3aGljaCBoYXMgbm8gbWVzaCwgd291bGQgbmVlZCB0aGVtIHRvbykuXG4gKlxuICogU28gdGhlIGRlcGxveSBnZW5lcmF0ZXMgb25lIGtleXBhaXIgcGVyIHNlcnZpY2UsIG1vdW50cyBlYWNoIHByaXZhdGUga2V5IE9OTFlcbiAqIGludG8gaXRzIG93biBzZXJ2aWNlLCBhbmQgbW91bnRzIE9ORSBwdWJsaWMgYnVuZGxlIChyZWFkLW9ubHkpIGV2ZXJ5d2hlcmU6XG4gKlxuICogICBgU0VSVklDRV9TSUdOSU5HX0tFWV9GSUxFYCAgdGhpcyBzZXJ2aWNlJ3MgRUMgUC0yNTYgcHJpdmF0ZSBrZXkgKFBLQ1MjOCBQRU0pXG4gKiAgIGBTRVJWSUNFX0tFWV9CVU5ETEVfRklMRWAgICBgeyBcInNlcnZpY2VzXCI6IHsgXCI8bmFtZT5cIjogeyBcImtleXNcIjogWzxqd2s+4oCmXSB9IH0gfWBcbiAqXG4gKiBSb3RhdGlvbiBpcyBieSBga2lkYCwgaW4gdGhlIHNhbWUgc2hhcGUgYXMgdGhlIHVzZXIgY2hhaW46IGFkZCB0aGUgbmV3IHB1YmxpY1xuICoga2V5IHRvIHRoZSBidW5kbGUgKHRoZSBzZXJ2aWNlIG5vdyBwdWJsaXNoZXMgdHdvKSwgcm9sbCB0aGUgYnVuZGxlIG91dCwgc3dhcFxuICogdGhhdCBzZXJ2aWNlJ3MgcHJpdmF0ZSBrZXksIHRoZW4gZHJvcCB0aGUgcmV0aXJlZCBwdWJsaWMga2V5LiBUaGUgYnVuZGxlIGlzXG4gKiByZS1yZWFkIG9uIGFuIGludGVydmFsIEFORCBvbiBhbiBtdGltZSBjaGFuZ2UsIHNvIGEgcm90YXRpb24gbmVlZHMgbm8gcmVzdGFydC5cbiAqIGBkZXBsb3kvYmluL3NlcnZpY2Utc2lnbmluZy1rZXlzLnNoYCBnZW5lcmF0ZXMgYW5kIHJvdGF0ZXMgYm90aCBoYWx2ZXMuXG4gKlxuICogIyMgRXBoZW1lcmFsIG1vZGVcbiAqXG4gKiBXaXRoIG5vIGBTRVJWSUNFX1NJR05JTkdfS0VZX0ZJTEVgIGNvbmZpZ3VyZWQsIGEga2V5cGFpciBpcyBnZW5lcmF0ZWRcbiAqIElOLVBST0NFU1MgcGVyIHJlcXVlc3RlZCBzZXJ2aWNlIG5hbWUgYW5kIHJlZ2lzdGVyZWQgaW4gdGhlIGluLW1lbW9yeSBidW5kbGUuXG4gKiBUaGF0IGtlZXBzIHNpbmdsZS1wcm9jZXNzIHN1aXRlcyBhbmQgbG9jYWwgb25lLW9mZiBzY3JpcHRzIHdvcmtpbmcgd2l0aCBubyBrZXlcbiAqIG1hdGVyaWFsLCBhbmQgaXQgY2FuIG5ldmVyIHdpZGVuIHRydXN0OiB0aGUgb25seSB2ZXJpZmllciB0aGF0IGFjY2VwdHMgc3VjaCBhXG4gKiB0b2tlbiBpcyB0aGUgcHJvY2VzcyB0aGF0IG1pbnRlZCBpdC4gSXQgaXMgbG9nZ2VkIG9uY2UgYXQgd2FybiBsZXZlbCwgYW5kXG4gKiBjb3VudGVkLCBiZWNhdXNlIGluIGEgcmVhbCBkZXBsb3ltZW50IGl0IG1lYW5zIHRoaXMgc2VydmljZSdzIHBlZXJzIHdpbGxcbiAqIHJlamVjdCBldmVyeXRoaW5nIGl0IHNlbmRzLlxuICovXG5cbmltcG9ydCBjcnlwdG8sIHsgdHlwZSBLZXlPYmplY3QgfSBmcm9tICdjcnlwdG8nO1xuaW1wb3J0IHsgcmVhZEZpbGVTeW5jLCBzdGF0U3luYyB9IGZyb20gJ2ZzJztcbmltcG9ydCBqd3QgZnJvbSAnanNvbndlYnRva2VuJztcbmltcG9ydCB7IHB1YmxpY0p3a0Zyb20sIHB1YmxpY0tleUZyb21Kd2ssIHR5cGUgUHVibGljSndrIH0gZnJvbSAnLi4vdXRpbHMvandrLmpzJztcbmltcG9ydCB7IGNyZWF0ZUxvZ2dlciB9IGZyb20gJy4uL3V0aWxzL2xvZ2dlci5qcyc7XG5pbXBvcnQgeyBlbWl0Q291bnRlciB9IGZyb20gJy4uL3V0aWxzL21ldHJpYy1lbWl0dGVyLmpzJztcblxuY29uc3QgbG9nZ2VyID0gY3JlYXRlTG9nZ2VyKCdzZXJ2aWNlLWtleXMnKTtcblxuLyoqIFRoZSBPTkxZIGFsZ29yaXRobSBhbiBpbnRlcm5hbCBzZXJ2aWNlIHRva2VuIG1heSBiZSBzaWduZWQgd2l0aC4gKi9cbmV4cG9ydCBjb25zdCBTRVJWSUNFX1RPS0VOX0FMR09SSVRITSA9ICdFUzI1Nic7XG5cbi8qKiBTdWJqZWN0IHByZWZpeCBuYW1pbmcgdGhlIGNhbGxpbmcgc2VydmljZSBvbiBhIHNlcnZpY2UgdG9rZW4gKGBzZXJ2aWNlOjxuYW1lPmApLiAqL1xuZXhwb3J0IGNvbnN0IFNFUlZJQ0VfU1VCSkVDVF9QUkVGSVggPSAnc2VydmljZTonO1xuXG4vKiogSG93IGxvbmcgYSBsb2FkZWQgYnVuZGxlIGlzIHNlcnZlZCBiZWZvcmUgaXQgaXMgcmUtcmVhZCAocm90YXRpb24gcGlja3VwKS4gKi9cbmNvbnN0IEJVTkRMRV9SRUZSRVNIX0lOVEVSVkFMX01TID0gMzAwXzAwMDsgLy8gNSBtaW51dGVzXG5cbi8qKiBPbmUgc2VydmljZSdzIHB1Ymxpc2hlZCB2ZXJpZmljYXRpb24ga2V5cywgYXMgdGhleSBhcHBlYXIgaW4gdGhlIGJ1bmRsZSBmaWxlLiAqL1xuZXhwb3J0IGludGVyZmFjZSBTZXJ2aWNlS2V5RW50cnkge1xuICBrZXlzOiBQdWJsaWNKd2tbXTtcbn1cblxuLyoqIFRoZSBkb2N1bWVudCBgU0VSVklDRV9LRVlfQlVORExFX0ZJTEVgIGhvbGRzOiBldmVyeSBzZXJ2aWNlJ3MgcHVibGljIGtleXMuICovXG5leHBvcnQgaW50ZXJmYWNlIFNlcnZpY2VLZXlCdW5kbGUge1xuICBzZXJ2aWNlczogUmVjb3JkPHN0cmluZywgU2VydmljZUtleUVudHJ5Pjtcbn1cblxuLyoqIEEgc2lnbmluZyBrZXkgdGhpcyBwcm9jZXNzIGhvbGRzLCBpbiB0aGUgZm9ybXMgc2lnbmluZyBuZWVkcy4gKi9cbmludGVyZmFjZSBMb2NhbFNpZ25pbmdLZXkge1xuICBzZXJ2aWNlTmFtZTogc3RyaW5nO1xuICBraWQ6IHN0cmluZztcbiAgcHJpdmF0ZUtleTogS2V5T2JqZWN0O1xuICBwdWJsaWNKd2s6IFB1YmxpY0p3aztcbn1cblxuLyoqIFRoZSBidW5kbGUgY291bGQgbm90IGJlIGxvYWRlZCBvciBkb2VzIG5vdCBrbm93IHRoZSBrZXkgYSB0b2tlbiBuYW1lcy4gKi9cbmV4cG9ydCBjbGFzcyBTZXJ2aWNlS2V5RXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKG1lc3NhZ2UpO1xuICAgIHRoaXMubmFtZSA9ICdTZXJ2aWNlS2V5RXJyb3InO1xuICB9XG59XG5cbi8qKlxuICogVGhpcyBwcm9jZXNzJ3Mgc2VydmljZSBpZGVudGl0eSDigJQgdGhlIGA8bmFtZT5gIGluIGBzZXJ2aWNlOjxuYW1lPmAgYW5kIHRoZVxuICogbmFtZSBpdHMga2V5IGlzIHB1Ymxpc2hlZCB1bmRlci4gYFNFUlZJQ0VfTkFNRWAgaXMgYWxyZWFkeSBzZXQgcGVyIHNlcnZpY2UgaW5cbiAqIGV2ZXJ5IGRlcGxveSB0YXJnZXQgKGNvbXBvc2UsIG1pbmlrdWJlLCBlYzIsIGVrcyksIGFuZCBpcyB0aGUgc2FtZSB2YWx1ZSB0aGVcbiAqIGF1ZGl0IGFuZCBtZXRyaWMgbGFiZWxzIHVzZSwgc28gdGhlcmUgaXMgb25lIHNvdXJjZSBvZiB0cnV0aCBmb3IgXCJ3aG8gYW0gSVwiLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2VydmljZUlkZW50aXR5KCk6IHN0cmluZyB7XG4gIHJldHVybiBwcm9jZXNzLmVudi5TRVJWSUNFX05BTUUgfHwgJ2FwaSc7XG59XG5cbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gTG9jYWwgc2lnbmluZyBrZXlcbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuXG5sZXQgbG9jYWxLZXk6IExvY2FsU2lnbmluZ0tleSB8IHVuZGVmaW5lZDtcbmxldCBlcGhlbWVyYWxLZXlzOiBNYXA8c3RyaW5nLCBMb2NhbFNpZ25pbmdLZXk+IHwgdW5kZWZpbmVkO1xubGV0IGVwaGVtZXJhbFdhcm5lZCA9IGZhbHNlO1xuXG5mdW5jdGlvbiBsb2FkQ29uZmlndXJlZFNpZ25pbmdLZXkoZmlsZTogc3RyaW5nKTogTG9jYWxTaWduaW5nS2V5IHtcbiAgY29uc3Qgc2VydmljZU5hbWUgPSBzZXJ2aWNlSWRlbnRpdHkoKTtcbiAgbGV0IHByaXZhdGVLZXk6IEtleU9iamVjdDtcbiAgdHJ5IHtcbiAgICBwcml2YXRlS2V5ID0gY3J5cHRvLmNyZWF0ZVByaXZhdGVLZXkocmVhZEZpbGVTeW5jKGZpbGUsICd1dGY4JykpO1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIHRocm93IG5ldyBTZXJ2aWNlS2V5RXJyb3IoYENvdWxkIG5vdCByZWFkIHRoZSBzZXJ2aWNlIHNpZ25pbmcga2V5IGF0ICR7ZmlsZX06ICR7ZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpfWApO1xuICB9XG4gIC8vIHB1YmxpY0p3a0Zyb20gcmVqZWN0cyBhbnl0aGluZyBidXQgYW4gRUMgUC0yNTYga2V5LCBzbyBhIHNlcnZpY2UgY29uZmlndXJlZFxuICAvLyB3aXRoIChzYXkpIGFuIFJTQSBrZXkgZmFpbHMgSEVSRSByYXRoZXIgdGhhbiBtaW50aW5nIHRva2VucyBub2JvZHkgdmVyaWZpZXMuXG4gIGNvbnN0IHB1YmxpY0p3ayA9IHB1YmxpY0p3a0Zyb20oY3J5cHRvLmNyZWF0ZVB1YmxpY0tleShwcml2YXRlS2V5KSk7XG4gIHJldHVybiB7IHNlcnZpY2VOYW1lLCBraWQ6IHB1YmxpY0p3ay5raWQsIHByaXZhdGVLZXksIHB1YmxpY0p3ayB9O1xufVxuXG4vKipcbiAqIFRoZSBpbi1wcm9jZXNzIGtleSBmb3IgYHNlcnZpY2VOYW1lYCBpbiBlcGhlbWVyYWwgbW9kZSwgZ2VuZXJhdGVkIG9uIGZpcnN0XG4gKiB1c2UgYW5kIHJlZ2lzdGVyZWQgaW4gdGhlIGluLW1lbW9yeSBidW5kbGUgc28gdGhpcyBwcm9jZXNzIGNhbiB2ZXJpZnkgaXRzIG93blxuICogdG9rZW5zLiBLZXllZCBieSBOQU1FIChub3QganVzdCBvbmUga2V5KSBzbyBhIHNpbmdsZS1wcm9jZXNzIHN1aXRlIGNhbiBtaW50XG4gKiB0b2tlbnMgZm9yIHNldmVyYWwgc2VydmljZXMsIGFzIHRoZSByZWFsIGZsZWV0IGRvZXMgYWNyb3NzIHByb2Nlc3Nlcy5cbiAqL1xuZnVuY3Rpb24gZXBoZW1lcmFsU2lnbmluZ0tleShzZXJ2aWNlTmFtZTogc3RyaW5nKTogTG9jYWxTaWduaW5nS2V5IHtcbiAgZXBoZW1lcmFsS2V5cyA/Pz0gbmV3IE1hcCgpO1xuICBjb25zdCBleGlzdGluZyA9IGVwaGVtZXJhbEtleXMuZ2V0KHNlcnZpY2VOYW1lKTtcbiAgaWYgKGV4aXN0aW5nKSByZXR1cm4gZXhpc3Rpbmc7XG5cbiAgaWYgKCFlcGhlbWVyYWxXYXJuZWQpIHtcbiAgICBlcGhlbWVyYWxXYXJuZWQgPSB0cnVlO1xuICAgIGVtaXRDb3VudGVyKCdzZXJ2aWNlX3NpZ25pbmdfZXBoZW1lcmFsX2tleV90b3RhbCcsIHsgc2VydmljZTogc2VydmljZUlkZW50aXR5KCkgfSk7XG4gICAgbG9nZ2VyLndhcm4oJ1NFUlZJQ0VfU0lHTklOR19LRVlfRklMRSBpcyBub3Qgc2V0IOKAlCBtaW50aW5nIGludGVybmFsIHNlcnZpY2UgdG9rZW5zIHdpdGggYW4gRVBIRU1FUkFMIGtleS4gT25seSB0aGlzIHByb2Nlc3Mgd2lsbCBhY2NlcHQgdGhlbTsgZXZlcnkgcGVlciB3aWxsIHJlamVjdCB0aGVtLiBTZXQgU0VSVklDRV9TSUdOSU5HX0tFWV9GSUxFICsgU0VSVklDRV9LRVlfQlVORExFX0ZJTEUgKGRlcGxveS9iaW4vc2VydmljZS1zaWduaW5nLWtleXMuc2gpLicpO1xuICB9XG4gIGNvbnN0IHsgcHJpdmF0ZUtleSwgcHVibGljS2V5IH0gPSBjcnlwdG8uZ2VuZXJhdGVLZXlQYWlyU3luYygnZWMnLCB7IG5hbWVkQ3VydmU6ICdwcmltZTI1NnYxJyB9KTtcbiAgY29uc3QgcHVibGljSndrID0gcHVibGljSndrRnJvbShwdWJsaWNLZXkpO1xuICBjb25zdCBnZW5lcmF0ZWQ6IExvY2FsU2lnbmluZ0tleSA9IHsgc2VydmljZU5hbWUsIGtpZDogcHVibGljSndrLmtpZCwgcHJpdmF0ZUtleSwgcHVibGljSndrIH07XG4gIGVwaGVtZXJhbEtleXMuc2V0KHNlcnZpY2VOYW1lLCBnZW5lcmF0ZWQpO1xuICByZXR1cm4gZ2VuZXJhdGVkO1xufVxuXG4vKiogV2hldGhlciB0aGlzIHByb2Nlc3Mgc2lnbnMgd2l0aCBhIG1vdW50ZWQga2V5IChgY29uZmlndXJlZGApIG9yIGFuIGluLXByb2Nlc3Mgb25lLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNlcnZpY2VLZXlNb2RlKCk6ICdjb25maWd1cmVkJyB8ICdlcGhlbWVyYWwnIHtcbiAgcmV0dXJuIHByb2Nlc3MuZW52LlNFUlZJQ0VfU0lHTklOR19LRVlfRklMRSA/ICdjb25maWd1cmVkJyA6ICdlcGhlbWVyYWwnO1xufVxuXG4vKipcbiAqIFRoZSBrZXkgYHNlcnZpY2VOYW1lYCBtdXN0IGJlIHNpZ25lZCB3aXRoLlxuICpcbiAqIEluIGBjb25maWd1cmVkYCBtb2RlIHRoZXJlIGlzIGV4YWN0bHkgT05FIGtleSDigJQgdGhpcyBzZXJ2aWNlJ3Mg4oCUIHNvIGEgcmVxdWVzdFxuICogdG8gc2lnbiBhcyBzb21lIE9USEVSIHNlcnZpY2UgaXMgYSBidWcgYW5kIHRocm93cy4gVGhhdCBtYXR0ZXJzOiBiZWZvcmUgIzE0IGFcbiAqIHNlcnZpY2UgY291bGQgbGVnaXRpbWF0ZWx5IG1pbnQgYHNlcnZpY2U6cGxhdGZvcm1gIHdpdGggdGhlIHNoYXJlZCBzZWNyZXRcbiAqIChhcGkvcGx1Z2luIGRpZCwgZm9yIGl0cyByZWdpc3RyeSBwdXNoZXMpLCBhbmQgdW5kZXIgcGVyLXNlcnZpY2Uga2V5cyBzdWNoIGFcbiAqIHRva2VuIHdvdWxkIGJlIHNpbGVudGx5IHVudmVyaWZpYWJsZSBldmVyeXdoZXJlLiBGYWlsaW5nIGF0IHRoZSBtaW50IHR1cm5zXG4gKiB0aGF0IGludG8gYW4gaW1tZWRpYXRlLCBvYnZpb3VzIGVycm9yIGluc3RlYWQgb2YgYSBydW50aW1lIDQwMS5cbiAqL1xuZnVuY3Rpb24gc2lnbmluZ0tleUZvcihzZXJ2aWNlTmFtZTogc3RyaW5nKTogTG9jYWxTaWduaW5nS2V5IHtcbiAgY29uc3QgZmlsZSA9IHByb2Nlc3MuZW52LlNFUlZJQ0VfU0lHTklOR19LRVlfRklMRTtcbiAgaWYgKCFmaWxlKSByZXR1cm4gZXBoZW1lcmFsU2lnbmluZ0tleShzZXJ2aWNlTmFtZSk7XG5cbiAgaWYgKCFsb2NhbEtleSB8fCBsb2NhbEtleS5zZXJ2aWNlTmFtZSAhPT0gc2VydmljZUlkZW50aXR5KCkpIHtcbiAgICBsb2NhbEtleSA9IGxvYWRDb25maWd1cmVkU2lnbmluZ0tleShmaWxlKTtcbiAgfVxuICBpZiAoc2VydmljZU5hbWUgIT09IGxvY2FsS2V5LnNlcnZpY2VOYW1lKSB7XG4gICAgdGhyb3cgbmV3IFNlcnZpY2VLZXlFcnJvcihcbiAgICAgIGBUaGlzIHByb2Nlc3MgaXMgJyR7bG9jYWxLZXkuc2VydmljZU5hbWV9JyBhbmQgaG9sZHMgb25seSBpdHMgb3duIHNpZ25pbmcga2V5LCBzbyBpdCBjYW5ub3QgbWludCBhIHRva2VuIGZvciAnJHtzZXJ2aWNlTmFtZX0nLiBgXG4gICAgICArICdOYW1lIHRoZSBjYWxsaW5nIHNlcnZpY2UgY29ycmVjdGx5LCBvciBnaXZlIHRoZSBjYWxsZXIgaXRzIG93biBrZXkuJyxcbiAgICApO1xuICB9XG4gIHJldHVybiBsb2NhbEtleTtcbn1cblxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4vLyBWZXJpZmljYXRpb24gYnVuZGxlXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cblxuaW50ZXJmYWNlIExvYWRlZEJ1bmRsZSB7XG4gIC8qKiBga2lkYCDihpIgdGhlIHNlcnZpY2UgdGhhdCBwdWJsaXNoZWQgaXQsIHBsdXMgdGhlIHZlcmlmaWNhdGlvbiBrZXkuICovXG4gIGJ5S2lkOiBNYXA8c3RyaW5nLCB7IHNlcnZpY2VOYW1lOiBzdHJpbmc7IGtleTogS2V5T2JqZWN0IH0+O1xuICBsb2FkZWRBdDogbnVtYmVyO1xuICBtdGltZU1zOiBudW1iZXI7XG59XG5cbmxldCBidW5kbGU6IExvYWRlZEJ1bmRsZSB8IHVuZGVmaW5lZDtcblxuZnVuY3Rpb24gcGFyc2VCdW5kbGUocmF3OiBzdHJpbmcsIGZpbGU6IHN0cmluZyk6IE1hcDxzdHJpbmcsIHsgc2VydmljZU5hbWU6IHN0cmluZzsga2V5OiBLZXlPYmplY3QgfT4ge1xuICBsZXQgZG9jdW1lbnQ6IHVua25vd247XG4gIHRyeSB7XG4gICAgZG9jdW1lbnQgPSBKU09OLnBhcnNlKHJhdyk7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgdGhyb3cgbmV3IFNlcnZpY2VLZXlFcnJvcihgJHtmaWxlfSBpcyBub3QgdmFsaWQgSlNPTjogJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcil9YCk7XG4gIH1cbiAgY29uc3Qgc2VydmljZXMgPSAoZG9jdW1lbnQgYXMgUGFydGlhbDxTZXJ2aWNlS2V5QnVuZGxlPik/LnNlcnZpY2VzO1xuICBpZiAoIXNlcnZpY2VzIHx8IHR5cGVvZiBzZXJ2aWNlcyAhPT0gJ29iamVjdCcpIHtcbiAgICB0aHJvdyBuZXcgU2VydmljZUtleUVycm9yKGAke2ZpbGV9IGhhcyBubyBcInNlcnZpY2VzXCIgb2JqZWN0YCk7XG4gIH1cbiAgY29uc3QgYnlLaWQgPSBuZXcgTWFwPHN0cmluZywgeyBzZXJ2aWNlTmFtZTogc3RyaW5nOyBrZXk6IEtleU9iamVjdCB9PigpO1xuICBmb3IgKGNvbnN0IFtzZXJ2aWNlTmFtZSwgZW50cnldIG9mIE9iamVjdC5lbnRyaWVzKHNlcnZpY2VzKSkge1xuICAgIGZvciAoY29uc3QgandrIG9mIGVudHJ5Py5rZXlzID8/IFtdKSB7XG4gICAgICBpZiAoIWp3az8ua2lkKSBjb250aW51ZTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGJ5S2lkLnNldChqd2sua2lkLCB7IHNlcnZpY2VOYW1lLCBrZXk6IHB1YmxpY0tleUZyb21Kd2soandrKSB9KTtcbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIC8vIE9uZSB1bnVzYWJsZSBlbnRyeSBtdXN0IG5vdCBkaXNjYXJkIHRoZSByZXN0IG9mIHRoZSBidW5kbGUg4oCUIHRoZSBzYW1lXG4gICAgICAgIC8vIHJ1bGUgdGhlIEpXS1MgY2FjaGUgYXBwbGllcyB0byBwbGF0Zm9ybSdzIHB1Ymxpc2hlZCBzZXQuXG4gICAgICAgIGxvZ2dlci53YXJuKCdTa2lwcGluZyB1bnVzYWJsZSBzZXJ2aWNlIGtleScsIHsgc2VydmljZU5hbWUsIGtpZDogandrLmtpZCwgZXJyb3I6IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKSB9KTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgaWYgKGJ5S2lkLnNpemUgPT09IDApIHRocm93IG5ldyBTZXJ2aWNlS2V5RXJyb3IoYCR7ZmlsZX0gY29udGFpbmVkIG5vIHVzYWJsZSBzZXJ2aWNlIGtleXNgKTtcbiAgcmV0dXJuIGJ5S2lkO1xufVxuXG4vKipcbiAqIFRoZSBga2lkYCDihpIgc2VydmljZSBtYXAsIHJlLXJlYWQgd2hlbiB0aGUgcmVmcmVzaCBpbnRlcnZhbCBoYXMgcGFzc2VkIG9yIHRoZVxuICogZmlsZSdzIG10aW1lIGNoYW5nZWQgKHNvIGEgcm90YXRpb24gaXMgcGlja2VkIHVwIHdpdGhvdXQgYSByZXN0YXJ0KS5cbiAqXG4gKiBJbiBlcGhlbWVyYWwgbW9kZSB0aGUgXCJidW5kbGVcIiBpcyB3aGF0ZXZlciB0aGlzIHByb2Nlc3MgZ2VuZXJhdGVkIGZvciBpdHNlbGYuXG4gKiBOZXZlciB0aHJvd3MgZm9yIGEgbWlzc2luZyBmaWxlIGluIHRoYXQgbW9kZSDigJQgaXQganVzdCBob2xkcyB0aG9zZSBrZXlzLlxuICovXG5mdW5jdGlvbiB2ZXJpZmljYXRpb25LZXlzKCk6IE1hcDxzdHJpbmcsIHsgc2VydmljZU5hbWU6IHN0cmluZzsga2V5OiBLZXlPYmplY3QgfT4ge1xuICBjb25zdCBmaWxlID0gcHJvY2Vzcy5lbnYuU0VSVklDRV9LRVlfQlVORExFX0ZJTEU7XG4gIGlmICghZmlsZSkge1xuICAgIGNvbnN0IGxvY2FsID0gbmV3IE1hcDxzdHJpbmcsIHsgc2VydmljZU5hbWU6IHN0cmluZzsga2V5OiBLZXlPYmplY3QgfT4oKTtcbiAgICBmb3IgKGNvbnN0IGtleSBvZiBlcGhlbWVyYWxLZXlzPy52YWx1ZXMoKSA/PyBbXSkge1xuICAgICAgbG9jYWwuc2V0KGtleS5raWQsIHsgc2VydmljZU5hbWU6IGtleS5zZXJ2aWNlTmFtZSwga2V5OiBjcnlwdG8uY3JlYXRlUHVibGljS2V5KGtleS5wcml2YXRlS2V5KSB9KTtcbiAgICB9XG4gICAgcmV0dXJuIGxvY2FsO1xuICB9XG5cbiAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgbGV0IG10aW1lTXMgPSAwO1xuICB0cnkge1xuICAgIG10aW1lTXMgPSBzdGF0U3luYyhmaWxlKS5tdGltZU1zO1xuICB9IGNhdGNoIHtcbiAgICAvLyBVbnJlYWRhYmxlIG5vdzoga2VlcCBzZXJ2aW5nIGEgYnVuZGxlIGFscmVhZHkgaW4gaGFuZCAocHVibGljIGtleXMgZG9uJ3RcbiAgICAvLyBleHBpcmUsIGFuZCByZWZ1c2luZyBldmVyeSBpbnRlcm5hbCBjYWxsIGJlY2F1c2UgYSBtb3VudCBibGlwcGVkIHdvdWxkIGJlXG4gICAgLy8gYSBzZWxmLWluZmxpY3RlZCBvdXRhZ2UpLiBXaXRoIG5vdGhpbmcgaW4gaGFuZCwgZmFpbCBjbG9zZWQgYmVsb3cuXG4gICAgaWYgKGJ1bmRsZSkgcmV0dXJuIGJ1bmRsZS5ieUtpZDtcbiAgfVxuICBpZiAoYnVuZGxlICYmIGJ1bmRsZS5tdGltZU1zID09PSBtdGltZU1zICYmIG5vdyAtIGJ1bmRsZS5sb2FkZWRBdCA8IEJVTkRMRV9SRUZSRVNIX0lOVEVSVkFMX01TKSB7XG4gICAgcmV0dXJuIGJ1bmRsZS5ieUtpZDtcbiAgfVxuXG4gIHRyeSB7XG4gICAgY29uc3QgYnlLaWQgPSBwYXJzZUJ1bmRsZShyZWFkRmlsZVN5bmMoZmlsZSwgJ3V0ZjgnKSwgZmlsZSk7XG4gICAgYnVuZGxlID0geyBieUtpZCwgbG9hZGVkQXQ6IG5vdywgbXRpbWVNcyB9O1xuICAgIHJldHVybiBieUtpZDtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBpZiAoYnVuZGxlKSB7XG4gICAgICBsb2dnZXIud2FybignU2VydmljZSBrZXkgYnVuZGxlIHJlbG9hZCBmYWlsZWQ7IGNvbnRpbnVpbmcgb24gdGhlIGxvYWRlZCBjb3B5JywgeyBmaWxlLCBlcnJvcjogZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpIH0pO1xuICAgICAgcmV0dXJuIGJ1bmRsZS5ieUtpZDtcbiAgICB9XG4gICAgdGhyb3cgZXJyb3IgaW5zdGFuY2VvZiBTZXJ2aWNlS2V5RXJyb3IgPyBlcnJvciA6IG5ldyBTZXJ2aWNlS2V5RXJyb3IoU3RyaW5nKGVycm9yKSk7XG4gIH1cbn1cblxuLyoqXG4gKiBXaGV0aGVyIGBraWRgIGJlbG9uZ3MgdG8gYW4gaW50ZXJuYWwgU0VSVklDRSBrZXkuIFRoaXMgaXMgaG93IGEgYmVhcmVyIHRva2VuXG4gKiBpcyByb3V0ZWQgYmV0d2VlbiB0aGUgdHdvIEVTMjU2IGNoYWluczogYm90aCB1c2VyIGFuZCBzZXJ2aWNlIHRva2VucyBhcmVcbiAqIEVTMjU2IHdpdGggYSBga2lkYCwgYW5kIGBraWRgIGlzIGFuIFJGQyA3NjM4IHRodW1icHJpbnQsIHNvIG93bmVyc2hpcCBvZiB0aGVcbiAqIGlkIOKAlCBub3QgYSBjbGFpbSBpbnNpZGUgdGhlIHRva2VuIOKAlCBkZWNpZGVzIHdoaWNoIGtleSBzZXQgbWF5IHZlcmlmeSBpdC5cbiAqIFJldHVybnMgYGZhbHNlYCAobmV2ZXIgdGhyb3dzKSB3aGVuIG5vIGJ1bmRsZSBjYW4gYmUgcmVhZDsgdGhlIHRva2VuIHRoZW5cbiAqIHRha2VzIHRoZSB1c2VyIGNoYWluIGFuZCBpcyByZWplY3RlZCB0aGVyZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzU2VydmljZUtpZChraWQ6IHN0cmluZyk6IGJvb2xlYW4ge1xuICB0cnkge1xuICAgIHJldHVybiB2ZXJpZmljYXRpb25LZXlzKCkuaGFzKGtpZCk7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxufVxuXG4vKiogRXZlcnkgc2VydmljZSBuYW1lIHRoZSBidW5kbGUgcHVibGlzaGVzIGEga2V5IGZvci4gRGlhZ25vc3RpY3MgYW5kIHRlc3RzLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGtub3duU2VydmljZU5hbWVzKCk6IHN0cmluZ1tdIHtcbiAgdHJ5IHtcbiAgICByZXR1cm4gWy4uLm5ldyBTZXQoWy4uLnZlcmlmaWNhdGlvbktleXMoKS52YWx1ZXMoKV0ubWFwKCh2KSA9PiB2LnNlcnZpY2VOYW1lKSldLnNvcnQoKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIFtdO1xuICB9XG59XG5cbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gU2lnbiAvIHZlcmlmeVxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbi8qKiBiYXNlNjR1cmwgb2YgYSBKU09OIHZhbHVlLCB0aGUgSldTIHdheSAobm8gcGFkZGluZywgVVJMIGFscGhhYmV0KS4gKi9cbmZ1bmN0aW9uIGI2NHVKc29uKHZhbHVlOiB1bmtub3duKTogc3RyaW5nIHtcbiAgcmV0dXJuIEJ1ZmZlci5mcm9tKEpTT04uc3RyaW5naWZ5KHZhbHVlKSwgJ3V0Zi04JykudG9TdHJpbmcoJ2Jhc2U2NHVybCcpO1xufVxuXG4vKipcbiAqIFNpZ24gYGNsYWltc2AgYXMgdGhpcyBzZXJ2aWNlJ3MgaW50ZXJuYWwgdG9rZW46IEVTMjU2LCBga2lkYCA9IHRoZSBzaWduZXInc1xuICogdGh1bWJwcmludCwgYHN1YmAgPSBgc2VydmljZTo8c2VydmljZU5hbWU+YC4gYGlhdGAvYGV4cGAgYW5kIHRoZSBvcHRpb25hbFxuICogaXNzdWVyL2F1ZGllbmNlIGFyZSBzdGFtcGVkIGhlcmUgc28gdGhlcmUgaXMgb25lIHBsYWNlIHRoYXQgZGVjaWRlcyB3aGF0IGFcbiAqIHNlcnZpY2UgdG9rZW4gc2F5cy5cbiAqXG4gKiBIYW5kLXJvbGxlZCAocmF0aGVyIHRoYW4gYGp3dC5zaWduYCkgb25seSBzbyB0aGUgaGVhZGVyIGNhcnJpZXMgdGhlIGBraWRgIGluXG4gKiB0aGUgc2FtZSBzaGFwZSBwbGF0Zm9ybSdzIHNpZ25lciBwcm9kdWNlcyDigJQgYGNyeXB0by5zaWduYCBpcyBzeW5jaHJvbm91cywgc29cbiAqIHVubGlrZSB0aGUgS01TIHBhdGggdGhpcyBjb3N0cyBub3RoaW5nLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2lnblNlcnZpY2VKd3QoXG4gIGNsYWltczogUmVjb3JkPHN0cmluZywgdW5rbm93bj4sXG4gIG9wdGlvbnM6IHsgc2VydmljZU5hbWU6IHN0cmluZzsgZXhwaXJlc0luU2Vjb25kczogbnVtYmVyOyBpc3N1ZXI/OiBzdHJpbmc7IGF1ZGllbmNlPzogc3RyaW5nIH0sXG4pOiBzdHJpbmcge1xuICBjb25zdCBrZXkgPSBzaWduaW5nS2V5Rm9yKG9wdGlvbnMuc2VydmljZU5hbWUpO1xuICBjb25zdCBub3cgPSBNYXRoLmZsb29yKERhdGUubm93KCkgLyAxMDAwKTtcbiAgY29uc3QgYm9keSA9IHtcbiAgICAuLi5jbGFpbXMsXG4gICAgc3ViOiBgJHtTRVJWSUNFX1NVQkpFQ1RfUFJFRklYfSR7b3B0aW9ucy5zZXJ2aWNlTmFtZX1gLFxuICAgIGlhdDogbm93LFxuICAgIGV4cDogbm93ICsgb3B0aW9ucy5leHBpcmVzSW5TZWNvbmRzLFxuICAgIC4uLihvcHRpb25zLmlzc3VlciA/IHsgaXNzOiBvcHRpb25zLmlzc3VlciB9IDoge30pLFxuICAgIC4uLihvcHRpb25zLmF1ZGllbmNlID8geyBhdWQ6IG9wdGlvbnMuYXVkaWVuY2UgfSA6IHt9KSxcbiAgfTtcbiAgY29uc3Qgc2lnbmluZ0lucHV0ID0gYCR7YjY0dUpzb24oeyBhbGc6IFNFUlZJQ0VfVE9LRU5fQUxHT1JJVEhNLCB0eXA6ICdKV1QnLCBraWQ6IGtleS5raWQgfSl9LiR7YjY0dUpzb24oYm9keSl9YDtcbiAgY29uc3Qgc2lnbmF0dXJlID0gY3J5cHRvLnNpZ24oJ3NoYTI1NicsIEJ1ZmZlci5mcm9tKHNpZ25pbmdJbnB1dCwgJ3V0Zi04JyksIHtcbiAgICBrZXk6IGtleS5wcml2YXRlS2V5LFxuICAgIGRzYUVuY29kaW5nOiAnaWVlZS1wMTM2MycsXG4gIH0pO1xuICByZXR1cm4gYCR7c2lnbmluZ0lucHV0fS4ke3NpZ25hdHVyZS50b1N0cmluZygnYmFzZTY0dXJsJyl9YDtcbn1cblxuLyoqXG4gKiBWZXJpZnkgYW4gaW50ZXJuYWwgc2VydmljZSB0b2tlbiwgU1lOQ0hST05PVVNMWSwgYW5kIHJldHVybiBpdHMgY2xhaW1zLlxuICpcbiAqIFRoZSBzZWN1cml0eSBwcm9wZXJ0eSB0aGlzIGZ1bmN0aW9uIGV4aXN0cyBmb3I6IHRoZSBga2lkYCBzZWxlY3RzIHRoZSBrZXlcbiAqIEFORCBuYW1lcyB0aGUgc2lnbmVyLCBhbmQgdGhlIHRva2VuJ3MgYHN1YmAgbXVzdCBhZ3JlZS4gU28gYGJpbGxpbmdgIGNhbm5vdFxuICogYmUgaW1wZXJzb25hdGVkIGJ5IGFueSBvdGhlciBzZXJ2aWNlIOKAlCBub3QgZXZlbiBvbmUgdGhhdCBob2xkcyBhIHZhbGlkIGtleSBvZlxuICogaXRzIG93biDigJQgd2hpY2ggaXMgd2hhdCB0aGUgc2hhcmVkIHNlY3JldCBjb3VsZCBuZXZlciBndWFyYW50ZWUuXG4gKlxuICogQHRocm93cyB7and0Lkpzb25XZWJUb2tlbkVycm9yfSB1bmtub3duIGBraWRgLCB3cm9uZyBhbGdvcml0aG0sIGJhZFxuICogICAgICAgICBzaWduYXR1cmUsIGV4cGlyZWQsIG9yIGEgYHN1YmAgdGhhdCBkaXNhZ3JlZXMgd2l0aCB0aGUgc2lnbmluZyBrZXkuXG4gKiBAdGhyb3dzIHtTZXJ2aWNlS2V5RXJyb3J9IG5vIHZlcmlmaWNhdGlvbiBidW5kbGUgY291bGQgYmUgcmVhZCBhdCBhbGwg4oCUIHRoZVxuICogICAgICAgICB0b2tlbidzIHZhbGlkaXR5IGlzIFVOS05PV04gYW5kIHRoZSBjYWxsZXIgbXVzdCBub3QgdHJlYXQgaXQgYXMgdmFsaWQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB2ZXJpZnlTZXJ2aWNlSnd0PFQgPSBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj4oXG4gIHRva2VuOiBzdHJpbmcsXG4gIG9wdGlvbnM6IHsga2lkOiBzdHJpbmc7IGlzc3Vlcj86IHN0cmluZzsgYXVkaWVuY2U/OiBzdHJpbmcgfSxcbik6IFQge1xuICBjb25zdCBlbnRyeSA9IHZlcmlmaWNhdGlvbktleXMoKS5nZXQob3B0aW9ucy5raWQpO1xuICBpZiAoIWVudHJ5KSB7XG4gICAgZW1pdENvdW50ZXIoJ3NlcnZpY2VfdG9rZW5fdmVyaWZ5X3RvdGFsJywgeyByZXN1bHQ6ICd1bmtub3duX2tpZCcgfSk7XG4gICAgdGhyb3cgbmV3IGp3dC5Kc29uV2ViVG9rZW5FcnJvcihgTm8gcHVibGlzaGVkIHNlcnZpY2Uga2V5IGZvciBraWQgJHtvcHRpb25zLmtpZH1gKTtcbiAgfVxuICBsZXQgY2xhaW1zOiBUICYgeyBzdWI/OiB1bmtub3duIH07XG4gIHRyeSB7XG4gICAgY2xhaW1zID0gand0LnZlcmlmeSh0b2tlbiwgZW50cnkua2V5LCB7XG4gICAgICBhbGdvcml0aG1zOiBbU0VSVklDRV9UT0tFTl9BTEdPUklUSE1dLFxuICAgICAgLi4uKG9wdGlvbnMuaXNzdWVyID8geyBpc3N1ZXI6IG9wdGlvbnMuaXNzdWVyIH0gOiB7fSksXG4gICAgICAuLi4ob3B0aW9ucy5hdWRpZW5jZSA/IHsgYXVkaWVuY2U6IG9wdGlvbnMuYXVkaWVuY2UgfSA6IHt9KSxcbiAgICB9KSBhcyBUICYgeyBzdWI/OiB1bmtub3duIH07XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgZW1pdENvdW50ZXIoJ3NlcnZpY2VfdG9rZW5fdmVyaWZ5X3RvdGFsJywgeyByZXN1bHQ6ICdpbnZhbGlkJyB9KTtcbiAgICB0aHJvdyBlcnJvcjtcbiAgfVxuICBpZiAoY2xhaW1zLnN1YiAhPT0gYCR7U0VSVklDRV9TVUJKRUNUX1BSRUZJWH0ke2VudHJ5LnNlcnZpY2VOYW1lfWApIHtcbiAgICAvLyBBIHZhbGlkIHNpZ25hdHVyZSBieSB0aGUgV1JPTkcgc2VydmljZTogdGhlIGtleSBiZWxvbmdzIHRvIGBlbnRyeS5zZXJ2aWNlTmFtZWBcbiAgICAvLyBidXQgdGhlIHRva2VuIHNwZWFrcyBmb3Igc29tZW9uZSBlbHNlLiBUaGlzIGlzIHRoZSBjcm9zcy1zZXJ2aWNlIGZvcmdlcnlcbiAgICAvLyB0aGUgc2hhcmVkIHNlY3JldCBtYWRlIHVuZGV0ZWN0YWJsZS5cbiAgICBlbWl0Q291bnRlcignc2VydmljZV90b2tlbl92ZXJpZnlfdG90YWwnLCB7IHJlc3VsdDogJ3N1YmplY3RfbWlzbWF0Y2gnLCBzZXJ2aWNlOiBlbnRyeS5zZXJ2aWNlTmFtZSB9KTtcbiAgICB0aHJvdyBuZXcgand0Lkpzb25XZWJUb2tlbkVycm9yKGBTZXJ2aWNlIHRva2VuIHN1YmplY3QgJHtTdHJpbmcoY2xhaW1zLnN1Yil9IHdhcyBzaWduZWQgYnkgJHtlbnRyeS5zZXJ2aWNlTmFtZX1gKTtcbiAgfVxuICBlbWl0Q291bnRlcignc2VydmljZV90b2tlbl92ZXJpZnlfdG90YWwnLCB7IHJlc3VsdDogJ29rJywgc2VydmljZTogZW50cnkuc2VydmljZU5hbWUgfSk7XG4gIHJldHVybiBjbGFpbXM7XG59XG5cbi8qKlxuICogVHJ1ZSB3aGlsZSB0aGlzIHNlcnZpY2Ugc3RpbGwgcHVibGlzaGVzIG1vcmUgdGhhbiBvbmUgdmVyaWZpY2F0aW9uIGtleSDigJQgaS5lLlxuICogYSBzaWduaW5nLWtleSByb3RhdGlvbiBpcyBvcGVuIGFuZCB0aGUgcmV0aXJpbmcga2V5IGlzIHN0aWxsIHRydXN0ZWQuIEZlZWRzXG4gKiB0aGUgYHNlY3JldF9yb3RhdGlvbl9wcmV2aW91c19zZXR7c2VjcmV0PVwiU0VSVklDRV9TSUdOSU5HX0tFWVwifWAgZ2F1Z2UuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc1JldGlyaW5nU2VydmljZUtleVB1Ymxpc2hlZCgpOiBib29sZWFuIHtcbiAgdHJ5IHtcbiAgICBjb25zdCBtZSA9IHNlcnZpY2VJZGVudGl0eSgpO1xuICAgIHJldHVybiBbLi4udmVyaWZpY2F0aW9uS2V5cygpLnZhbHVlcygpXS5maWx0ZXIoKHYpID0+IHYuc2VydmljZU5hbWUgPT09IG1lKS5sZW5ndGggPiAxO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuLyoqXG4gKiBUZXN0IHNlYW06IGRyb3AgdGhlIGxvYWRlZCBzaWduaW5nIGtleSwgdGhlIGJ1bmRsZSBhbmQgYW55IGVwaGVtZXJhbCBrZXlzIHNvXG4gKiB0aGUgbmV4dCBzaWduL3ZlcmlmeSByZS1yZWFkcyB0aGUgZW52aXJvbm1lbnQuIERvbid0IGNhbGwgZnJvbSBwcm9kdWN0aW9uIGNvZGVcbiAqIOKAlCByb3RhdGlvbiBpcyBwaWNrZWQgdXAgYnkgdGhlIHJlZnJlc2ggaW50ZXJ2YWwgYW5kIHRoZSBtdGltZSBjaGVjay5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIF9yZXNldFNlcnZpY2VLZXlzRm9yVGVzdHMoKTogdm9pZCB7XG4gIGxvY2FsS2V5ID0gdW5kZWZpbmVkO1xuICBidW5kbGUgPSB1bmRlZmluZWQ7XG4gIGVwaGVtZXJhbEtleXMgPSB1bmRlZmluZWQ7XG4gIGVwaGVtZXJhbFdhcm5lZCA9IGZhbHNlO1xufVxuIl19
@@ -1,3 +1,10 @@
1
+ import { type ReadyAwareRedis } from './env-redis.js';
2
+ /** What a ticket authorizes: the owning org and, for subject-bound channels, the stream subject. */
3
+ export interface SseTicketRecord {
4
+ orgId: string;
5
+ /** Stream subject the ticket is bound to (e.g. a build-log requestId). Absent for org-keyed channels. */
6
+ subject?: string;
7
+ }
1
8
  /** Result of {@link SseTicketStore.issue}. */
2
9
  export type SseTicketIssueResult = {
3
10
  ok: true;
@@ -7,21 +14,31 @@ export type SseTicketIssueResult = {
7
14
  reason: 'total' | 'org';
8
15
  };
9
16
  /**
10
- * Store for short-lived, single-use SSE auth tickets. A client exchanges its JWT
11
- * for a ticket (so the token never lands in an EventSource query string / access
12
- * log), then redeems it on the SSE `GET`.
17
+ * The ONE store for short-lived, single-use SSE auth tickets, plus the
18
+ * stream-ownership bindings subject-bound channels need.
19
+ *
20
+ * A client exchanges its JWT for a ticket (so the token never lands in an
21
+ * EventSource query string / access log), then redeems it on the SSE `GET`.
13
22
  *
14
- * The store is the multi-replica correctness boundary: a ticket minted on pod A
15
- * MUST be redeemable on pod B. The Redis backend makes that true; the in-memory
16
- * backend is a single-process fallback for dev / single-replica installs.
23
+ * - **Tickets** are single-use across pods: the Redis backend redeems with
24
+ * `GETDEL`, so two pods can never both spend one ticket.
25
+ * - **Caps count LIVE tickets** (issued, not yet redeemed, not expired) — per org
26
+ * and in total. A redeemed or expired ticket frees its slot immediately.
27
+ * - **Ownership** records which org owns a stream subject, so a subject-bound
28
+ * channel can refuse to mint a ticket for another org's stream.
29
+ *
30
+ * Both backends share these semantics; the in-memory one is correct for a
31
+ * single process only.
17
32
  */
18
33
  export interface SseTicketStore {
19
- /** Mint a ticket for `orgId`, or reject when a rate window is saturated. */
20
- issue(orgId: string): Promise<SseTicketIssueResult>;
21
- /** Atomically redeem a ticket exactly once; returns its org or null if invalid/expired/spent. */
22
- consume(ticketId: string): Promise<{
23
- orgId: string;
24
- } | null>;
34
+ /** Mint a ticket for `orgId` (optionally bound to `subject`), or reject when a live-ticket cap is reached. */
35
+ issue(orgId: string, subject?: string): Promise<SseTicketIssueResult>;
36
+ /** Redeem a ticket exactly once; null when unknown, expired or already spent. */
37
+ consume(ticketId: string): Promise<SseTicketRecord | null>;
38
+ /** Record the org that owns `subject` for `ttlMs`. */
39
+ bindOwner(subject: string, orgId: string, ttlMs: number): Promise<void>;
40
+ /** The org that owns `subject`, or null when none is bound. Throws when the backend can't answer. */
41
+ getOwner(subject: string): Promise<string | null>;
25
42
  /** Release timers/resources. In-memory clears its sweep interval; Redis is a no-op. */
26
43
  stop(): void;
27
44
  }
@@ -29,23 +46,39 @@ export interface SseTicketStore {
29
46
  export interface SseTicketStoreConfig {
30
47
  /** Ticket lifetime — long enough for the client to open the EventSource. */
31
48
  ttlMs: number;
32
- /** Hard cap on tickets minted per TTL window across all orgs (abuse bound). */
49
+ /** Cap on LIVE tickets across all orgs (abuse / memory bound). */
33
50
  maxTotal: number;
34
- /** Per-org cap on tickets minted per TTL window (single-tenant abuse bound). */
51
+ /** Cap on LIVE tickets per org (single-tenant fairness bound). */
35
52
  maxPerOrg: number;
36
53
  /**
37
- * Optional Redis key namespace so independent channels (e.g. message
38
- * notifications vs. reporting execution-status) don't share one ticket
39
- * keyspace — a ticket minted for one channel must not be redeemable on
40
- * another. Omit for the default (shared) namespace.
54
+ * Key namespace so independent channels (message notifications, reporting
55
+ * execution-status, a service's build-log stream) never share tickets, caps
56
+ * or ownership bindings — a ticket minted for one channel must not be
57
+ * redeemable on another. Omit for the default namespace.
41
58
  */
42
59
  keyPrefix?: string;
43
60
  }
61
+ /** In-memory backend — single-process fallback when Redis isn't configured. */
62
+ export declare function createMemorySseTicketStore(config: SseTicketStoreConfig): SseTicketStore;
63
+ /** Minimal ioredis surface the Redis backend needs (GETDEL requires Redis ≥ 6.2). */
64
+ export interface SseTicketRedis extends ReadyAwareRedis {
65
+ eval(script: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;
66
+ getdel(key: string): Promise<string | null>;
67
+ zrem(key: string, ...members: string[]): Promise<number>;
68
+ set(key: string, value: string, ...args: (string | number)[]): Promise<unknown>;
69
+ get(key: string): Promise<string | null>;
70
+ }
71
+ /**
72
+ * Redis backend — multi-replica safe.
73
+ *
74
+ * Fail-CLOSED: a Redis error makes `issue` reject (`total`), `consume` return
75
+ * null and `getOwner` throw, so an outage degrades to "no live stream" rather
76
+ * than handing out or accepting unvalidated tickets. `bindOwner` is best-effort.
77
+ */
78
+ export declare function createRedisSseTicketStore(redis: SseTicketRedis, config: SseTicketStoreConfig): SseTicketStore;
44
79
  /**
45
80
  * Build an {@link SseTicketStore} from the standard Redis env (`REDIS_URL` or
46
- * `REDIS_SENTINELS`). Uses the Redis backend when configured
47
- * (multi-replica safe), otherwise falls back to the in-memory single-process
48
- * backend. A service opts in with one boot line:
49
- * `const tickets = createEnvSseTicketStore({ ttlMs, maxTotal, maxPerOrg });`
81
+ * `REDIS_SENTINELS`): the Redis backend when configured (multi-replica safe),
82
+ * otherwise the in-memory single-process backend.
50
83
  */
51
84
  export declare function createEnvSseTicketStore(config: SseTicketStoreConfig): SseTicketStore;