@pipeline-builder/api-core 3.4.36 → 3.4.38
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 +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.js +3 -3
- package/lib/middleware/auth.d.ts +9 -4
- package/lib/middleware/auth.js +16 -13
- package/lib/services/http-client.d.ts +5 -0
- package/lib/services/http-client.js +14 -1
- package/lib/services/index.d.ts +1 -0
- package/lib/services/index.js +2 -1
- package/lib/services/quota.d.ts +65 -1
- package/lib/services/quota.js +99 -3
- package/lib/services/remote-audit-client.d.ts +63 -0
- package/lib/services/remote-audit-client.js +67 -0
- package/lib/types/common.d.ts +37 -2
- package/lib/types/common.js +5 -2
- package/lib/types/feature-flags.d.ts +5 -5
- package/lib/types/feature-flags.js +9 -9
- package/lib/types/http.d.ts +4 -14
- package/lib/types/http.js +1 -1
- package/lib/types/quota-tiers.d.ts +13 -7
- package/lib/types/quota-tiers.js +54 -5
- package/lib/utils/identity.js +6 -6
- package/lib/utils/index.d.ts +3 -0
- package/lib/utils/index.js +4 -1
- package/lib/utils/metric-emitter.d.ts +36 -0
- package/lib/utils/metric-emitter.js +37 -0
- package/lib/utils/org-aws-credentials.d.ts +154 -0
- package/lib/utils/org-aws-credentials.js +159 -0
- package/lib/utils/secret-encryption.d.ts +205 -0
- package/lib/utils/secret-encryption.js +388 -0
- package/package.json +14 -6
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright 2026 Pipeline Builder Contributors
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.OrgAwsCredentialsManager = void 0;
|
|
6
|
+
exports.resolveOrgCredentialsOnce = resolveOrgCredentialsOnce;
|
|
7
|
+
exports.withOrgAwsCredentials = withOrgAwsCredentials;
|
|
8
|
+
/**
|
|
9
|
+
* Per-process manager that resolves and caches per-org credential providers.
|
|
10
|
+
* One instance per service; share it across handlers.
|
|
11
|
+
*/
|
|
12
|
+
class OrgAwsCredentialsManager {
|
|
13
|
+
resolver;
|
|
14
|
+
fallbackOverride;
|
|
15
|
+
region;
|
|
16
|
+
endpoint;
|
|
17
|
+
cache = new Map();
|
|
18
|
+
inFlight = new Map();
|
|
19
|
+
/** Lazy default chain. Built once per manager so we don't import the
|
|
20
|
+
* credential-providers SDK in test environments that never touch the
|
|
21
|
+
* fallback path. */
|
|
22
|
+
fallbackCache;
|
|
23
|
+
constructor(opts) {
|
|
24
|
+
this.resolver = opts.resolver;
|
|
25
|
+
this.fallbackOverride = opts.fallback;
|
|
26
|
+
this.region = opts.region ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
|
|
27
|
+
this.endpoint = opts.endpoint ?? process.env.AWS_STS_ENDPOINT;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Return an AwsCredentialIdentityProvider scoped to `orgId`. Pass the
|
|
31
|
+
* returned value into any SDK v3 client's `credentials` option.
|
|
32
|
+
*
|
|
33
|
+
* Concurrent first-touch callers share one resolver invocation; later
|
|
34
|
+
* calls hit the cache. The provider itself caches and refreshes
|
|
35
|
+
* AssumeRole-derived credentials internally.
|
|
36
|
+
*/
|
|
37
|
+
async getCredentials(orgId) {
|
|
38
|
+
if (!orgId)
|
|
39
|
+
throw new Error('OrgAwsCredentialsManager.getCredentials requires a non-empty orgId');
|
|
40
|
+
const cached = this.cache.get(orgId);
|
|
41
|
+
if (cached)
|
|
42
|
+
return cached.provider;
|
|
43
|
+
let pending = this.inFlight.get(orgId);
|
|
44
|
+
if (!pending) {
|
|
45
|
+
pending = this.resolveAndBuild(orgId);
|
|
46
|
+
this.inFlight.set(orgId, pending);
|
|
47
|
+
void pending.finally(() => {
|
|
48
|
+
if (this.inFlight.get(orgId) === pending)
|
|
49
|
+
this.inFlight.delete(orgId);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return pending;
|
|
53
|
+
}
|
|
54
|
+
/** Drop the cached provider for an org. Call after the operator rotates
|
|
55
|
+
* the role ARN or external id so the next request rebuilds. */
|
|
56
|
+
evict(orgId) {
|
|
57
|
+
this.cache.delete(orgId);
|
|
58
|
+
}
|
|
59
|
+
/** Drop every cached provider. Useful in tests; rarely in production. */
|
|
60
|
+
evictAll() {
|
|
61
|
+
this.cache.clear();
|
|
62
|
+
}
|
|
63
|
+
async resolveAndBuild(orgId) {
|
|
64
|
+
const cfg = await this.resolver(orgId);
|
|
65
|
+
if (!cfg || !cfg.assumeRoleArn) {
|
|
66
|
+
const fallback = await this.getFallback();
|
|
67
|
+
this.cache.set(orgId, { fingerprint: 'fallback', provider: fallback });
|
|
68
|
+
return fallback;
|
|
69
|
+
}
|
|
70
|
+
const provider = await this.buildAssumeRoleProvider(orgId, cfg);
|
|
71
|
+
this.cache.set(orgId, { fingerprint: fingerprintConfig(cfg), provider });
|
|
72
|
+
return provider;
|
|
73
|
+
}
|
|
74
|
+
async getFallback() {
|
|
75
|
+
if (this.fallbackOverride)
|
|
76
|
+
return this.fallbackOverride;
|
|
77
|
+
if (this.fallbackCache)
|
|
78
|
+
return this.fallbackCache;
|
|
79
|
+
// Lazy-import so test envs that supply their own resolver/fallback
|
|
80
|
+
// don't load the credential-providers SDK.
|
|
81
|
+
const { fromNodeProviderChain } = await import('@aws-sdk/credential-providers');
|
|
82
|
+
this.fallbackCache = fromNodeProviderChain();
|
|
83
|
+
return this.fallbackCache;
|
|
84
|
+
}
|
|
85
|
+
async buildAssumeRoleProvider(orgId, cfg) {
|
|
86
|
+
// Dynamic imports — STS + credential-providers are heavyweight and
|
|
87
|
+
// services that never have per-org roles configured shouldn't load them.
|
|
88
|
+
const [{ STSClient }, { fromTemporaryCredentials }] = await Promise.all([
|
|
89
|
+
import('@aws-sdk/client-sts'),
|
|
90
|
+
import('@aws-sdk/credential-providers'),
|
|
91
|
+
]);
|
|
92
|
+
const masterCredentials = this.fallbackOverride ?? (await this.getFallback());
|
|
93
|
+
const sessionName = cfg.roleSessionName ?? `pipeline-builder-${orgId}`.slice(0, 64);
|
|
94
|
+
const region = cfg.region ?? this.region;
|
|
95
|
+
return fromTemporaryCredentials({
|
|
96
|
+
// Inner STS client uses the service's own credentials (the fallback
|
|
97
|
+
// chain). Those creds need `sts:AssumeRole` on the org's role.
|
|
98
|
+
masterCredentials,
|
|
99
|
+
clientConfig: {
|
|
100
|
+
region,
|
|
101
|
+
...(this.endpoint ? { endpoint: this.endpoint } : {}),
|
|
102
|
+
// Cast to satisfy fromTemporaryCredentials' typing; STSClient
|
|
103
|
+
// matches the structural client shape it expects.
|
|
104
|
+
},
|
|
105
|
+
params: {
|
|
106
|
+
RoleArn: cfg.assumeRoleArn,
|
|
107
|
+
RoleSessionName: sessionName,
|
|
108
|
+
DurationSeconds: cfg.sessionDurationSeconds ?? 3600,
|
|
109
|
+
...(cfg.externalId ? { ExternalId: cfg.externalId } : {}),
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.OrgAwsCredentialsManager = OrgAwsCredentialsManager;
|
|
115
|
+
/**
|
|
116
|
+
* Deterministic fingerprint of a config. Used by `getCredentials` to
|
|
117
|
+
* detect "the cache is stale because the operator changed the config" —
|
|
118
|
+
* not exposed publicly, just defense in depth against a missed `evict`.
|
|
119
|
+
*/
|
|
120
|
+
function fingerprintConfig(cfg) {
|
|
121
|
+
return [
|
|
122
|
+
cfg.assumeRoleArn,
|
|
123
|
+
cfg.externalId ?? '',
|
|
124
|
+
cfg.region ?? '',
|
|
125
|
+
cfg.sessionDurationSeconds ?? '',
|
|
126
|
+
cfg.roleSessionName ?? '',
|
|
127
|
+
].join('|');
|
|
128
|
+
}
|
|
129
|
+
/** Convenience: directly resolve credentials for a single call. The manager
|
|
130
|
+
* doesn't cache when called this way — useful in CLIs / one-shot scripts. */
|
|
131
|
+
async function resolveOrgCredentialsOnce(orgId, resolver) {
|
|
132
|
+
const manager = new OrgAwsCredentialsManager({ resolver });
|
|
133
|
+
const provider = await manager.getCredentials(orgId);
|
|
134
|
+
return provider();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Adapter: resolve per-org credentials and construct an AWS SDK client
|
|
138
|
+
* pre-bound to them. The factory is async because credential resolution may
|
|
139
|
+
* need to make a network call (fetching org config + AssumeRole). Once
|
|
140
|
+
* resolved, the returned client is ready for normal SDK calls; the provider
|
|
141
|
+
* inside refreshes credentials automatically before they expire.
|
|
142
|
+
*
|
|
143
|
+
* Typical usage:
|
|
144
|
+
* ```ts
|
|
145
|
+
* const s3 = await withOrgAwsCredentials(manager, orgId, (creds) =>
|
|
146
|
+
* new S3Client({ credentials: creds, region: 'us-west-2' }));
|
|
147
|
+
* await s3.send(new ListObjectsV2Command({ Bucket: bucketFor(orgId) }));
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* The factory is invoked exactly once per call — callers that need a
|
|
151
|
+
* long-lived client should cache the result themselves rather than rebuilding
|
|
152
|
+
* on every operation. (Re-resolving on every op is fine for cold paths and
|
|
153
|
+
* a wasted couple of object allocations on hot paths.)
|
|
154
|
+
*/
|
|
155
|
+
async function withOrgAwsCredentials(manager, orgId, factory) {
|
|
156
|
+
const credentials = await manager.getCredentials(orgId);
|
|
157
|
+
return factory(credentials);
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3JnLWF3cy1jcmVkZW50aWFscy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9vcmctYXdzLWNyZWRlbnRpYWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDOzs7QUFxUHRDLDhEQU9DO0FBcUJELHNEQU9DO0FBdktEOzs7R0FHRztBQUNILE1BQWEsd0JBQXdCO0lBQ2xCLFFBQVEsQ0FBdUI7SUFDL0IsZ0JBQWdCLENBQWlDO0lBQ2pELE1BQU0sQ0FBVTtJQUNoQixRQUFRLENBQVU7SUFDbEIsS0FBSyxHQUFHLElBQUksR0FBRyxFQUFzQixDQUFDO0lBQ3RDLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBa0QsQ0FBQztJQUN0Rjs7eUJBRXFCO0lBQ2IsYUFBYSxDQUFpQztJQUV0RCxZQUFZLElBQXFDO1FBQy9DLElBQUksQ0FBQyxRQUFRLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQztRQUM5QixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQztRQUN0QyxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsQ0FBQztRQUN0RixJQUFJLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQztJQUNoRSxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNILEtBQUssQ0FBQyxjQUFjLENBQUMsS0FBYTtRQUNoQyxJQUFJLENBQUMsS0FBSztZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsb0VBQW9FLENBQUMsQ0FBQztRQUVsRyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNyQyxJQUFJLE1BQU07WUFBRSxPQUFP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S0FBSyxVQUFVLHlCQUF5QixDQUM3QyxLQUFhLEVBQ2IsUUFBOEI7SUFFOUIsTUFBTSxPQUFPLEdBQUcsSUFBSSx3QkFBd0IsQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7SUFDM0QsTUFBTSxRQUFRLEdBQUcsTUFBTSxPQUFPLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3JELE9BQU8sUUFBUSxFQUFFLENBQUM7QUFDcEIsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrQkc7QUFDSSxLQUFLLFVBQVUscUJBQXFCLENBQ3pDLE9BQWlDLEVBQ2pDLEtBQWEsRUFDYixPQUFnRTtJQUVoRSxNQUFNLFdBQVcsR0FBRyxNQUFNLE9BQU8sQ0FBQyxjQUFjLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDeEQsT0FBTyxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUM7QUFDOUIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFBlci1vcmcgSUFNIHJvbGUgYXNzdW1wdGlvbiBmb3IgYnVpbGQgLyBydW50aW1lIEFXUyBBUEkgY2FsbHMuXG4gKlxuICogVGhlIHNoYXJlZCBwb3N0dXJlIGZvciBBV1MgY2FsbHMgdG9kYXkgaXMgXCJ0aGUgc2VydmljZSdzIElBTSByb2xlLCBmdWxsXG4gKiBibGFzdCByYWRpdXMgYWNyb3NzIGV2ZXJ5IG9yZy5cIiBUaGUgb3BlcmF0b3Itc2lkZSBtaXRpZ2F0aW9uIG9wZXJhdG9yc1xuICogYWN0dWFsbHkgd2FudCBpczogZWFjaCBjdXN0b21lciBvcmcgZ2V0cyBpdHMgb3duIElBTSByb2xlIGluIGl0cyBvd25cbiAqIGFjY291bnQ7IGJ1aWxkL3J1bnRpbWUgQVdTIGNhbGxzIGZvciB0aGF0IG9yZyBgc3RzOkFzc3VtZVJvbGVgIGludG8gdGhlXG4gKiBjdXN0b21lcidzIHJvbGU7IGEgY29tcHJvbWlzZSBvZiBvbmUgb3JnJ3Mgcm9sZSBjYW4ndCBlbnVtZXJhdGUgYW5vdGhlclxuICogb3JnJ3MgUzMgYnVja2V0cyAvIEVDUiByZXBvcy5cbiAqXG4gKiBUaGlzIG1vZHVsZSBpcyB0aGUgY3JlZGVudGlhbC1wcm92aWRlciBwcmltaXRpdmUuIEl0J3MgcGx1Z2dhYmxlIG9uIHRoZVxuICogb3JnLWNvbmZpZyByZXNvbHZlciBzbyB0aGUgc2FtZSBjb2RlIHBhdGggd29ya3Mgd2hldGhlciBjb25maWcgaXMgcmVhZFxuICogZnJvbSBNb25nbywgUG9zdGdyZXMsIG9yIGVudiB2YXJzLiBDYWxsZXJzIHJlY2VpdmUgYSBzdGFuZGFyZCBTREsgdjNcbiAqIGBBd3NDcmVkZW50aWFsSWRlbnRpdHlQcm92aWRlcmAgYW5kIHBhc3MgaXQgdG8gYW55IFNESyBjbGllbnRcbiAqIChgbmV3IENvZGVCdWlsZENsaWVudCh7IGNyZWRlbnRpYWxzIH0pYCwgZXRjLikuXG4gKlxuICogTm8gZ2xvYmFsIHN0YXRlIOKAlCBjYWxsZXJzIGNvbnN0cnVjdCBvbmUgYE9yZ0F3c0NyZWRlbnRpYWxzTWFuYWdlcmAgcGVyXG4gKiBwcm9jZXNzIGFuZCBgYXdhaXQgbWFuYWdlci5nZXRDcmVkZW50aWFscyhvcmdJZClgIGJlZm9yZSBlYWNoIEFXUyBjYWxsLlxuICogVGhlIHJldHVybmVkIHByb3ZpZGVyIGhhbmRsZXMgY3JlZGVudGlhbCByZWZyZXNoIGludGVybmFsbHkgKHRoZVxuICogdW5kZXJseWluZyBgZnJvbVRlbXBvcmFyeUNyZWRlbnRpYWxzYCByZS1jYWxscyBBc3N1bWVSb2xlIH41IG1pbiBiZWZvcmVcbiAqIHRoZSB0ZW1wb3JhcnkgY3JlZGVudGlhbHMgZXhwaXJlKSwgc28gY2FsbCBzaXRlcyBkb24ndCBtYW5hZ2UgVFRMLlxuICpcbiAqIFNhZmV0eSBwcm9wZXJ0aWVzOlxuICogIC0gYGV4dGVybmFsSWRgIGlzIHBsdW1iZWQgdGhyb3VnaCB0byBBc3N1bWVSb2xlLiBPcGVyYXRvcnMgYmFrZSB0aGlzXG4gKiAgICBpbnRvIHRoZSBJQU0gdHJ1c3QgcG9saWN5IGFzIHRoZSBcImNvbmZ1c2VkIGRlcHV0eVwiIG1pdGlnYXRpb247IHRoaXNcbiAqICAgIG1vZHVsZSBuZXZlciBzaWxlbnRseSBvbWl0cyBpdC5cbiAqICAtIE9yZ3Mgd2l0aG91dCBhIGNvbmZpZ3VyZWQgcm9sZSBmYWxsIHRocm91Z2ggdG8gdGhlIHN1cHBsaWVkXG4gKiAgICBgZmFsbGJhY2tgIHByb3ZpZGVyICh0eXBpY2FsbHkgdGhlIFNESyBkZWZhdWx0IGNoYWluKS4gTWl4ZWQtbW9kZVxuICogICAgZGVwbG95bWVudHMgd2hlcmUgc29tZSBvcmdzIGhhdmUgcGVyLW9yZyByb2xlcyBhbmQgb3RoZXJzIHVzZSB0aGVcbiAqICAgIHNoYXJlZCByb2xlIGFyZSBleHBsaWNpdGx5IHN1cHBvcnRlZC5cbiAqICAtIGBldmljdChvcmdJZClgIGRyb3BzIHRoZSBjYWNoZWQgcHJvdmlkZXIgc28gdGhlIG5leHQgY2FsbCByZS1yZXNvbHZlc1xuICogICAgZnJvbSB0aGUgcmVzb2x2ZXIg4oCUIHVzZSBhZnRlciB0aGUgb3BlcmF0b3IgY2hhbmdlcyBhbiBvcmcncyByb2xlLlxuICpcbiAqIEludGVncmF0aW9uIHBhdHRlcm4gZm9yIEFXUyBTREsgY2xpZW50czpcbiAqXG4gKiAgIGBgYHRzXG4gKiAgIGltcG9ydCB7IFMzQ2xpZW50IH0gZnJvbSAnQGF3cy1zZGsvY2xpZW50LXMzJztcbiAqICAgaW1wb3J0IHsgd2l0aE9yZ0F3c0NyZWRlbnRpYWxzIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuICpcbiAqICAgY29uc3QgbWFuYWdlciA9IG5ldyBPcmdBd3NDcmVkZW50aWFsc01hbmFnZXIoeyByZXNvbHZlciB9KTtcbiAqXG4gKiAgIGFzeW5jIGZ1bmN0aW9uIHMzRm9yT3JnKG9yZ0lkOiBzdHJpbmcpIHtcbiAqICAgICByZXR1cm4gd2l0aE9yZ0F3c0NyZWRlbnRpYWxzKG1hbmFnZXIsIG9yZ0lkLCAoY3JlZHMpID0+XG4gKiAgICAgICBuZXcgUzNDbGllbnQoeyBjcmVkZW50aWFsczogY3JlZHMsIHJlZ2lvbjogJ3VzLXdlc3QtMicgfSkpO1xuICogICB9XG4gKiAgIGBgYFxuICpcbiAqIFRvZGF5J3MgY29kZWJhc2U6IG5vIHBsYXRmb3JtLXNpZGUgc2VydmljZSBtYWtlcyBBV1MgQVBJIGNhbGxzIHNjb3BlZCB0b1xuICogYSBjdXN0b21lciBvcmcgKGJ1aWxkIHJ1bm5lcnMgdXNlIGJ1aWxka2l0ZDsgQVdTIExhbWJkYSBoYW5kbGVycyBydW4gaW5cbiAqIGN1c3RvbWVyIHRlcnJpdG9yeSB3aXRoIHRoZWlyIG93biBJQU0pLiBUaGlzIHByaW1pdGl2ZSBpcyBpbiBwbGFjZSBmb3JcbiAqIHdoZW4gdGhlIGFyY2hpdGVjdHVyZSBncm93cyB0byBhZGQgc3VjaCBjYWxsIHNpdGVzIOKAlCBwZXItb3JnIFMzIGJ1Y2tldHMsXG4gKiBwZXItb3JnIEVDUiByZXBvcywgcGVyLW9yZyBDb2RlQnVpbGQgcHJvamVjdHMg4oCUIHdpdGhvdXQgZm9yY2luZyBhblxuICogaW5zZWN1cmUgZGVmYXVsdC5cbiAqL1xuXG4vKiogU3RydWN0dXJhbCBzaGFwZSBvZiBhbiBBV1MgY3JlZGVudGlhbC4gTWF0Y2hlcyBgQHNtaXRoeS90eXBlcyNBd3NDcmVkZW50aWFsSWRlbnRpdHlgXG4gKiAgZXhhY3RseSDigJQgZGVmaW5lZCBsb2NhbGx5IHNvIGFwaS1jb3JlIGRvZXNuJ3QgbmVlZCB0byBkZWNsYXJlIEBzbWl0aHkvdHlwZXNcbiAqICBhcyBhIGRpcmVjdCBkZXAgKGl0J3MgYSB0cmFuc2l0aXZlIG9mIGV2ZXJ5IEFXUyBTREsgY2xpZW50IHdlIHVzZSkuICovXG5leHBvcnQgaW50ZXJmYWNlIEF3c0NyZWRlbnRpYWxJZGVudGl0eSB7XG4gIGFjY2Vzc0tleUlkOiBzdHJpbmc7XG4gIHNlY3JldEFjY2Vzc0tleTogc3RyaW5nO1xuICBzZXNzaW9uVG9rZW4/OiBzdHJpbmc7XG4gIGV4cGlyYXRpb24/OiBEYXRlO1xuICBjcmVkZW50aWFsU2NvcGU/OiBzdHJpbmc7XG4gIGFjY291bnRJZD86IHN0cmluZztcbn1cblxuLyoqIFN0YW5kYXJkIGNyZWRlbnRpYWwtcHJvdmlkZXIgY2FsbGFibGUuIE1hdGNoZXMgYEBzbWl0aHkvdHlwZXMjQXdzQ3JlZGVudGlhbElkZW50aXR5UHJvdmlkZXJgLiAqL1xuZXhwb3J0IHR5cGUgQXdzQ3JlZGVudGlhbElkZW50aXR5UHJvdmlkZXIgPSAoKSA9PiBQcm9taXNlPEF3c0NyZWRlbnRpYWxJZGVudGl0eT47XG5cbi8qKiBPcGVyYXRvci1zdXBwbGllZCBwZXItb3JnIElBTSByb2xlICsgcmVnaW9uIHBpbm5pbmcuICovXG5leHBvcnQgaW50ZXJmYWNlIE9yZ0F3c0NvbmZpZyB7XG4gIC8qKiBBUk4gb2YgdGhlIHJvbGUgdGhpcyBvcmcncyBidWlsZC9ydW50aW1lIGNvZGUgc2hvdWxkIGFzc3VtZS4gKi9cbiAgYXNzdW1lUm9sZUFybjogc3RyaW5nO1xuICAvKiogRXh0ZXJuYWwgaWQgYmFrZWQgaW50byB0aGUgcm9sZSdzIHRydXN0IHBvbGljeSAocmVjb21tZW5kZWQpLiAqL1xuICBleHRlcm5hbElkPzogc3RyaW5nO1xuICAvKiogUmVnaW9uIHRvIHVzZSB3aGVuIHRoZSBjYWxsaW5nIGNvZGUgZG9lc24ndCBwaW4gb25lLiAqL1xuICByZWdpb24/OiBzdHJpbmc7XG4gIC8qKiBBc3N1bWVSb2xlIHNlc3Npb24gZHVyYXRpb24gKHNlY29uZHMpLiBBV1MgYWxsb3dzIDkwMC00MzIwMDsgdGhlXG4gICAqICBlZmZlY3RpdmUgY2VpbGluZyBpcyB0aGUgcm9sZSdzIGBNYXhTZXNzaW9uRHVyYXRpb25gLiBEZWZhdWx0IDM2MDAuICovXG4gIHNlc3Npb25EdXJhdGlvblNlY29uZHM/OiBudW1iZXI7XG4gIC8qKiBTZXNzaW9uIG5hbWUgZW1iZWRkZWQgaW4gQ2xvdWRUcmFpbC4gVXNlZnVsIGZvciBpbmNpZGVudC1yZXNwb25zZVxuICAgKiAgYXR0cmlidXRpb24uIERlZmF1bHQgYHBpcGVsaW5lLWJ1aWxkZXItPG9yZ0lkPmAuICovXG4gIHJvbGVTZXNzaW9uTmFtZT86IHN0cmluZztcbn1cblxuLyoqIEFzeW5jIHJlc29sdmVyOiBvcmdJZCDihpIgY29uZmlnIHwgbnVsbC4gUmV0dXJuaW5nIG51bGwgbWVhbnMgXCJ0aGlzIG9yZ1xuICogIGhhcyBubyBwZXItb3JnIHJvbGU7IHVzZSB0aGUgZmFsbGJhY2sgcHJvdmlkZXIuXCIgKi9cbmV4cG9ydCB0eXBlIE9yZ0F3c0NvbmZpZ1Jlc29sdmVyID0gKG9yZ0lkOiBzdHJpbmcpID0+IFByb21pc2U8T3JnQXdzQ29uZmlnIHwgbnVsbD47XG5cbi8qKiBDb25zdHJ1Y3RvciBvcHRpb25zLiAqL1xuZXhwb3J0IGludGVyZmFjZSBPcmdBd3NDcmVkZW50aWFsc01hbmFnZXJPcHRpb25zIHtcbiAgcmVzb2x2ZXI6IE9yZ0F3c0NvbmZpZ1Jlc29sdmVyO1xuICAvKiogUHJvdmlkZXIgdXNlZCBmb3Igb3JncyB3aG9zZSByZXNvbHZlciByZXR1cm5zIG51bGwuIERlZmF1bHQ6IHRoZSBTREtcbiAgICogIGRlZmF1bHQgY2hhaW4gdmlhIGBAYXdzLXNkay9jcmVkZW50aWFsLXByb3ZpZGVycyNmcm9tTm9kZVByb3ZpZGVyQ2hhaW5gLiAqL1xuICBmYWxsYmFjaz86IEF3c0NyZWRlbnRpYWxJZGVudGl0eVByb3ZpZGVyO1xuICAvKiogUmVnaW9uIHBhc3NlZCB0byB0aGUgaW5uZXIgU1RTIGNsaWVudCB3aGVuIHRoZSBvcmcgY29uZmlnIGRvZXNuJ3RcbiAgICogIHBpbiBvbmUuIERlZmF1bHQ6IGBBV1NfUkVHSU9OYCAvIGBBV1NfREVGQVVMVF9SRUdJT05gIGVudi4gKi9cbiAgcmVnaW9uPzogc3RyaW5nO1xuICAvKiogU1RTIGVuZHBvaW50IG92ZXJyaWRlIChMb2NhbFN0YWNrLCBWUEMgZW5kcG9pbnQpLiBEZWZhdWx0OiBTREsgZGVmYXVsdC4gKi9cbiAgZW5kcG9pbnQ/OiBzdHJpbmc7XG59XG5cbmludGVyZmFjZSBDYWNoZUVudHJ5IHtcbiAgLyoqIFN0YWJsZSBpZGVudGl0eSBvZiB0aGUgY29uZmlnIHdlIGJ1aWx0IHRoaXMgZW50cnkgZnJvbS4gTGV0cyBgZ2V0YFxuICAgKiAgZGV0ZWN0IHRoYXQgdGhlIG9wZXJhdG9yIGNoYW5nZWQgdGhlIGNvbmZpZyBhbmQgd2Ugc2hvdWxkIHJlLWJ1aWxkXG4gICAqICByYXRoZXIgdGhhbiBzZXJ2ZSBhIHN0YWxlIHByb3ZpZGVyLiAqL1xuICBmaW5nZXJwcmludDogc3RyaW5nO1xuICBwcm92aWRlcjogQXdzQ3JlZGVudGlhbElkZW50aXR5UHJvdmlkZXI7XG59XG5cbi8qKlxuICogUGVyLXByb2Nlc3MgbWFuYWdlciB0aGF0IHJlc29sdmVzIGFuZCBjYWNoZXMgcGVyLW9yZyBjcmVkZW50aWFsIHByb3ZpZGVycy5cbiAqIE9uZSBpbnN0YW5jZSBwZXIgc2VydmljZTsgc2hhcmUgaXQgYWNyb3NzIGhhbmRsZXJzLlxuICovXG5leHBvcnQgY2xhc3MgT3JnQXdzQ3JlZGVudGlhbHNNYW5hZ2VyIHtcbiAgcHJpdmF0ZSByZWFkb25seSByZXNvbHZlcjogT3JnQXdzQ29uZmlnUmVzb2x2ZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgZmFsbGJhY2tPdmVycmlkZT86IEF3c0NyZWRlbnRpYWxJZGVudGl0eVByb3ZpZGVyO1xuICBwcml2YXRlIHJlYWRvbmx5IHJlZ2lvbj86IHN0cmluZztcbiAgcHJpdmF0ZSByZWFkb25seSBlbmRwb2ludD86IHN0cmluZztcbiAgcHJpdmF0ZSByZWFkb25seSBjYWNoZSA9IG5ldyBNYXA8c3RyaW5nLCBDYWNoZUVudHJ5PigpO1xuICBwcml2YXRlIHJlYWRvbmx5IGluRmxpZ2h0ID0gbmV3IE1hcDxzdHJpbmcsIFByb21pc2U8QXdzQ3JlZGVudGlhbElkZW50aXR5UHJvdmlkZXI+PigpO1xuICAvKiogTGF6eSBkZWZhdWx0IGNoYWluLiBCdWlsdCBvbmNlIHBlciBtYW5hZ2VyIHNvIHdlIGRvbid0IGltcG9ydCB0aGVcbiAgICogIGNyZWRlbnRpYWwtcHJvdmlkZXJzIFNESyBpbiB0ZXN0IGVudmlyb25tZW50cyB0aGF0IG5ldmVyIHRvdWNoIHRoZVxuICAgKiAgZmFsbGJhY2sgcGF0aC4gKi9cbiAgcHJpdmF0ZSBmYWxsYmFja0NhY2hlPzogQXdzQ3JlZGVudGlhbElkZW50aXR5UHJvdmlkZXI7XG5cbiAgY29uc3RydWN0b3Iob3B0czogT3JnQXdzQ3JlZGVudGlhbHNNYW5hZ2VyT3B0aW9ucykge1xuICAgIHRoaXMucmVzb2x2ZXIgPSBvcHRzLnJlc29sdmVyO1xuICAgIHRoaXMuZmFsbGJhY2tPdmVycmlkZSA9IG9wdHMuZmFsbGJhY2s7XG4gICAgdGhpcy5yZWdpb24gPSBvcHRzLnJlZ2lvbiA/PyBwcm9jZXNzLmVudi5BV1NfUkVHSU9OID8/IHByb2Nlc3MuZW52LkFXU19ERUZBVUxUX1JFR0lPTjtcbiAgICB0aGlzLmVuZHBvaW50ID0gb3B0cy5lbmRwb2ludCA/PyBwcm9jZXNzLmVudi5BV1NfU1RTX0VORFBPSU5UO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybiBhbiBBd3NDcmVkZW50aWFsSWRlbnRpdHlQcm92aWRlciBzY29wZWQgdG8gYG9yZ0lkYC4gUGFzcyB0aGVcbiAgICogcmV0dXJuZWQgdmFsdWUgaW50byBhbnkgU0RLIHYzIGNsaWVudCdzIGBjcmVkZW50aWFsc2Agb3B0aW9uLlxuICAgKlxuICAgKiBDb25jdXJyZW50IGZpcnN0LXRvdWNoIGNhbGxlcnMgc2hhcmUgb25lIHJlc29sdmVyIGludm9jYXRpb247IGxhdGVyXG4gICAqIGNhbGxzIGhpdCB0aGUgY2FjaGUuIFRoZSBwcm92aWRlciBpdHNlbGYgY2FjaGVzIGFuZCByZWZyZXNoZXNcbiAgICogQXNzdW1lUm9sZS1kZXJpdmVkIGNyZWRlbnRpYWxzIGludGVybmFsbHkuXG4gICAqL1xuICBhc3luYyBnZXRDcmVkZW50aWFscyhvcmdJZDogc3RyaW5nKTogUHJvbWlzZTxBd3NDcmVkZW50aWFsSWRlbnRpdHlQcm92aWRlcj4ge1xuICAgIGlmICghb3JnSWQpIHRocm93IG5ldyBFcnJvcignT3JnQXdzQ3JlZGVudGlhbHNNYW5hZ2VyLmdldENyZWRlbnRpYWxzIHJlcXVpcmVzIGEgbm9uLWVtcHR5IG9yZ0lkJyk7XG5cbiAgICBjb25zdCBjYWNoZWQgPSB0aGlzLmNhY2hlLmdldChvcmdJZCk7XG4gICAgaWYgKGNhY2hlZCkgcmV0dXJuIGNhY2hlZC5wcm92aWRlcjtcblxuICAgIGxldCBwZW5kaW5nID0gdGhpcy5pbkZsaWdodC5nZXQob3JnSWQpO1xuICAgIGlmICghcGVuZGluZykge1xuICAgICAgcGVuZGluZyA9IHRoaXMucmVzb2x2ZUFuZEJ1aWxkKG9yZ0lkKTtcbiAgICAgIHRoaXMuaW5GbGlnaHQuc2V0KG9yZ0lkLCBwZW5kaW5nKTtcbiAgICAgIHZvaWQgcGVuZGluZy5maW5hbGx5KCgpID0+IHtcbiAgICAgICAgaWYgKHRoaXMuaW5GbGlnaHQuZ2V0KG9yZ0lkKSA9PT0gcGVuZGluZykgdGhpcy5pbkZsaWdodC5kZWxldGUob3JnSWQpO1xuICAgICAgfSk7XG4gICAgfVxuICAgIHJldHVybiBwZW5kaW5nO1xuICB9XG5cbiAgLyoqIERyb3AgdGhlIGNhY2hlZCBwcm92aWRlciBmb3IgYW4gb3JnLiBDYWxsIGFmdGVyIHRoZSBvcGVyYXRvciByb3RhdGVzXG4gICAqICB0aGUgcm9sZSBBUk4gb3IgZXh0ZXJuYWwgaWQgc28gdGhlIG5leHQgcmVxdWVzdCByZWJ1aWxkcy4gKi9cbiAgZXZpY3Qob3JnSWQ6IHN0cmluZyk6IHZvaWQge1xuICAgIHRoaXMuY2FjaGUuZGVsZXRlKG9yZ0lkKTtcbiAgfVxuXG4gIC8qKiBEcm9wIGV2ZXJ5IGNhY2hlZCBwcm92aWRlci4gVXNlZnVsIGluIHRlc3RzOyByYXJlbHkgaW4gcHJvZHVjdGlvbi4gKi9cbiAgZXZpY3RBbGwoKTogdm9pZCB7XG4gICAgdGhpcy5jYWNoZS5jbGVhcigpO1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyByZXNvbHZlQW5kQnVpbGQob3JnSWQ6IHN0cmluZyk6IFByb21pc2U8QXdzQ3JlZGVudGlhbElkZW50aXR5UHJvdmlkZXI+IHtcbiAgICBjb25zdCBjZmcgPSBhd2FpdCB0aGlzLnJlc29sdmVyKG9yZ0lkKTtcbiAgICBpZiAoIWNmZyB8fCAhY2ZnLmFzc3VtZVJvbGVBcm4pIHtcbiAgICAgIGNvbnN0IGZhbGxiYWNrID0gYXdhaXQgdGhpcy5nZXRGYWxsYmFjaygpO1xuICAgICAgdGhpcy5jYWNoZS5zZXQob3JnSWQsIHsgZmluZ2VycHJpbnQ6ICdmYWxsYmFjaycsIHByb3ZpZGVyOiBmYWxsYmFjayB9KTtcbiAgICAgIHJldHVybiBmYWxsYmFjaztcbiAgICB9XG5cbiAgICBjb25zdCBwcm92aWRlciA9IGF3YWl0IHRoaXMuYnVpbGRBc3N1bWVSb2xlUHJvdmlkZXIob3JnSWQsIGNmZyk7XG4gICAgdGhpcy5jYWNoZS5zZXQob3JnSWQsIHsgZmluZ2VycHJpbnQ6IGZpbmdlcnByaW50Q29uZmlnKGNmZyksIHByb3ZpZGVyIH0pO1xuICAgIHJldHVybiBwcm92aWRlcjtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgZ2V0RmFsbGJhY2soKTogUHJvbWlzZTxBd3NDcmVkZW50aWFsSWRlbnRpdHlQcm92aWRlcj4ge1xuICAgIGlmICh0aGlzLmZhbGxiYWNrT3ZlcnJpZGUpIHJldHVybiB0aGlzLmZhbGxiYWNrT3ZlcnJpZGU7XG4gICAgaWYgKHRoaXMuZmFsbGJhY2tDYWNoZSkgcmV0dXJuIHRoaXMuZmFsbGJhY2tDYWNoZTtcbiAgICAvLyBMYXp5LWltcG9ydCBzbyB0ZXN0IGVudnMgdGhhdCBzdXBwbHkgdGhlaXIgb3duIHJlc29sdmVyL2ZhbGxiYWNrXG4gICAgLy8gZG9uJ3QgbG9hZCB0aGUgY3JlZGVudGlhbC1wcm92aWRlcnMgU0RLLlxuICAgIGNvbnN0IHsgZnJvbU5vZGVQcm92aWRlckNoYWluIH0gPSBhd2FpdCBpbXBvcnQoJ0Bhd3Mtc2RrL2NyZWRlbnRpYWwtcHJvdmlkZXJzJyk7XG4gICAgdGhpcy5mYWxsYmFja0NhY2hlID0gZnJvbU5vZGVQcm92aWRlckNoYWluKCk7XG4gICAgcmV0dXJuIHRoaXMuZmFsbGJhY2tDYWNoZTtcbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgYnVpbGRBc3N1bWVSb2xlUHJvdmlkZXIob3JnSWQ6IHN0cmluZywgY2ZnOiBPcmdBd3NDb25maWcpOiBQcm9taXNlPEF3c0NyZWRlbnRpYWxJZGVudGl0eVByb3ZpZGVyPiB7XG4gICAgLy8gRHluYW1pYyBpbXBvcnRzIOKAlCBTVFMgKyBjcmVkZW50aWFsLXByb3ZpZGVycyBhcmUgaGVhdnl3ZWlnaHQgYW5kXG4gICAgLy8gc2VydmljZXMgdGhhdCBuZXZlciBoYXZlIHBlci1vcmcgcm9sZXMgY29uZmlndXJlZCBzaG91bGRuJ3QgbG9hZCB0aGVtLlxuICAgIGNvbnN0IFt7IFNUU0NsaWVudCB9LCB7IGZyb21UZW1wb3JhcnlDcmVkZW50aWFscyB9XSA9IGF3YWl0IFByb21pc2UuYWxsKFtcbiAgICAgIGltcG9ydCgnQGF3cy1zZGsvY2xpZW50LXN0cycpLFxuICAgICAgaW1wb3J0KCdAYXdzLXNkay9jcmVkZW50aWFsLXByb3ZpZGVycycpLFxuICAgIF0pO1xuXG4gICAgY29uc3QgbWFzdGVyQ3JlZGVudGlhbHMgPSB0aGlzLmZhbGxiYWNrT3ZlcnJpZGUgPz8gKGF3YWl0IHRoaXMuZ2V0RmFsbGJhY2soKSk7XG5cbiAgICBjb25zdCBzZXNzaW9uTmFtZSA9IGNmZy5yb2xlU2Vzc2lvbk5hbWUgPz8gYHBpcGVsaW5lLWJ1aWxkZXItJHtvcmdJZH1gLnNsaWNlKDAsIDY0KTtcbiAgICBjb25zdCByZWdpb24gPSBjZmcucmVnaW9uID8/IHRoaXMucmVnaW9uO1xuXG4gICAgcmV0dXJuIGZyb21UZW1wb3JhcnlDcmVkZW50aWFscyh7XG4gICAgICAvLyBJbm5lciBTVFMgY2xpZW50IHVzZXMgdGhlIHNlcnZpY2UncyBvd24gY3JlZGVudGlhbHMgKHRoZSBmYWxsYmFja1xuICAgICAgLy8gY2hhaW4pLiBUaG9zZSBjcmVkcyBuZWVkIGBzdHM6QXNzdW1lUm9sZWAgb24gdGhlIG9yZydzIHJvbGUuXG4gICAgICBtYXN0ZXJDcmVkZW50aWFscyxcbiAgICAgIGNsaWVudENvbmZpZzoge1xuICAgICAgICByZWdpb24sXG4gICAgICAgIC4uLih0aGlzLmVuZHBvaW50ID8geyBlbmRwb2ludDogdGhpcy5lbmRwb2ludCB9IDoge30pLFxuICAgICAgICAvLyBDYXN0IHRvIHNhdGlzZnkgZnJvbVRlbXBvcmFyeUNyZWRlbnRpYWxzJyB0eXBpbmc7IFNUU0NsaWVudFxuICAgICAgICAvLyBtYXRjaGVzIHRoZSBzdHJ1Y3R1cmFsIGNsaWVudCBzaGFwZSBpdCBleHBlY3RzLlxuICAgICAgfSBhcyB1bmtub3duIGFzIENvbnN0cnVjdG9yUGFyYW1ldGVyczx0eXBlb2YgU1RTQ2xpZW50PlswXSxcbiAgICAgIHBhcmFtczoge1xuICAgICAgICBSb2xlQXJuOiBjZmcuYXNzdW1lUm9sZUFybixcbiAgICAgICAgUm9sZVNlc3Npb25OYW1lOiBzZXNzaW9uTmFtZSxcbiAgICAgICAgRHVyYXRpb25TZWNvbmRzOiBjZmcuc2Vzc2lvbkR1cmF0aW9uU2Vjb25kcyA/PyAzNjAwLFxuICAgICAgICAuLi4oY2ZnLmV4dGVybmFsSWQgPyB7IEV4dGVybmFsSWQ6IGNmZy5leHRlcm5hbElkIH0gOiB7fSksXG4gICAgICB9LFxuICAgIH0pO1xuICB9XG59XG5cbi8qKlxuICogRGV0ZXJtaW5pc3RpYyBmaW5nZXJwcmludCBvZiBhIGNvbmZpZy4gVXNlZCBieSBgZ2V0Q3JlZGVudGlhbHNgIHRvXG4gKiBkZXRlY3QgXCJ0aGUgY2FjaGUgaXMgc3RhbGUgYmVjYXVzZSB0aGUgb3BlcmF0b3IgY2hhbmdlZCB0aGUgY29uZmlnXCIg4oCUXG4gKiBub3QgZXhwb3NlZCBwdWJsaWNseSwganVzdCBkZWZlbnNlIGluIGRlcHRoIGFnYWluc3QgYSBtaXNzZWQgYGV2aWN0YC5cbiAqL1xuZnVuY3Rpb24gZmluZ2VycHJpbnRDb25maWcoY2ZnOiBPcmdBd3NDb25maWcpOiBzdHJpbmcge1xuICByZXR1cm4gW1xuICAgIGNmZy5hc3N1bWVSb2xlQXJuLFxuICAgIGNmZy5leHRlcm5hbElkID8/ICcnLFxuICAgIGNmZy5yZWdpb24gPz8gJycsXG4gICAgY2ZnLnNlc3Npb25EdXJhdGlvblNlY29uZHMgPz8gJycsXG4gICAgY2ZnLnJvbGVTZXNzaW9uTmFtZSA/PyAnJyxcbiAgXS5qb2luKCd8Jyk7XG59XG5cbi8qKiBDb252ZW5pZW5jZTogZGlyZWN0bHkgcmVzb2x2ZSBjcmVkZW50aWFscyBmb3IgYSBzaW5nbGUgY2FsbC4gVGhlIG1hbmFnZXJcbiAqICBkb2Vzbid0IGNhY2hlIHdoZW4gY2FsbGVkIHRoaXMgd2F5IOKAlCB1c2VmdWwgaW4gQ0xJcyAvIG9uZS1zaG90IHNjcmlwdHMuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVzb2x2ZU9yZ0NyZWRlbnRpYWxzT25jZShcbiAgb3JnSWQ6IHN0cmluZyxcbiAgcmVzb2x2ZXI6IE9yZ0F3c0NvbmZpZ1Jlc29sdmVyLFxuKTogUHJvbWlzZTxBd3NDcmVkZW50aWFsSWRlbnRpdHk+IHtcbiAgY29uc3QgbWFuYWdlciA9IG5ldyBPcmdBd3NDcmVkZW50aWFsc01hbmFnZXIoeyByZXNvbHZlciB9KTtcbiAgY29uc3QgcHJvdmlkZXIgPSBhd2FpdCBtYW5hZ2VyLmdldENyZWRlbnRpYWxzKG9yZ0lkKTtcbiAgcmV0dXJuIHByb3ZpZGVyKCk7XG59XG5cbi8qKlxuICogQWRhcHRlcjogcmVzb2x2ZSBwZXItb3JnIGNyZWRlbnRpYWxzIGFuZCBjb25zdHJ1Y3QgYW4gQVdTIFNESyBjbGllbnRcbiAqIHByZS1ib3VuZCB0byB0aGVtLiBUaGUgZmFjdG9yeSBpcyBhc3luYyBiZWNhdXNlIGNyZWRlbnRpYWwgcmVzb2x1dGlvbiBtYXlcbiAqIG5lZWQgdG8gbWFrZSBhIG5ldHdvcmsgY2FsbCAoZmV0Y2hpbmcgb3JnIGNvbmZpZyArIEFzc3VtZVJvbGUpLiBPbmNlXG4gKiByZXNvbHZlZCwgdGhlIHJldHVybmVkIGNsaWVudCBpcyByZWFkeSBmb3Igbm9ybWFsIFNESyBjYWxsczsgdGhlIHByb3ZpZGVyXG4gKiBpbnNpZGUgcmVmcmVzaGVzIGNyZWRlbnRpYWxzIGF1dG9tYXRpY2FsbHkgYmVmb3JlIHRoZXkgZXhwaXJlLlxuICpcbiAqIFR5cGljYWwgdXNhZ2U6XG4gKiAgIGBgYHRzXG4gKiAgIGNvbnN0IHMzID0gYXdhaXQgd2l0aE9yZ0F3c0NyZWRlbnRpYWxzKG1hbmFnZXIsIG9yZ0lkLCAoY3JlZHMpID0+XG4gKiAgICAgbmV3IFMzQ2xpZW50KHsgY3JlZGVudGlhbHM6IGNyZWRzLCByZWdpb246ICd1cy13ZXN0LTInIH0pKTtcbiAqICAgYXdhaXQgczMuc2VuZChuZXcgTGlzdE9iamVjdHNWMkNvbW1hbmQoeyBCdWNrZXQ6IGJ1Y2tldEZvcihvcmdJZCkgfSkpO1xuICogICBgYGBcbiAqXG4gKiBUaGUgZmFjdG9yeSBpcyBpbnZva2VkIGV4YWN0bHkgb25jZSBwZXIgY2FsbCDigJQgY2FsbGVycyB0aGF0IG5lZWQgYVxuICogbG9uZy1saXZlZCBjbGllbnQgc2hvdWxkIGNhY2hlIHRoZSByZXN1bHQgdGhlbXNlbHZlcyByYXRoZXIgdGhhbiByZWJ1aWxkaW5nXG4gKiBvbiBldmVyeSBvcGVyYXRpb24uIChSZS1yZXNvbHZpbmcgb24gZXZlcnkgb3AgaXMgZmluZSBmb3IgY29sZCBwYXRocyBhbmRcbiAqIGEgd2FzdGVkIGNvdXBsZSBvZiBvYmplY3QgYWxsb2NhdGlvbnMgb24gaG90IHBhdGhzLilcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHdpdGhPcmdBd3NDcmVkZW50aWFsczxUQ2xpZW50PihcbiAgbWFuYWdlcjogT3JnQXdzQ3JlZGVudGlhbHNNYW5hZ2VyLFxuICBvcmdJZDogc3RyaW5nLFxuICBmYWN0b3J5OiAoY3JlZGVudGlhbHM6IEF3c0NyZWRlbnRpYWxJZGVudGl0eVByb3ZpZGVyKSA9PiBUQ2xpZW50LFxuKTogUHJvbWlzZTxUQ2xpZW50PiB7XG4gIGNvbnN0IGNyZWRlbnRpYWxzID0gYXdhaXQgbWFuYWdlci5nZXRDcmVkZW50aWFscyhvcmdJZCk7XG4gIHJldHVybiBmYWN0b3J5KGNyZWRlbnRpYWxzKTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/** On-disk shape of an encrypted secret. JSON-serializable. */
|
|
2
|
+
export interface EncryptedBlob {
|
|
3
|
+
/** Algorithm tag. Bump on format change so a future migration can detect old blobs. */
|
|
4
|
+
alg: 'aes-256-gcm-v1';
|
|
5
|
+
/** Base64-encoded 12-byte IV. */
|
|
6
|
+
iv: string;
|
|
7
|
+
/** Base64-encoded ciphertext + 16-byte authentication tag concatenated. */
|
|
8
|
+
ciphertext: string;
|
|
9
|
+
/** Optional key id populated by KMS-backed providers, ignored by the env provider. */
|
|
10
|
+
kid?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Pluggable key source. Env-backed by default; KMS-backed available. */
|
|
13
|
+
export interface KeyProvider {
|
|
14
|
+
/** Derive a 32-byte symmetric key bound to `orgId`. */
|
|
15
|
+
deriveKey(orgId: string): Buffer;
|
|
16
|
+
/** Optional async variant for providers that need to do I/O (e.g.
|
|
17
|
+
* per-org KMS lookup the first time an org is seen). Default
|
|
18
|
+
* implementation forwards to the sync `deriveKey`. */
|
|
19
|
+
deriveKeyAsync?(orgId: string): Promise<Buffer>;
|
|
20
|
+
/** Optional KMS-key-id this provider used for `orgId`. Embedded in the
|
|
21
|
+
* `EncryptedBlob.kid` field on write so decrypt can verify the right
|
|
22
|
+
* KMS CMK is being used (defense against an attacker who swaps a blob
|
|
23
|
+
* between orgs). Default returns undefined (env provider). */
|
|
24
|
+
kidFor?(orgId: string): string | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Default provider derives a per-org key from `SECRET_ENCRYPTION_KEY` via
|
|
28
|
+
* HKDF-SHA256. Suitable for self-hosted / dev where operators don't have KMS.
|
|
29
|
+
*
|
|
30
|
+
* Fails fast if the env is missing or the key is the wrong length so misconfig
|
|
31
|
+
* surfaces at first use rather than silently fingerprinting all writes with
|
|
32
|
+
* a default zero key.
|
|
33
|
+
*/
|
|
34
|
+
export declare class EnvKeyProvider implements KeyProvider {
|
|
35
|
+
private readonly masterKey;
|
|
36
|
+
constructor(masterKeyOverride?: string);
|
|
37
|
+
deriveKey(orgId: string): Buffer;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* AWS-KMS-backed KeyProvider.
|
|
41
|
+
*
|
|
42
|
+
* Trade-off picked: store ONE master key encrypted under a KMS CMK; on
|
|
43
|
+
* first use, call `kms:Decrypt` to recover the master key bytes; HKDF-
|
|
44
|
+
* derive per-org from it (same as EnvKeyProvider). The process then holds
|
|
45
|
+
* the plaintext master key in memory until restart.
|
|
46
|
+
*
|
|
47
|
+
* PROS: one KMS call per process lifetime (cheap, low p99 impact),
|
|
48
|
+
* the encrypted-master-key blob is safe to commit/log/checkin,
|
|
49
|
+
* KMS audit log records when the master is recovered.
|
|
50
|
+
* CONS: process memory still holds the master key same posture as
|
|
51
|
+
* EnvKeyProvider once warmed up. For stronger isolation an
|
|
52
|
+
* operator can move to per-record envelope encryption (call
|
|
53
|
+
* GenerateDataKey on every write); that's a follow-on.
|
|
54
|
+
*
|
|
55
|
+
* Operator setup * 1. Create a KMS CMK with key policy allowing the platform service's
|
|
56
|
+
* IAM role kms:Decrypt.
|
|
57
|
+
* 2. Generate a random 32-byte master * head -c 32 /dev/urandom | base64
|
|
58
|
+
* 3. Wrap it with KMS * aws kms encrypt --key-id <KEY_ID> \
|
|
59
|
+
* --plaintext <base64-from-step-2> --output text \
|
|
60
|
+
* --query CiphertextBlob
|
|
61
|
+
* 4. Set on the service * SECRET_ENCRYPTION_KMS_KEY_ID=<KEY_ID>
|
|
62
|
+
* SECRET_ENCRYPTION_KMS_CIPHERTEXT=<base64-output-of-step-3>
|
|
63
|
+
* 5. Pick this provider via `setKeyProvider(new KmsKeyProvider())`.
|
|
64
|
+
*
|
|
65
|
+
* Construct lazily importing the AWS SDK has a non-trivial cold-start
|
|
66
|
+
* cost so envs that stay on EnvKeyProvider never load it.
|
|
67
|
+
*/
|
|
68
|
+
export declare class KmsKeyProvider implements KeyProvider {
|
|
69
|
+
private masterKeyCache;
|
|
70
|
+
private readonly keyId;
|
|
71
|
+
private readonly ciphertextB64;
|
|
72
|
+
private readonly region?;
|
|
73
|
+
private readonly endpoint?;
|
|
74
|
+
private decryptInFlight;
|
|
75
|
+
constructor(opts?: {
|
|
76
|
+
keyId?: string;
|
|
77
|
+
ciphertextBase64?: string;
|
|
78
|
+
region?: string;
|
|
79
|
+
endpoint?: string;
|
|
80
|
+
});
|
|
81
|
+
deriveKey(orgId: string): Buffer;
|
|
82
|
+
/**
|
|
83
|
+
* Eagerly recover the master key from KMS so subsequent `deriveKey`
|
|
84
|
+
* calls are sync. Idempotent concurrent callers share the same in-
|
|
85
|
+
* flight promise so we don't spawn multiple KMS Decrypt requests at
|
|
86
|
+
* boot. Throws on any KMS failure; the caller (typically the service's
|
|
87
|
+
* onBeforeStart hook) decides whether to fall back to a different
|
|
88
|
+
* provider or fail-startup.
|
|
89
|
+
*/
|
|
90
|
+
warmup(): Promise<void>;
|
|
91
|
+
private fetchAndDecrypt;
|
|
92
|
+
}
|
|
93
|
+
/** Read the active default provider. Exposed so service code (e.g. an
|
|
94
|
+
* admin endpoint that just rotated an org's KMS config) can call
|
|
95
|
+
* `provider.evict(orgId)` on the live provider rather than reconstructing
|
|
96
|
+
* one. Triggers lazy initialization on first access, same as the internal
|
|
97
|
+
* callers below. */
|
|
98
|
+
export declare function getDefaultKeyProvider(): KeyProvider;
|
|
99
|
+
/** Reset the cached default provider for tests that mutate `process.env`. */
|
|
100
|
+
export declare function resetDefaultKeyProvider(): void;
|
|
101
|
+
/** Replace the default provider services that opt into KMS call this
|
|
102
|
+
* once at startup with a warmed-up `KmsKeyProvider`. */
|
|
103
|
+
export declare function setKeyProvider(provider: KeyProvider): void;
|
|
104
|
+
/** Per-org KMS config the operator supplies. The `keyId` identifies the
|
|
105
|
+
* KMS CMK to call Decrypt on; `ciphertextBase64` is the wrapped 32-byte
|
|
106
|
+
* master generated by `aws kms encrypt` against that key. */
|
|
107
|
+
export interface PerOrgKmsConfig {
|
|
108
|
+
keyId: string;
|
|
109
|
+
ciphertextBase64: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Async resolver that maps an org id to its KMS config. Returns `null` when
|
|
113
|
+
* the org has no per-org config — the provider then falls back to the
|
|
114
|
+
* `fallback` provider supplied at construction (usually an EnvKeyProvider
|
|
115
|
+
* or a default KmsKeyProvider).
|
|
116
|
+
*/
|
|
117
|
+
export type PerOrgKmsResolver = (orgId: string) => Promise<PerOrgKmsConfig | null>;
|
|
118
|
+
/**
|
|
119
|
+
* Per-org KMS-backed KeyProvider. The blast radius of a KMS key compromise
|
|
120
|
+
* is one org instead of every org under a shared master.
|
|
121
|
+
*
|
|
122
|
+
* Each org has its own KMS CMK + its own wrapped master (stored in Mongo
|
|
123
|
+
* via the operator's setup script). On first encrypt/decrypt for an org,
|
|
124
|
+
* the provider:
|
|
125
|
+
* 1. Calls the resolver to fetch the org's KMS config.
|
|
126
|
+
* 2. Calls `kms:Decrypt` to recover the 32-byte master.
|
|
127
|
+
* 3. Caches the recovered master in-memory for the process lifetime.
|
|
128
|
+
* 4. HKDF-derives per-call from that master + the org id salt.
|
|
129
|
+
*
|
|
130
|
+
* Blobs encrypted by this provider carry `kid = <kms-key-id>` so the
|
|
131
|
+
* decrypt path detects a stale config (operator rotated the key but
|
|
132
|
+
* existing rows weren't re-encrypted) BEFORE AES-GCM throws an opaque
|
|
133
|
+
* authentication-tag error.
|
|
134
|
+
*
|
|
135
|
+
* Orgs without per-org config fall through to the `fallback` provider —
|
|
136
|
+
* mixed-mode deployments where some orgs have KMS isolation and others
|
|
137
|
+
* stay on the shared master are explicitly supported.
|
|
138
|
+
*/
|
|
139
|
+
export declare class PerOrgKmsKeyProvider implements KeyProvider {
|
|
140
|
+
/** Cached per-org master keys, keyed by orgId. */
|
|
141
|
+
private readonly masters;
|
|
142
|
+
/** Resolved per-org configs cached for the process lifetime. */
|
|
143
|
+
private readonly configs;
|
|
144
|
+
/** In-flight resolver promises so concurrent first-touch callers share one KMS Decrypt.
|
|
145
|
+
* Resolves to `null` for orgs with no per-org config — caller treats null as
|
|
146
|
+
* "fall through to the fallback provider", same as a cold cache miss. */
|
|
147
|
+
private readonly inFlight;
|
|
148
|
+
/** Provider used for orgs that have no per-org config. */
|
|
149
|
+
private readonly fallback;
|
|
150
|
+
private readonly resolver;
|
|
151
|
+
private readonly region?;
|
|
152
|
+
private readonly endpoint?;
|
|
153
|
+
constructor(opts: {
|
|
154
|
+
resolver: PerOrgKmsResolver;
|
|
155
|
+
fallback: KeyProvider;
|
|
156
|
+
region?: string;
|
|
157
|
+
endpoint?: string;
|
|
158
|
+
});
|
|
159
|
+
/** Sync `deriveKey` only works for already-warmed orgs. Cold orgs fall
|
|
160
|
+
* through to the fallback provider. Callers that want per-org isolation
|
|
161
|
+
* MUST `await deriveKeyAsync(orgId)` once first (e.g. during request setup
|
|
162
|
+
* or as part of a warmup pass) — otherwise the org silently uses the
|
|
163
|
+
* shared master. */
|
|
164
|
+
deriveKey(orgId: string): Buffer;
|
|
165
|
+
deriveKeyAsync(orgId: string): Promise<Buffer>;
|
|
166
|
+
kidFor(orgId: string): string | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Resolve the org's KMS config, call Decrypt, cache the master. Subsequent
|
|
169
|
+
* calls for the same org are no-ops. Concurrent callers share the in-flight
|
|
170
|
+
* Decrypt promise.
|
|
171
|
+
*
|
|
172
|
+
* Returns silently when the org has no per-org config — the fallback
|
|
173
|
+
* provider handles those orgs.
|
|
174
|
+
*/
|
|
175
|
+
ensureWarmed(orgId: string): Promise<void>;
|
|
176
|
+
private resolveAndDecrypt;
|
|
177
|
+
private fetchAndDecrypt;
|
|
178
|
+
/** Evict a cached per-org master. Use after a key rotation so the next
|
|
179
|
+
* touch re-fetches the new wrapped master from the resolver. */
|
|
180
|
+
evict(orgId: string): void;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Encrypt a plaintext string for storage. Returns an `EncryptedBlob` that
|
|
184
|
+
* can be JSON-serialized into the underlying column / Mongo document.
|
|
185
|
+
*
|
|
186
|
+
* Empty strings round-trip as `null` so the calling model layer can treat
|
|
187
|
+
* "no secret set" identically to "field absent".
|
|
188
|
+
*/
|
|
189
|
+
export declare function encryptSecret(plaintext: string, orgId: string, provider?: KeyProvider): EncryptedBlob;
|
|
190
|
+
/**
|
|
191
|
+
* Decrypt an `EncryptedBlob` written by `encryptSecret`. Throws on * - unknown `alg` (forces an explicit migration when the format changes)
|
|
192
|
+
* - wrong orgId (HKDF binding mismatch fails the auth tag)
|
|
193
|
+
* - tampered ciphertext (GCM auth tag check fails)
|
|
194
|
+
*
|
|
195
|
+
* Callers handle the throw masking the failure as `null` would hide
|
|
196
|
+
* silent corruption / wrong-org reads.
|
|
197
|
+
*/
|
|
198
|
+
export declare function decryptSecret(blob: EncryptedBlob, orgId: string, provider?: KeyProvider): string;
|
|
199
|
+
/**
|
|
200
|
+
* Type guard handy for model layers that hold a column whose value may be
|
|
201
|
+
* either a clear-text string (legacy / unencrypted) OR an encrypted blob
|
|
202
|
+
* (post-migration). Mixed states arise mid-migration; the model decides
|
|
203
|
+
* what to do (decrypt on read, encrypt on next write).
|
|
204
|
+
*/
|
|
205
|
+
export declare function isEncryptedBlob(value: unknown): value is EncryptedBlob;
|