@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.
@@ -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;