@dbx-tools/cli-tunnel 0.6.59 → 0.6.85
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 -364
- package/index.ts +2 -19
- package/lib/index.d.ts +2 -19
- package/lib/index.js +2 -15
- package/lib/src/app.d.ts +12 -123
- package/lib/src/app.js +22 -250
- package/lib/src/cli.d.ts +19 -22
- package/lib/src/cli.js +143 -128
- package/lib/src/options.d.ts +46 -0
- package/lib/src/options.js +51 -0
- package/lib/src/proxy.d.ts +27 -37
- package/lib/src/proxy.js +115 -220
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -81
- package/src/app.ts +20 -282
- package/src/cli.ts +152 -162
- package/src/options.ts +85 -0
- package/src/proxy.ts +139 -261
- package/bin/dbx-tools-tunnel.ts +0 -13
- package/lib/bin/dbx-tools-tunnel.d.ts +0 -2
- package/lib/bin/dbx-tools-tunnel.js +0 -14
- package/lib/src/allowlist.d.ts +0 -32
- package/lib/src/allowlist.js +0 -57
- package/lib/src/env.d.ts +0 -57
- package/lib/src/env.js +0 -60
- package/lib/src/headers.d.ts +0 -108
- package/lib/src/headers.js +0 -140
- package/lib/src/otp.d.ts +0 -49
- package/lib/src/otp.js +0 -124
- package/lib/src/plugin.d.ts +0 -147
- package/lib/src/plugin.js +0 -138
- package/lib/src/portr.d.ts +0 -40
- package/lib/src/portr.js +0 -93
- package/lib/src/rate-limit.d.ts +0 -35
- package/lib/src/rate-limit.js +0 -53
- package/lib/src/signing-key.d.ts +0 -86
- package/lib/src/signing-key.js +0 -170
- package/src/allowlist.ts +0 -60
- package/src/env.ts +0 -72
- package/src/headers.ts +0 -155
- package/src/otp.ts +0 -137
- package/src/plugin.ts +0 -269
- package/src/portr.ts +0 -113
- package/src/rate-limit.ts +0 -59
- package/src/signing-key.ts +0 -201
package/lib/src/plugin.d.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `authGate()` - the AppKit plugin behind the tunnel's email-OTP gate.
|
|
3
|
-
*
|
|
4
|
-
* It has NO routes of its own: the tunnel PROXY (not an HTTP server) calls the
|
|
5
|
-
* handlers this plugin exposes via {@link AuthGatePlugin.exports}. The plugin
|
|
6
|
-
* owns the allow-list, the per-email/per-IP rate limiters, the CacheManager-backed
|
|
7
|
-
* one-time-code store, and the session JWT. `createApp` (with no `server()`) is
|
|
8
|
-
* used only to auto-init `CacheManager` + prime the sibling `email` transport;
|
|
9
|
-
* this plugin is where the gate logic lives.
|
|
10
|
-
*
|
|
11
|
-
* Options come from CLI flags OR env, with sensible defaults - see
|
|
12
|
-
* {@link resolveAuthGateConfig}. The one runtime dependency the plugin can't
|
|
13
|
-
* resolve itself is HOW to email the code, so it takes a `sendCode` callback the
|
|
14
|
-
* app wires to the email plugin.
|
|
15
|
-
*
|
|
16
|
-
* @module
|
|
17
|
-
*/
|
|
18
|
-
import { Plugin, type BasePluginConfig } from "@databricks/appkit";
|
|
19
|
-
import type { AuthStatus } from "@dbx-tools/shared-email";
|
|
20
|
-
/** Options for the {@link authGate} plugin (all resolvable from env - see below). */
|
|
21
|
-
export interface AuthGateConfig extends BasePluginConfig {
|
|
22
|
-
/** Allow-list patterns (domain / glob / `/regex/`). Empty = allow nobody. Env TUNNEL_AUTH_ALLOW. */
|
|
23
|
-
allow?: string | string[];
|
|
24
|
-
/**
|
|
25
|
-
* Subject line for the code email. Env TUNNEL_AUTH_SUBJECT.
|
|
26
|
-
*
|
|
27
|
-
* Defaults to "Your verification code". The wording of the subject and
|
|
28
|
-
* {@link message} is deliberately the conventional phrasing rather than
|
|
29
|
-
* anything branded: iOS, Gmail, Outlook, and Android all detect a one-time
|
|
30
|
-
* code from this shape and offer to autofill it, and a novel phrasing is what
|
|
31
|
-
* breaks that detection.
|
|
32
|
-
*
|
|
33
|
-
* This is the subject TEMPLATE, not the literal line sent: the code is spliced
|
|
34
|
-
* into it (`"123456 is your verification code"`) because a push notification
|
|
35
|
-
* shows only the subject and preheader, and that notification is what mobile
|
|
36
|
-
* autofill reads. See `codeEmailSubject` in `./app.ts`.
|
|
37
|
-
*/
|
|
38
|
-
subject?: string;
|
|
39
|
-
/**
|
|
40
|
-
* Display name used in the code email copy. Env TUNNEL_AUTH_BRAND_NAME.
|
|
41
|
-
*
|
|
42
|
-
* Defaults to the brand context's `name` - the app's own `branding/brand.yaml`
|
|
43
|
-
* when it has one, else the dbx-tools default. Set this only to override the
|
|
44
|
-
* brand for this gate.
|
|
45
|
-
*/
|
|
46
|
-
brandName?: string;
|
|
47
|
-
/**
|
|
48
|
-
* Line shown immediately above the code in the email. Env TUNNEL_AUTH_MESSAGE.
|
|
49
|
-
*
|
|
50
|
-
* Keep the code on its OWN line directly after this text - that adjacency is
|
|
51
|
-
* what the platform code-detection heuristics key on.
|
|
52
|
-
*/
|
|
53
|
-
message?: string;
|
|
54
|
-
/**
|
|
55
|
-
* Session lifetime (seconds). Env TUNNEL_AUTH_SESSION_TTL. Default 2592000 (30d).
|
|
56
|
-
*
|
|
57
|
-
* Matched to the cache-backed signing key's own 30-day TTL (see
|
|
58
|
-
* `./signing-key.ts`): the cookie and the key that validates it should expire
|
|
59
|
-
* together, or one silently outlives the other.
|
|
60
|
-
*/
|
|
61
|
-
sessionTtlSeconds?: number;
|
|
62
|
-
/** One-time-code lifetime (seconds). Env TUNNEL_AUTH_CODE_TTL. Default 600 (10m). */
|
|
63
|
-
codeTtlSeconds?: number;
|
|
64
|
-
/** Max verify attempts per issued code. Default 5. */
|
|
65
|
-
maxAttempts?: number;
|
|
66
|
-
/**
|
|
67
|
-
* Force-clear cutoff: every session issued BEFORE it stops verifying, so moving
|
|
68
|
-
* it forward signs everyone out. Env TUNNEL_AUTH_SESSION_CUTOFF.
|
|
69
|
-
*
|
|
70
|
-
* Anything `object.toDate` accepts: a `Date`, `2026-08-02`, an ISO instant,
|
|
71
|
-
* epoch seconds/millis, or a relative duration (`-30d`, `7 days ago`). Unset
|
|
72
|
-
* means no cutoff.
|
|
73
|
-
*/
|
|
74
|
-
sessionCutoff?: string | number | Date;
|
|
75
|
-
/** Deliver a code to an address. Wired by the app to the email plugin. */
|
|
76
|
-
sendCode?: (email: string, code: string, opts: SendCodeOptions) => Promise<void>;
|
|
77
|
-
}
|
|
78
|
-
/** Branding/messaging passed to {@link AuthGateConfig.sendCode}. */
|
|
79
|
-
export interface SendCodeOptions {
|
|
80
|
-
subject: string;
|
|
81
|
-
brandName: string;
|
|
82
|
-
message: string;
|
|
83
|
-
/**
|
|
84
|
-
* Lifetime of the code being sent, in seconds - the RESOLVED
|
|
85
|
-
* {@link AuthGateConfig.codeTtlSeconds}, so the email can state the real
|
|
86
|
-
* expiry ("This code expires in 10 minutes") instead of a vague "shortly"
|
|
87
|
-
* that drifts from the configured TTL.
|
|
88
|
-
*/
|
|
89
|
-
codeTtlSeconds: number;
|
|
90
|
-
}
|
|
91
|
-
/** Resolved gate config with env fallbacks + defaults applied. */
|
|
92
|
-
export interface ResolvedAuthGateConfig {
|
|
93
|
-
allow: string[];
|
|
94
|
-
subject: string;
|
|
95
|
-
brandName: string;
|
|
96
|
-
message: string;
|
|
97
|
-
sessionTtlSeconds: number;
|
|
98
|
-
codeTtlSeconds: number;
|
|
99
|
-
maxAttempts: number;
|
|
100
|
-
/** Force-clear cutoff in epoch ms; `0` when unset. */
|
|
101
|
-
sessionCutoffMs: number;
|
|
102
|
-
}
|
|
103
|
-
/** Merge {@link AuthGateConfig} over env over defaults into a resolved config. */
|
|
104
|
-
export declare function resolveAuthGateConfig(config: AuthGateConfig): ResolvedAuthGateConfig;
|
|
105
|
-
/** The handlers the proxy calls in-process (returned by {@link AuthGatePlugin.exports}). */
|
|
106
|
-
export interface AuthGateApi {
|
|
107
|
-
/** Handle a code request. Always resolves `{ ok: true }` (anti-enumeration). */
|
|
108
|
-
request(email: string, ip: string): Promise<{
|
|
109
|
-
ok: true;
|
|
110
|
-
retryAfter?: number;
|
|
111
|
-
}>;
|
|
112
|
-
/** Handle a code verification. On success returns the session token to cookie. */
|
|
113
|
-
verify(email: string, code: string, ip: string): Promise<{
|
|
114
|
-
ok: boolean;
|
|
115
|
-
token?: string;
|
|
116
|
-
retryAfter?: number;
|
|
117
|
-
}>;
|
|
118
|
-
/** Resolve the authenticated email for a session token, or undefined. */
|
|
119
|
-
session(token: string | undefined): Promise<string | undefined>;
|
|
120
|
-
/** Session TTL in seconds (for the cookie Max-Age). */
|
|
121
|
-
readonly sessionTtlSeconds: number;
|
|
122
|
-
/** The gate status payload (`enabled` is always true when this plugin runs). */
|
|
123
|
-
status(token: string | undefined): Promise<AuthStatus>;
|
|
124
|
-
}
|
|
125
|
-
/** AppKit plugin owning the email-OTP gate's logic (no HTTP routes; proxy-driven). */
|
|
126
|
-
export declare class AuthGatePlugin extends Plugin<AuthGateConfig> {
|
|
127
|
-
static manifest: {
|
|
128
|
-
name: "authGate";
|
|
129
|
-
displayName: string;
|
|
130
|
-
description: string;
|
|
131
|
-
stability: "beta";
|
|
132
|
-
resources: {
|
|
133
|
-
required: never[];
|
|
134
|
-
optional: never[];
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
private resolved;
|
|
138
|
-
private codes;
|
|
139
|
-
private readonly requestLimiter;
|
|
140
|
-
private readonly verifyLimiter;
|
|
141
|
-
setup(): Promise<void>;
|
|
142
|
-
exports(): AuthGateApi;
|
|
143
|
-
private handleRequest;
|
|
144
|
-
private handleVerify;
|
|
145
|
-
}
|
|
146
|
-
/** Factory: `authGate({ allow, subject, ... })` for an AppKit `plugins` array. */
|
|
147
|
-
export declare const authGate: import("@databricks/appkit").ToPlugin<typeof AuthGatePlugin, AuthGateConfig, "authGate">;
|
package/lib/src/plugin.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `authGate()` - the AppKit plugin behind the tunnel's email-OTP gate.
|
|
3
|
-
*
|
|
4
|
-
* It has NO routes of its own: the tunnel PROXY (not an HTTP server) calls the
|
|
5
|
-
* handlers this plugin exposes via {@link AuthGatePlugin.exports}. The plugin
|
|
6
|
-
* owns the allow-list, the per-email/per-IP rate limiters, the CacheManager-backed
|
|
7
|
-
* one-time-code store, and the session JWT. `createApp` (with no `server()`) is
|
|
8
|
-
* used only to auto-init `CacheManager` + prime the sibling `email` transport;
|
|
9
|
-
* this plugin is where the gate logic lives.
|
|
10
|
-
*
|
|
11
|
-
* Options come from CLI flags OR env, with sensible defaults - see
|
|
12
|
-
* {@link resolveAuthGateConfig}. The one runtime dependency the plugin can't
|
|
13
|
-
* resolve itself is HOW to email the code, so it takes a `sendCode` callback the
|
|
14
|
-
* app wires to the email plugin.
|
|
15
|
-
*
|
|
16
|
-
* @module
|
|
17
|
-
*/
|
|
18
|
-
import { Plugin, toPlugin } from "@databricks/appkit";
|
|
19
|
-
import { brand, env, log, object, string } from "@dbx-tools/shared-core";
|
|
20
|
-
import { looksLikeEmail, matchesAllowlist } from "./allowlist.js";
|
|
21
|
-
import { ALLOW_ENV, BRAND_NAME_ENV, CODE_TTL_ENV, MESSAGE_ENV, SESSION_TTL_ENV, SUBJECT_ENV, } from "./env.js";
|
|
22
|
-
import { CodeStore, signSession, verifySession } from "./otp.js";
|
|
23
|
-
import { RateLimiter } from "./rate-limit.js";
|
|
24
|
-
import { KEY_TTL_SECONDS, resolveSessionCutoff, signingKey } from "./signing-key.js";
|
|
25
|
-
const logger = log.logger("tunnel:auth");
|
|
26
|
-
const DEFAULTS = {
|
|
27
|
-
subject: "Your verification code",
|
|
28
|
-
// The repo-wide brand context's display name, NOT a hardcoded product string:
|
|
29
|
-
// this name is what the recipient reads in the code email, so it has to be the
|
|
30
|
-
// same identity the rest of the app presents. A host with its own
|
|
31
|
-
// `branding/brand.yaml` overrides it by passing `brandName` (see
|
|
32
|
-
// {@link startGateApp}, which resolves the on-disk context); the shared default
|
|
33
|
-
// is the fallback when nothing is configured.
|
|
34
|
-
brandName: brand.defaultBrandContext.name,
|
|
35
|
-
message: "Your verification code is:",
|
|
36
|
-
// 30 days, the same window the cache-backed signing key is stored for.
|
|
37
|
-
sessionTtlSeconds: KEY_TTL_SECONDS,
|
|
38
|
-
codeTtlSeconds: 600,
|
|
39
|
-
maxAttempts: 5,
|
|
40
|
-
};
|
|
41
|
-
/** Merge {@link AuthGateConfig} over env over defaults into a resolved config. */
|
|
42
|
-
export function resolveAuthGateConfig(config) {
|
|
43
|
-
return {
|
|
44
|
-
// Both sources are unioned rather than one overriding: a deployment-wide
|
|
45
|
-
// TUNNEL_AUTH_ALLOW and a per-invocation `--allow` should both grant access.
|
|
46
|
-
// Hence `parseList` on each rather than `env.list`, which stops at the first
|
|
47
|
-
// source that yields anything.
|
|
48
|
-
allow: [...string.parseList(config.allow), ...string.parseList(env.text(ALLOW_ENV))],
|
|
49
|
-
subject: env.string(config.subject, SUBJECT_ENV) ?? DEFAULTS.subject,
|
|
50
|
-
brandName: env.string(config.brandName, BRAND_NAME_ENV) ?? DEFAULTS.brandName,
|
|
51
|
-
message: env.string(config.message, MESSAGE_ENV) ?? DEFAULTS.message,
|
|
52
|
-
sessionTtlSeconds: env.positiveInt(config.sessionTtlSeconds, SESSION_TTL_ENV, DEFAULTS.sessionTtlSeconds),
|
|
53
|
-
codeTtlSeconds: env.positiveInt(config.codeTtlSeconds, CODE_TTL_ENV, DEFAULTS.codeTtlSeconds),
|
|
54
|
-
maxAttempts: config.maxAttempts ?? DEFAULTS.maxAttempts,
|
|
55
|
-
sessionCutoffMs: resolveSessionCutoff(config.sessionCutoff),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/** AppKit plugin owning the email-OTP gate's logic (no HTTP routes; proxy-driven). */
|
|
59
|
-
export class AuthGatePlugin extends Plugin {
|
|
60
|
-
static manifest = {
|
|
61
|
-
name: "authGate",
|
|
62
|
-
displayName: "Auth Gate",
|
|
63
|
-
description: "Email one-time-password access gate for a public tunnel.",
|
|
64
|
-
stability: "beta",
|
|
65
|
-
resources: { required: [], optional: [] },
|
|
66
|
-
};
|
|
67
|
-
resolved;
|
|
68
|
-
codes;
|
|
69
|
-
// Requesting a code is email-spam-prone; verifying is a brute-force surface.
|
|
70
|
-
// Per-email AND per-IP so neither axis alone is a bypass.
|
|
71
|
-
requestLimiter = new RateLimiter(5, 15 * 60 * 1000);
|
|
72
|
-
verifyLimiter = new RateLimiter(10, 15 * 60 * 1000);
|
|
73
|
-
async setup() {
|
|
74
|
-
this.resolved = resolveAuthGateConfig(this.config);
|
|
75
|
-
this.codes = new CodeStore(this.resolved.codeTtlSeconds, this.resolved.maxAttempts);
|
|
76
|
-
// Resolve the signing key HERE rather than lazily on the first sign-in, so a
|
|
77
|
-
// cache that cannot hold it (and the resulting "sessions won't survive a
|
|
78
|
-
// restart" warning) shows up in the startup log, not hours later.
|
|
79
|
-
const { cutoffMs } = await signingKey(this.resolved.sessionCutoffMs);
|
|
80
|
-
logger.info("ready", {
|
|
81
|
-
patterns: this.resolved.allow.length,
|
|
82
|
-
sessionTtlSeconds: this.resolved.sessionTtlSeconds,
|
|
83
|
-
...object.optional("sessionCutoff", cutoffMs > 0 ? new Date(cutoffMs).toISOString() : null),
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
exports() {
|
|
87
|
-
return {
|
|
88
|
-
sessionTtlSeconds: this.resolved.sessionTtlSeconds,
|
|
89
|
-
request: (email, ip) => this.handleRequest(email, ip),
|
|
90
|
-
verify: (email, code, ip) => this.handleVerify(email, code, ip),
|
|
91
|
-
session: (token) => verifySession(token),
|
|
92
|
-
status: async (token) => ({
|
|
93
|
-
authenticated: Boolean(await verifySession(token)),
|
|
94
|
-
email: (await verifySession(token)) ?? undefined,
|
|
95
|
-
enabled: true,
|
|
96
|
-
}),
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
async handleRequest(email, ip) {
|
|
100
|
-
const address = email.trim().toLowerCase();
|
|
101
|
-
const byIp = this.requestLimiter.hit(`ip:${ip}`);
|
|
102
|
-
const byEmail = this.requestLimiter.hit(`email:${address}`);
|
|
103
|
-
if (!byIp.allowed || !byEmail.allowed) {
|
|
104
|
-
return { ok: true, retryAfter: byIp.retryAfter ?? byEmail.retryAfter };
|
|
105
|
-
}
|
|
106
|
-
if (looksLikeEmail(address) && matchesAllowlist(address, this.resolved.allow)) {
|
|
107
|
-
const code = await this.codes.issue(address);
|
|
108
|
-
try {
|
|
109
|
-
await this.config.sendCode?.(address, code, {
|
|
110
|
-
subject: this.resolved.subject,
|
|
111
|
-
brandName: this.resolved.brandName,
|
|
112
|
-
message: this.resolved.message,
|
|
113
|
-
codeTtlSeconds: this.resolved.codeTtlSeconds,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
logger.warn("failed to send OTP email", { error });
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return { ok: true };
|
|
121
|
-
}
|
|
122
|
-
async handleVerify(email, code, ip) {
|
|
123
|
-
const address = email.trim().toLowerCase();
|
|
124
|
-
const byIp = this.verifyLimiter.hit(`ip:${ip}`);
|
|
125
|
-
const byEmail = this.verifyLimiter.hit(`email:${address}`);
|
|
126
|
-
if (!byIp.allowed || !byEmail.allowed) {
|
|
127
|
-
return { ok: false, retryAfter: byIp.retryAfter ?? byEmail.retryAfter };
|
|
128
|
-
}
|
|
129
|
-
if ((await this.codes.verify(address, code.trim())) !== "ok")
|
|
130
|
-
return { ok: false };
|
|
131
|
-
this.requestLimiter.reset(`email:${address}`);
|
|
132
|
-
this.verifyLimiter.reset(`email:${address}`);
|
|
133
|
-
return { ok: true, token: await signSession(address, this.resolved.sessionTtlSeconds) };
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/** Factory: `authGate({ allow, subject, ... })` for an AppKit `plugins` array. */
|
|
137
|
-
export const authGate = toPlugin(AuthGatePlugin);
|
|
138
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3BsdWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7OztHQWdCRztBQUVILE9BQU8sRUFBRSxNQUFNLEVBQUUsUUFBUSxFQUE4QyxNQUFNLG9CQUFvQixDQUFDO0FBQ2xHLE9BQU8sRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFekUsT0FBTyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ2xFLE9BQU8sRUFDTCxTQUFTLEVBQ1QsY0FBYyxFQUNkLFlBQVksRUFDWixXQUFXLEVBQ1gsZUFBZSxFQUNmLFdBQVcsR0FDWixNQUFNLFVBQVUsQ0FBQztBQUNsQixPQUFPLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFBRSxhQUFhLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDakUsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxlQUFlLEVBQUUsb0JBQW9CLEVBQUUsVUFBVSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFckYsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQztBQXdGekMsTUFBTSxRQUFRLEdBQUc7SUFDZixPQUFPLEVBQUUsd0JBQXdCO0lBQ2pDLDhFQUE4RTtJQUM5RSwrRUFBK0U7SUFDL0Usa0VBQWtFO0lBQ2xFLGlFQUFpRTtJQUNqRSxnRkFBZ0Y7SUFDaEYsOENBQThDO0lBQzlDLFNBQVMsRUFBRSxLQUFLLENBQUMsbUJBQW1CLENBQUMsSUFBSTtJQUN6QyxPQUFPLEVBQUUsNEJBQTRCO0lBQ3JDLHVFQUF1RTtJQUN2RSxpQkFBaUIsRUFBRSxlQUFlO0lBQ2xDLGNBQWMsRUFBRSxHQUFHO0lBQ25CLFdBQVcsRUFBRSxD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LEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDaEQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsU0FBUyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQzNELElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3RDLE9BQU8sRUFBRSxFQUFFLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxJQUFJLENBQUMsVUFBVSxJQUFJLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUMxRSxDQUFDO1FBQ0QsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLEtBQUssSUFBSTtZQUFFLE9BQU8sRUFBRSxFQUFFLEVBQUUsS0FBSyxFQUFFLENBQUM7UUFDbkYsSUFBSSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsU0FBUyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQzlDLElBQUksQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLFNBQVMsT0FBTyxFQUFFLENBQUMsQ0FBQztRQUM3QyxPQUFPLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsTUFBTSxXQUFXLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsaUJBQWlCLENBQUMsRUFBRSxDQUFDO0lBQzFGLENBQUM7O0FBR0gsa0ZBQWtGO0FBQ2xGLE1BQU0sQ0FBQyxNQUFNLFFBQVEsR0FBRyxRQUFRLENBQUMsY0FBYyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIGBhdXRoR2F0ZSgpYCAtIHRoZSBBcHBLaXQgcGx1Z2luIGJlaGluZCB0aGUgdHVubmVsJ3MgZW1haWwtT1RQIGdhdGUuXG4gKlxuICogSXQgaGFzIE5PIHJvdXRlcyBvZiBpdHMgb3duOiB0aGUgdHVubmVsIFBST1hZIChub3QgYW4gSFRUUCBzZXJ2ZXIpIGNhbGxzIHRoZVxuICogaGFuZGxlcnMgdGhpcyBwbHVnaW4gZXhwb3NlcyB2aWEge0BsaW5rIEF1dGhHYXRlUGx1Z2luLmV4cG9ydHN9LiBUaGUgcGx1Z2luXG4gKiBvd25zIHRoZSBhbGxvdy1saXN0LCB0aGUgcGVyLWVtYWlsL3Blci1JUCByYXRlIGxpbWl0ZXJzLCB0aGUgQ2FjaGVNYW5hZ2VyLWJhY2tlZFxuICogb25lLXRpbWUtY29kZSBzdG9yZSwgYW5kIHRoZSBzZXNzaW9uIEpXVC4gYGNyZWF0ZUFwcGAgKHdpdGggbm8gYHNlcnZlcigpYCkgaXNcbiAqIHVzZWQgb25seSB0byBhdXRvLWluaXQgYENhY2hlTWFuYWdlcmAgKyBwcmltZSB0aGUgc2libGluZyBgZW1haWxgIHRyYW5zcG9ydDtcbiAqIHRoaXMgcGx1Z2luIGlzIHdoZXJlIHRoZSBnYXRlIGxvZ2ljIGxpdmVzLlxuICpcbiAqIE9wdGlvbnMgY29tZSBmcm9tIENMSSBmbGFncyBPUiBlbnYsIHdpdGggc2Vuc2libGUgZGVmYXVsdHMgLSBzZWVcbiAqIHtAbGluayByZXNvbHZlQXV0aEdhdGVDb25maWd9LiBUaGUgb25lIHJ1bnRpbWUgZGVwZW5kZW5jeSB0aGUgcGx1Z2luIGNhbid0XG4gKiByZXNvbHZlIGl0c2VsZiBpcyBIT1cgdG8gZW1haWwgdGhlIGNvZGUsIHNvIGl0IHRha2VzIGEgYHNlbmRDb2RlYCBjYWxsYmFjayB0aGVcbiAqIGFwcCB3aXJlcyB0byB0aGUgZW1haWwgcGx1Z2luLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBQbHVnaW4sIHRvUGx1Z2luLCB0eXBlIEJhc2VQbHVnaW5Db25maWcsIHR5cGUgUGx1Z2luTWFuaWZlc3QgfSBmcm9tIFwiQGRhdGFicmlja3MvYXBwa2l0XCI7XG5pbXBvcnQgeyBicmFuZCwgZW52LCBsb2csIG9iamVjdCwgc3RyaW5nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB0eXBlIHsgQXV0aFN0YXR1cyB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1lbWFpbFwiO1xuaW1wb3J0IHsgbG9va3NMaWtlRW1haWwsIG1hdGNoZXNBbGxvd2xpc3QgfSBmcm9tIFwiLi9hbGxvd2xpc3QudHNcIjtcbmltcG9ydCB7XG4gIEFMTE9XX0VOVixcbiAgQlJBTkRfTkFNRV9FTlYsXG4gIENPREVfVFRMX0VOVixcbiAgTUVTU0FHRV9FTlYsXG4gIFNFU1NJT05fVFRMX0VOVixcbiAgU1VCSkVDVF9FTlYsXG59IGZyb20gXCIuL2Vudi50c1wiO1xuaW1wb3J0IHsgQ29kZVN0b3JlLCBzaWduU2Vzc2lvbiwgdmVyaWZ5U2Vzc2lvbiB9IGZyb20gXCIuL290cC50c1wiO1xuaW1wb3J0IHsgUmF0ZUxpbWl0ZXIgfSBmcm9tIFwiLi9yYXRlLWxpbWl0LnRzXCI7XG5pbXBvcnQgeyBLRVlfVFRMX1NFQ09ORFMsIHJlc29sdmVTZXNzaW9uQ3V0b2ZmLCBzaWduaW5nS2V5IH0gZnJvbSBcIi4vc2lnbmluZy1rZXkudHNcIjtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcInR1bm5lbDphdXRoXCIpO1xuXG4vKiogT3B0aW9ucyBmb3IgdGhlIHtAbGluayBhdXRoR2F0ZX0gcGx1Z2luIChhbGwgcmVzb2x2YWJsZSBmcm9tIGVudiAtIHNlZSBiZWxvdykuICovXG5leHBvcnQgaW50ZXJmYWNlIEF1dGhHYXRlQ29uZmlnIGV4dGVuZHMgQmFzZVBsdWdpbkNvbmZpZyB7XG4gIC8qKiBBbGxvdy1saXN0IHBhdHRlcm5zIChkb21haW4gLyBnbG9iIC8gYC9yZWdleC9gKS4gRW1wdHkgPSBhbGxvdyBub2JvZHkuIEVudiBUVU5ORUxfQVVUSF9BTExPVy4gKi9cbiAgYWxsb3c/OiBzdHJpbmcgfCBzdHJpbmdbXTtcbiAgLyoqXG4gICAqIFN1YmplY3QgbGluZSBmb3IgdGhlIGNvZGUgZW1haWwuIEVudiBUVU5ORUxfQVVUSF9TVUJKRUNULlxuICAgKlxuICAgKiBEZWZhdWx0cyB0byBcIllvdXIgdmVyaWZpY2F0aW9uIGNvZGVcIi4gVGhlIHdvcmRpbmcgb2YgdGhlIHN1YmplY3QgYW5kXG4gICAqIHtAbGluayBtZXNzYWdlfSBpcyBkZWxpYmVyYXRlbHkgdGhlIGNvbnZlbnRpb25hbCBwaHJhc2luZyByYXRoZXIgdGhhblxuICAgKiBhbnl0aGluZyBicmFuZGVkOiBpT1MsIEdtYWlsLCBPdXRsb29rLCBhbmQgQW5kcm9pZCBhbGwgZGV0ZWN0IGEgb25lLXRpbWVcbiAgICogY29kZSBmcm9tIHRoaXMgc2hhcGUgYW5kIG9mZmVyIHRvIGF1dG9maWxsIGl0LCBhbmQgYSBub3ZlbCBwaHJhc2luZyBpcyB3aGF0XG4gICAqIGJyZWFrcyB0aGF0IGRldGVjdGlvbi5cbiAgICpcbiAgICogVGhpcyBpcyB0aGUgc3ViamVjdCBURU1QTEFURSwgbm90IHRoZSBsaXRlcmFsIGxpbmUgc2VudDogdGhlIGNvZGUgaXMgc3BsaWNlZFxuICAgKiBpbnRvIGl0IChgXCIxMjM0NTYgaXMgeW91ciB2ZXJpZmljYXRpb24gY29kZVwiYCkgYmVjYXVzZSBhIHB1c2ggbm90aWZpY2F0aW9uXG4gICAqIHNob3dzIG9ubHkgdGhlIHN1YmplY3QgYW5kIHByZWhlYWRlciwgYW5kIHRoYXQgbm90aWZpY2F0aW9uIGlzIHdoYXQgbW9iaWxlXG4gICAqIGF1dG9maWxsIHJlYWRzLiBTZWUgYGNvZGVFbWFpbFN1YmplY3RgIGluIGAuL2FwcC50c2AuXG4gICAqL1xuICBzdWJqZWN0Pzogc3RyaW5nO1xuICAvKipcbiAgICogRGlzcGxheSBuYW1lIHVzZWQgaW4gdGhlIGNvZGUgZW1haWwgY29weS4gRW52IFRVTk5FTF9BVVRIX0JSQU5EX05BTUUuXG4gICAqXG4gICAqIERlZmF1bHRzIHRvIHRoZSBicmFuZCBjb250ZXh0J3MgYG5hbWVgIC0gdGhlIGFwcCdzIG93biBgYnJhbmRpbmcvYnJhbmQueWFtbGBcbiAgICogd2hlbiBpdCBoYXMgb25lLCBlbHNlIHRoZSBkYngtdG9vbHMgZGVmYXVsdC4gU2V0IHRoaXMgb25seSB0byBvdmVycmlkZSB0aGVcbiAgICogYnJhbmQgZm9yIHRoaXMgZ2F0ZS5cbiAgICovXG4gIGJyYW5kTmFtZT86IHN0cmluZztcbiAgLyoqXG4gICAqIExpbmUgc2hvd24gaW1tZWRpYXRlbHkgYWJvdmUgdGhlIGNvZGUgaW4gdGhlIGVtYWlsLiBFbnYgVFVOTkVMX0FVVEhfTUVTU0FHRS5cbiAgICpcbiAgICogS2VlcCB0aGUgY29kZSBvbiBpdHMgT1dOIGxpbmUgZGlyZWN0bHkgYWZ0ZXIgdGhpcyB0ZXh0IC0gdGhhdCBhZGphY2VuY3kgaXNcbiAgICogd2hhdCB0aGUgcGxhdGZvcm0gY29kZS1kZXRlY3Rpb24gaGV1cmlzdGljcyBrZXkgb24uXG4gICAqL1xuICBtZXNzYWdlPzogc3RyaW5nO1xuICAvKipcbiAgICogU2Vzc2lvbiBsaWZldGltZSAoc2Vjb25kcykuIEVudiBUVU5ORUxfQVVUSF9TRVNTSU9OX1RUTC4gRGVmYXVsdCAyNTkyMDAwICgzMGQpLlxuICAgKlxuICAgKiBNYXRjaGVkIHRvIHRoZSBjYWNoZS1iYWNrZWQgc2lnbmluZyBrZXkncyBvd24gMzAtZGF5IFRUTCAoc2VlXG4gICAqIGAuL3NpZ25pbmcta2V5LnRzYCk6IHRoZSBjb29raWUgYW5kIHRoZSBrZXkgdGhhdCB2YWxpZGF0ZXMgaXQgc2hvdWxkIGV4cGlyZVxuICAgKiB0b2dldGhlciwgb3Igb25lIHNpbGVudGx5IG91dGxpdmVzIHRoZSBvdGhlci5cbiAgICovXG4gIHNlc3Npb25UdGxTZWNvbmRzPzogbnVtYmVyO1xuICAvKiogT25lLXRpbWUtY29kZSBsaWZldGltZSAoc2Vjb25kcykuIEVudiBUVU5ORUxfQVVUSF9DT0RFX1RUTC4gRGVmYXVsdCA2MDAgKDEwbSkuICovXG4gIGNvZGVUdGxTZWNvbmRzPzogbnVtYmVyO1xuICAvKiogTWF4IHZlcmlmeSBhdHRlbXB0cyBwZXIgaXNzdWVkIGNvZGUuIERlZmF1bHQgNS4gKi9cbiAgbWF4QXR0ZW1wdHM/OiBudW1iZXI7XG4gIC8qKlxuICAgKiBGb3JjZS1jbGVhciBjdXRvZmY6IGV2ZXJ5IHNlc3Npb24gaXNzdWVkIEJFRk9SRSBpdCBzdG9wcyB2ZXJpZnlpbmcsIHNvIG1vdmluZ1xuICAgKiBpdCBmb3J3YXJkIHNpZ25zIGV2ZXJ5b25lIG91dC4gRW52IFRVTk5FTF9BVVRIX1NFU1NJT05fQ1VUT0ZGLlxuICAgKlxuICAgKiBBbnl0aGluZyBgb2JqZWN0LnRvRGF0ZWAgYWNjZXB0czogYSBgRGF0ZWAsIGAyMDI2LTA4LTAyYCwgYW4gSVNPIGluc3RhbnQsXG4gICAqIGVwb2NoIHNlY29uZHMvbWlsbGlzLCBvciBhIHJlbGF0aXZlIGR1cmF0aW9uIChgLTMwZGAsIGA3IGRheXMgYWdvYCkuIFVuc2V0XG4gICAqIG1lYW5zIG5vIGN1dG9mZi5cbiAgICovXG4gIHNlc3Npb25DdXRvZmY/OiBzdHJpbmcgfCBudW1iZXIgfCBEYXRlO1xuICAvKiogRGVsaXZlciBhIGNvZGUgdG8gYW4gYWRkcmVzcy4gV2lyZWQgYnkgdGhlIGFwcCB0byB0aGUgZW1haWwgcGx1Z2luLiAqL1xuICBzZW5kQ29kZT86IChlbWFpbDogc3RyaW5nLCBjb2RlOiBzdHJpbmcsIG9wdHM6IFNlbmRDb2RlT3B0aW9ucykgPT4gUHJvbWlzZTx2b2lkPjtcbn1cblxuLyoqIEJyYW5kaW5nL21lc3NhZ2luZyBwYXNzZWQgdG8ge0BsaW5rIEF1dGhHYXRlQ29uZmlnLnNlbmRDb2RlfS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2VuZENvZGVPcHRpb25zIHtcbiAgc3ViamVjdDogc3RyaW5nO1xuICBicmFuZE5hbWU6IHN0cmluZztcbiAgbWVzc2FnZTogc3RyaW5nO1xuICAvKipcbiAgICogTGlmZXRpbWUgb2YgdGhlIGNvZGUgYmVpbmcgc2VudCwgaW4gc2Vjb25kcyAtIHRoZSBSRVNPTFZFRFxuICAgKiB7QGxpbmsgQXV0aEdhdGVDb25maWcuY29kZVR0bFNlY29uZHN9LCBzbyB0aGUgZW1haWwgY2FuIHN0YXRlIHRoZSByZWFsXG4gICAqIGV4cGlyeSAoXCJUaGlzIGNvZGUgZXhwaXJlcyBpbiAxMCBtaW51dGVzXCIpIGluc3RlYWQgb2YgYSB2YWd1ZSBcInNob3J0bHlcIlxuICAgKiB0aGF0IGRyaWZ0cyBmcm9tIHRoZSBjb25maWd1cmVkIFRUTC5cbiAgICovXG4gIGNvZGVUdGxTZWNvbmRzOiBudW1iZXI7XG59XG5cbi8qKiBSZXNvbHZlZCBnYXRlIGNvbmZpZyB3aXRoIGVudiBmYWxsYmFja3MgKyBkZWZhdWx0cyBhcHBsaWVkLiAqL1xuZXhwb3J0IGludGVyZmFjZSBSZXNvbHZlZEF1dGhHYXRlQ29uZmlnIHtcbiAgYWxsb3c6IHN0cmluZ1tdO1xuICBzdWJqZWN0OiBzdHJpbmc7XG4gIGJyYW5kTmFtZTogc3RyaW5nO1xuICBtZXNzYWdlOiBzdHJpbmc7XG4gIHNlc3Npb25UdGxTZWNvbmRzOiBudW1iZXI7XG4gIGNvZGVUdGxTZWNvbmRzOiBudW1iZXI7XG4gIG1heEF0dGVtcHRzOiBudW1iZXI7XG4gIC8qKiBGb3JjZS1jbGVhciBjdXRvZmYgaW4gZXBvY2ggbXM7IGAwYCB3aGVuIHVuc2V0LiAqL1xuICBzZXNzaW9uQ3V0b2ZmTXM6IG51bWJlcjtcbn1cblxuY29uc3QgREVGQVVMVFMgPSB7XG4gIHN1YmplY3Q6IFwiWW91ciB2ZXJpZmljYXRpb24gY29kZVwiLFxuICAvLyBUaGUgcmVwby13aWRlIGJyYW5kIGNvbnRleHQncyBkaXNwbGF5IG5hbWUsIE5PVCBhIGhhcmRjb2RlZCBwcm9kdWN0IHN0cmluZzpcbiAgLy8gdGhpcyBuYW1lIGlzIHdoYXQgdGhlIHJlY2lwaWVudCByZWFkcyBpbiB0aGUgY29kZSBlbWFpbCwgc28gaXQgaGFzIHRvIGJlIHRoZVxuICAvLyBzYW1lIGlkZW50aXR5IHRoZSByZXN0IG9mIHRoZSBhcHAgcHJlc2VudHMuIEEgaG9zdCB3aXRoIGl0cyBvd25cbiAgLy8gYGJyYW5kaW5nL2JyYW5kLnlhbWxgIG92ZXJyaWRlcyBpdCBieSBwYXNzaW5nIGBicmFuZE5hbWVgIChzZWVcbiAgLy8ge0BsaW5rIHN0YXJ0R2F0ZUFwcH0sIHdoaWNoIHJlc29sdmVzIHRoZSBvbi1kaXNrIGNvbnRleHQpOyB0aGUgc2hhcmVkIGRlZmF1bHRcbiAgLy8gaXMgdGhlIGZhbGxiYWNrIHdoZW4gbm90aGluZyBpcyBjb25maWd1cmVkLlxuICBicmFuZE5hbWU6IGJyYW5kLmRlZmF1bHRCcmFuZENvbnRleHQubmFtZSxcbiAgbWVzc2FnZTogXCJZb3VyIHZlcmlmaWNhdGlvbiBjb2RlIGlzOlwiLFxuICAvLyAzMCBkYXlzLCB0aGUgc2FtZSB3aW5kb3cgdGhlIGNhY2hlLWJhY2tlZCBzaWduaW5nIGtleSBpcyBzdG9yZWQgZm9yLlxuICBzZXNzaW9uVHRsU2Vjb25kczogS0VZX1RUTF9TRUNPTkRTLFxuICBjb2RlVHRsU2Vjb25kczogNjAwLFxuICBtYXhBdHRlbXB0czogNSxcbn07XG5cbi8qKiBNZXJnZSB7QGxpbmsgQXV0aEdhdGVDb25maWd9IG92ZXIgZW52IG92ZXIgZGVmYXVsdHMgaW50byBhIHJlc29sdmVkIGNvbmZpZy4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlQXV0aEdhdGVDb25maWcoY29uZmlnOiBBdXRoR2F0ZUNvbmZpZyk6IFJlc29sdmVkQXV0aEdhdGVDb25maWcge1xuICByZXR1cm4ge1xuICAgIC8vIEJvdGggc291cmNlcyBhcmUgdW5pb25lZCByYXRoZXIgdGhhbiBvbmUgb3ZlcnJpZGluZzogYSBkZXBsb3ltZW50LXdpZGVcbiAgICAvLyBUVU5ORUxfQVVUSF9BTExPVyBhbmQgYSBwZXItaW52b2NhdGlvbiBgLS1hbGxvd2Agc2hvdWxkIGJvdGggZ3JhbnQgYWNjZXNzLlxuICAgIC8vIEhlbmNlIGBwYXJzZUxpc3RgIG9uIGVhY2ggcmF0aGVyIHRoYW4gYGVudi5saXN0YCwgd2hpY2ggc3RvcHMgYXQgdGhlIGZpcnN0XG4gICAgLy8gc291cmNlIHRoYXQgeWllbGRzIGFueXRoaW5nLlxuICAgIGFsbG93OiBbLi4uc3RyaW5nLnBhcnNlTGlzdChjb25maWcuYWxsb3cpLCAuLi5zdHJpbmcucGFyc2VMaXN0KGVudi50ZXh0KEFMTE9XX0VOVikpXSxcbiAgICBzdWJqZWN0OiBlbnYuc3RyaW5nKGNvbmZpZy5zdWJqZWN0LCBTVUJKRUNUX0VOVikgPz8gREVGQVVMVFMuc3ViamVjdCxcbiAgICBicmFuZE5hbWU6IGVudi5zdHJpbmcoY29uZmlnLmJyYW5kTmFtZSwgQlJBTkRfTkFNRV9FTlYpID8/IERFRkFVTFRTLmJyYW5kTmFtZSxcbiAgICBtZXNzYWdlOiBlbnYuc3RyaW5nKGNvbmZpZy5tZXNzYWdlLCBNRVNTQUdFX0VOVikgPz8gREVGQVVMVFMubWVzc2FnZSxcbiAgICBzZXNzaW9uVHRsU2Vjb25kczogZW52LnBvc2l0aXZlSW50KFxuICAgICAgY29uZmlnLnNlc3Npb25UdGxTZWNvbmRzLFxuICAgICAgU0VTU0lPTl9UVExfRU5WLFxuICAgICAgREVGQVVMVFMuc2Vzc2lvblR0bFNlY29uZHMsXG4gICAgKSxcbiAgICBjb2RlVHRsU2Vjb25kczogZW52LnBvc2l0aXZlSW50KGNvbmZpZy5jb2RlVHRsU2Vjb25kcywgQ09ERV9UVExfRU5WLCBERUZBVUxUUy5jb2RlVHRsU2Vjb25kcyksXG4gICAgbWF4QXR0ZW1wdHM6IGNvbmZpZy5tYXhBdHRlbXB0cyA/PyBERUZBVUxUUy5tYXhBdHRlbXB0cyxcbiAgICBzZXNzaW9uQ3V0b2ZmTXM6IHJlc29sdmVTZXNzaW9uQ3V0b2ZmKGNvbmZpZy5zZXNzaW9uQ3V0b2ZmKSxcbiAgfTtcbn1cblxuLyoqIFRoZSBoYW5kbGVycyB0aGUgcHJveHkgY2FsbHMgaW4tcHJvY2VzcyAocmV0dXJuZWQgYnkge0BsaW5rIEF1dGhHYXRlUGx1Z2luLmV4cG9ydHN9KS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQXV0aEdhdGVBcGkge1xuICAvKiogSGFuZGxlIGEgY29kZSByZXF1ZXN0LiBBbHdheXMgcmVzb2x2ZXMgYHsgb2s6IHRydWUgfWAgKGFudGktZW51bWVyYXRpb24pLiAqL1xuICByZXF1ZXN0KGVtYWlsOiBzdHJpbmcsIGlwOiBzdHJpbmcpOiBQcm9taXNlPHsgb2s6IHRydWU7IHJldHJ5QWZ0ZXI/OiBudW1iZXIgfT47XG4gIC8qKiBIYW5kbGUgYSBjb2RlIHZlcmlmaWNhdGlvbi4gT24gc3VjY2VzcyByZXR1cm5zIHRoZSBzZXNzaW9uIHRva2VuIHRvIGNvb2tpZS4gKi9cbiAgdmVyaWZ5KFxuICAgIGVtYWlsOiBzdHJpbmcsXG4gICAgY29kZTogc3RyaW5nLFxuICAgIGlwOiBzdHJpbmcsXG4gICk6IFByb21pc2U8eyBvazogYm9vbGVhbjsgdG9rZW4/OiBzdHJpbmc7IHJldHJ5QWZ0ZXI/OiBudW1iZXIgfT47XG4gIC8qKiBSZXNvbHZlIHRoZSBhdXRoZW50aWNhdGVkIGVtYWlsIGZvciBhIHNlc3Npb24gdG9rZW4sIG9yIHVuZGVmaW5lZC4gKi9cbiAgc2Vzc2lvbih0b2tlbjogc3RyaW5nIHwgdW5kZWZpbmVkKTogUHJvbWlzZTxzdHJpbmcgfCB1bmRlZmluZWQ+O1xuICAvKiogU2Vzc2lvbiBUVEwgaW4gc2Vjb25kcyAoZm9yIHRoZSBjb29raWUgTWF4LUFnZSkuICovXG4gIHJlYWRvbmx5IHNlc3Npb25UdGxTZWNvbmRzOiBudW1iZXI7XG4gIC8qKiBUaGUgZ2F0ZSBzdGF0dXMgcGF5bG9hZCAoYGVuYWJsZWRgIGlzIGFsd2F5cyB0cnVlIHdoZW4gdGhpcyBwbHVnaW4gcnVucykuICovXG4gIHN0YXR1cyh0b2tlbjogc3RyaW5nIHwgdW5kZWZpbmVkKTogUHJvbWlzZTxBdXRoU3RhdHVzPjtcbn1cblxuLyoqIEFwcEtpdCBwbHVnaW4gb3duaW5nIHRoZSBlbWFpbC1PVFAgZ2F0ZSdzIGxvZ2ljIChubyBIVFRQIHJvdXRlczsgcHJveHktZHJpdmVuKS4gKi9cbmV4cG9ydCBjbGFzcyBBdXRoR2F0ZVBsdWdpbiBleHRlbmRzIFBsdWdpbjxBdXRoR2F0ZUNvbmZpZz4ge1xuICBzdGF0aWMgbWFuaWZlc3QgPSB7XG4gICAgbmFtZTogXCJhdXRoR2F0ZVwiLFxuICAgIGRpc3BsYXlOYW1lOiBcIkF1dGggR2F0ZVwiLFxuICAgIGRlc2NyaXB0aW9uOiBcIkVtYWlsIG9uZS10aW1lLXBhc3N3b3JkIGFjY2VzcyBnYXRlIGZvciBhIHB1YmxpYyB0dW5uZWwuXCIsXG4gICAgc3RhYmlsaXR5OiBcImJldGFcIixcbiAgICByZXNvdXJjZXM6IHsgcmVxdWlyZWQ6IFtdLCBvcHRpb25hbDogW10gfSxcbiAgfSBzYXRpc2ZpZXMgUGx1Z2luTWFuaWZlc3Q8XCJhdXRoR2F0ZVwiPjtcblxuICBwcml2YXRlIHJlc29sdmVkITogUmVzb2x2ZWRBdXRoR2F0ZUNvbmZpZztcbiAgcHJpdmF0ZSBjb2RlcyE6IENvZGVTdG9yZTtcbiAgLy8gUmVxdWVzdGluZyBhIGNvZGUgaXMgZW1haWwtc3BhbS1wcm9uZTsgdmVyaWZ5aW5nIGlzIGEgYnJ1dGUtZm9yY2Ugc3VyZmFjZS5cbiAgLy8gUGVyLWVtYWlsIEFORCBwZXItSVAgc28gbmVpdGhlciBheGlzIGFsb25lIGlzIGEgYnlwYXNzLlxuICBwcml2YXRlIHJlYWRvbmx5IHJlcXVlc3RMaW1pdGVyID0gbmV3IFJhdGVMaW1pdGVyKDUsIDE1ICogNjAgKiAxMDAwKTtcbiAgcHJpdmF0ZSByZWFkb25seSB2ZXJpZnlMaW1pdGVyID0gbmV3IFJhdGVMaW1pdGVyKDEwLCAxNSAqIDYwICogMTAwMCk7XG5cbiAgb3ZlcnJpZGUgYXN5bmMgc2V0dXAoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgdGhpcy5yZXNvbHZlZCA9IHJlc29sdmVBdXRoR2F0ZUNvbmZpZyh0aGlzLmNvbmZpZyk7XG4gICAgdGhpcy5jb2RlcyA9IG5ldyBDb2RlU3RvcmUodGhpcy5yZXNvbHZlZC5jb2RlVHRsU2Vjb25kcywgdGhpcy5yZXNvbHZlZC5tYXhBdHRlbXB0cyk7XG4gICAgLy8gUmVzb2x2ZSB0aGUgc2lnbmluZyBrZXkgSEVSRSByYXRoZXIgdGhhbiBsYXppbHkgb24gdGhlIGZpcnN0IHNpZ24taW4sIHNvIGFcbiAgICAvLyBjYWNoZSB0aGF0IGNhbm5vdCBob2xkIGl0IChhbmQgdGhlIHJlc3VsdGluZyBcInNlc3Npb25zIHdvbid0IHN1cnZpdmUgYVxuICAgIC8vIHJlc3RhcnRcIiB3YXJuaW5nKSBzaG93cyB1cCBpbiB0aGUgc3RhcnR1cCBsb2csIG5vdCBob3VycyBsYXRlci5cbiAgICBjb25zdCB7IGN1dG9mZk1zIH0gPSBhd2FpdCBzaWduaW5nS2V5KHRoaXMucmVzb2x2ZWQuc2Vzc2lvbkN1dG9mZk1zKTtcbiAgICBsb2dnZXIuaW5mbyhcInJlYWR5XCIsIHtcbiAgICAgIHBhdHRlcm5zOiB0aGlzLnJlc29sdmVkLmFsbG93Lmxlbmd0aCxcbiAgICAgIHNlc3Npb25UdGxTZWNvbmRzOiB0aGlzLnJlc29sdmVkLnNlc3Npb25UdGxTZWNvbmRzLFxuICAgICAgLi4ub2JqZWN0Lm9wdGlvbmFsKFwic2Vzc2lvbkN1dG9mZlwiLCBjdXRvZmZNcyA+IDAgPyBuZXcgRGF0ZShjdXRvZmZNcykudG9JU09TdHJpbmcoKSA6IG51bGwpLFxuICAgIH0pO1xuICB9XG5cbiAgb3ZlcnJpZGUgZXhwb3J0cygpOiBBdXRoR2F0ZUFwaSB7XG4gICAgcmV0dXJuIHtcbiAgICAgIHNlc3Npb25UdGxTZWNvbmRzOiB0aGlzLnJlc29sdmVkLnNlc3Npb25UdGxTZWNvbmRzLFxuICAgICAgcmVxdWVzdDogKGVtYWlsLCBpcCkgPT4gdGhpcy5oYW5kbGVSZXF1ZXN0KGVtYWlsLCBpcCksXG4gICAgICB2ZXJpZnk6IChlbWFpbCwgY29kZSwgaXApID0+IHRoaXMuaGFuZGxlVmVyaWZ5KGVtYWlsLCBjb2RlLCBpcCksXG4gICAgICBzZXNzaW9uOiAodG9rZW4pID0+IHZlcmlmeVNlc3Npb24odG9rZW4pLFxuICAgICAgc3RhdHVzOiBhc3luYyAodG9rZW4pID0+ICh7XG4gICAgICAgIGF1dGhlbnRpY2F0ZWQ6IEJvb2xlYW4oYXdhaXQgdmVyaWZ5U2Vzc2lvbih0b2tlbikpLFxuICAgICAgICBlbWFpbDogKGF3YWl0IHZlcmlmeVNlc3Npb24odG9rZW4pKSA/PyB1bmRlZmluZWQsXG4gICAgICAgIGVuYWJsZWQ6IHRydWUsXG4gICAgICB9KSxcbiAgICB9O1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBoYW5kbGVSZXF1ZXN0KFxuICAgIGVtYWlsOiBzdHJpbmcsXG4gICAgaXA6IHN0cmluZyxcbiAgKTogUHJvbWlzZTx7IG9rOiB0cnVlOyByZXRyeUFmdGVyPzogbnVtYmVyIH0+IHtcbiAgICBjb25zdCBhZGRyZXNzID0gZW1haWwudHJpbSgpLnRvTG93ZXJDYXNlKCk7XG4gICAgY29uc3QgYnlJcCA9IHRoaXMucmVxdWVzdExpbWl0ZXIuaGl0KGBpcDoke2lwfWApO1xuICAgIGNvbnN0IGJ5RW1haWwgPSB0aGlzLnJlcXVlc3RMaW1pdGVyLmhpdChgZW1haWw6JHthZGRyZXNzfWApO1xuICAgIGlmICghYnlJcC5hbGxvd2VkIHx8ICFieUVtYWlsLmFsbG93ZWQpIHtcbiAgICAgIHJldHVybiB7IG9rOiB0cnVlLCByZXRyeUFmdGVyOiBieUlwLnJldHJ5QWZ0ZXIgPz8gYnlFbWFpbC5yZXRyeUFmdGVyIH07XG4gICAgfVxuICAgIGlmIChsb29rc0xpa2VFbWFpbChhZGRyZXNzKSAmJiBtYXRjaGVzQWxsb3dsaXN0KGFkZHJlc3MsIHRoaXMucmVzb2x2ZWQuYWxsb3cpKSB7XG4gICAgICBjb25zdCBjb2RlID0gYXdhaXQgdGhpcy5jb2Rlcy5pc3N1ZShhZGRyZXNzKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGF3YWl0IHRoaXMuY29uZmlnLnNlbmRDb2RlPy4oYWRkcmVzcywgY29kZSwge1xuICAgICAgICAgIHN1YmplY3Q6IHRoaXMucmVzb2x2ZWQuc3ViamVjdCxcbiAgICAgICAgICBicmFuZE5hbWU6IHRoaXMucmVzb2x2ZWQuYnJhbmROYW1lLFxuICAgICAgICAgIG1lc3NhZ2U6IHRoaXMucmVzb2x2ZWQubWVzc2FnZSxcbiAgICAgICAgICBjb2RlVHRsU2Vjb25kczogdGhpcy5yZXNvbHZlZC5jb2RlVHRsU2Vjb25kcyxcbiAgICAgICAgfSk7XG4gICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICBsb2dnZXIud2FybihcImZhaWxlZCB0byBzZW5kIE9UUCBlbWFpbFwiLCB7IGVycm9yIH0pO1xuICAgICAgfVxuICAgIH1cbiAgICByZXR1cm4geyBvazogdHJ1ZSB9O1xuICB9XG5cbiAgcHJpdmF0ZSBhc3luYyBoYW5kbGVWZXJpZnkoXG4gICAgZW1haWw6IHN0cmluZyxcbiAgICBjb2RlOiBzdHJpbmcsXG4gICAgaXA6IHN0cmluZyxcbiAgKTogUHJvbWlzZTx7IG9rOiBib29sZWFuOyB0b2tlbj86IHN0cmluZzsgcmV0cnlBZnRlcj86IG51bWJlciB9PiB7XG4gICAgY29uc3QgYWRkcmVzcyA9IGVtYWlsLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xuICAgIGNvbnN0IGJ5SXAgPSB0aGlzLnZlcmlmeUxpbWl0ZXIuaGl0KGBpcDoke2lwfWApO1xuICAgIGNvbnN0IGJ5RW1haWwgPSB0aGlzLnZlcmlmeUxpbWl0ZXIuaGl0KGBlbWFpbDoke2FkZHJlc3N9YCk7XG4gICAgaWYgKCFieUlwLmFsbG93ZWQgfHwgIWJ5RW1haWwuYWxsb3dlZCkge1xuICAgICAgcmV0dXJuIHsgb2s6IGZhbHNlLCByZXRyeUFmdGVyOiBieUlwLnJldHJ5QWZ0ZXIgPz8gYnlFbWFpbC5yZXRyeUFmdGVyIH07XG4gICAgfVxuICAgIGlmICgoYXdhaXQgdGhpcy5jb2Rlcy52ZXJpZnkoYWRkcmVzcywgY29kZS50cmltKCkpKSAhPT0gXCJva1wiKSByZXR1cm4geyBvazogZmFsc2UgfTtcbiAgICB0aGlzLnJlcXVlc3RMaW1pdGVyLnJlc2V0KGBlbWFpbDoke2FkZHJlc3N9YCk7XG4gICAgdGhpcy52ZXJpZnlMaW1pdGVyLnJlc2V0KGBlbWFpbDoke2FkZHJlc3N9YCk7XG4gICAgcmV0dXJuIHsgb2s6IHRydWUsIHRva2VuOiBhd2FpdCBzaWduU2Vzc2lvbihhZGRyZXNzLCB0aGlzLnJlc29sdmVkLnNlc3Npb25UdGxTZWNvbmRzKSB9O1xuICB9XG59XG5cbi8qKiBGYWN0b3J5OiBgYXV0aEdhdGUoeyBhbGxvdywgc3ViamVjdCwgLi4uIH0pYCBmb3IgYW4gQXBwS2l0IGBwbHVnaW5zYCBhcnJheS4gKi9cbmV4cG9ydCBjb25zdCBhdXRoR2F0ZSA9IHRvUGx1Z2luKEF1dGhHYXRlUGx1Z2luKTtcbiJdfQ==
|
package/lib/src/portr.d.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* portr install + config + launch for the tunnel CLI.
|
|
3
|
-
*
|
|
4
|
-
* Some hosts (Databricks Apps among them) mount the container's `$HOME` read-only
|
|
5
|
-
* on cold start, so the portr binary and its config are placed under a writable,
|
|
6
|
-
* cwd-rooted `.home` unconditionally - it costs nothing where `$HOME` is writable.
|
|
7
|
-
* The install is idempotent (the installer skips when the on-PATH binary is
|
|
8
|
-
* current). The config is rendered from `TUNNEL_PUBLIC_DOMAIN` (`<subdomain>.<server>`)
|
|
9
|
-
* + `PORTR_TOKEN` and points portr at the PUBLIC port (the proxy listens there).
|
|
10
|
-
*
|
|
11
|
-
* @module
|
|
12
|
-
*/
|
|
13
|
-
import { spawn } from "node:child_process";
|
|
14
|
-
/** Resolved portr wiring, or `undefined` when no tunnel is configured. */
|
|
15
|
-
export interface PortrConfig {
|
|
16
|
-
subdomain: string;
|
|
17
|
-
server: string;
|
|
18
|
-
token: string;
|
|
19
|
-
port: number;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Resolve portr config from `TUNNEL_PUBLIC_DOMAIN` + `PORTR_TOKEN`, or an
|
|
23
|
-
* explicit `subdomain`. The domain is `<subdomain>.<server>` (e.g.
|
|
24
|
-
* `demo.apps.dbx.tools`). Returns `undefined` (no tunnel) when the token or a
|
|
25
|
-
* usable domain is absent.
|
|
26
|
-
*/
|
|
27
|
-
export declare function resolvePortrConfig(opts: {
|
|
28
|
-
publicDomain?: string;
|
|
29
|
-
subdomain?: string;
|
|
30
|
-
token?: string;
|
|
31
|
-
port: number;
|
|
32
|
-
}): PortrConfig | undefined;
|
|
33
|
-
/** Install portr (idempotent) into the cwd-rooted home and return the child env. */
|
|
34
|
-
export declare function installPortr(): NodeJS.ProcessEnv;
|
|
35
|
-
/** Render `~/.portr/config.yaml` for the resolved tunnel. */
|
|
36
|
-
export declare function writePortrConfig(config: PortrConfig, env: NodeJS.ProcessEnv): void;
|
|
37
|
-
/** Launch `portr start` as a child process (caller supervises + kills it). */
|
|
38
|
-
export declare function startPortr(config: PortrConfig, env: NodeJS.ProcessEnv): ReturnType<typeof spawn>;
|
|
39
|
-
/** True when the installed portr binary path exists (post-install sanity). */
|
|
40
|
-
export declare function portrInstalled(env: NodeJS.ProcessEnv): boolean;
|
package/lib/src/portr.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* portr install + config + launch for the tunnel CLI.
|
|
3
|
-
*
|
|
4
|
-
* Some hosts (Databricks Apps among them) mount the container's `$HOME` read-only
|
|
5
|
-
* on cold start, so the portr binary and its config are placed under a writable,
|
|
6
|
-
* cwd-rooted `.home` unconditionally - it costs nothing where `$HOME` is writable.
|
|
7
|
-
* The install is idempotent (the installer skips when the on-PATH binary is
|
|
8
|
-
* current). The config is rendered from `TUNNEL_PUBLIC_DOMAIN` (`<subdomain>.<server>`)
|
|
9
|
-
* + `PORTR_TOKEN` and points portr at the PUBLIC port (the proxy listens there).
|
|
10
|
-
*
|
|
11
|
-
* @module
|
|
12
|
-
*/
|
|
13
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
14
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
15
|
-
import { delimiter, join } from "node:path";
|
|
16
|
-
import { env, log } from "@dbx-tools/shared-core";
|
|
17
|
-
import { PUBLIC_DOMAIN_ENV } from "./env.js";
|
|
18
|
-
const logger = log.logger("tunnel:portr");
|
|
19
|
-
/**
|
|
20
|
-
* Resolve portr config from `TUNNEL_PUBLIC_DOMAIN` + `PORTR_TOKEN`, or an
|
|
21
|
-
* explicit `subdomain`. The domain is `<subdomain>.<server>` (e.g.
|
|
22
|
-
* `demo.apps.dbx.tools`). Returns `undefined` (no tunnel) when the token or a
|
|
23
|
-
* usable domain is absent.
|
|
24
|
-
*/
|
|
25
|
-
export function resolvePortrConfig(opts) {
|
|
26
|
-
// PORTR_* is upstream portr's own namespace, so it keeps its name.
|
|
27
|
-
const token = env.string(opts.token, "PORTR_TOKEN");
|
|
28
|
-
const domain = env.string(opts.publicDomain, PUBLIC_DOMAIN_ENV);
|
|
29
|
-
if (!token)
|
|
30
|
-
return undefined;
|
|
31
|
-
let subdomain = opts.subdomain;
|
|
32
|
-
let server;
|
|
33
|
-
if (domain) {
|
|
34
|
-
subdomain ??= domain.split(".")[0];
|
|
35
|
-
server = domain.slice(domain.indexOf(".") + 1);
|
|
36
|
-
}
|
|
37
|
-
server ??= env.text("PORTR_SERVER") ?? undefined;
|
|
38
|
-
if (!subdomain || !server || server === domain)
|
|
39
|
-
return undefined;
|
|
40
|
-
return { subdomain, server, token, port: opts.port };
|
|
41
|
-
}
|
|
42
|
-
/** The writable home portr installs + configures under (Apps `$HOME` is read-only). */
|
|
43
|
-
function portrHome() {
|
|
44
|
-
const home = join(process.cwd(), ".home");
|
|
45
|
-
mkdirSync(join(home, ".portr", "bin"), { recursive: true });
|
|
46
|
-
return home;
|
|
47
|
-
}
|
|
48
|
-
/** Install portr (idempotent) into the cwd-rooted home and return the child env. */
|
|
49
|
-
export function installPortr() {
|
|
50
|
-
const home = portrHome();
|
|
51
|
-
const env = {
|
|
52
|
-
...process.env,
|
|
53
|
-
HOME: home,
|
|
54
|
-
PORTR_AUTO_ADD_PATH: "no",
|
|
55
|
-
PATH: [join(home, ".portr", "bin"), process.env.PATH ?? ""].join(delimiter),
|
|
56
|
-
};
|
|
57
|
-
logger.info("installing portr (idempotent)");
|
|
58
|
-
const res = spawnSync("bash", ["-c", "curl -sSf https://install.portr.dev | sh"], {
|
|
59
|
-
env,
|
|
60
|
-
stdio: "inherit",
|
|
61
|
-
});
|
|
62
|
-
if (res.status !== 0)
|
|
63
|
-
throw new Error("portr install failed");
|
|
64
|
-
return env;
|
|
65
|
-
}
|
|
66
|
-
/** Render `~/.portr/config.yaml` for the resolved tunnel. */
|
|
67
|
-
export function writePortrConfig(config, env) {
|
|
68
|
-
const path = join(env.HOME, ".portr", "config.yaml");
|
|
69
|
-
writeFileSync(path, [
|
|
70
|
-
`server_url: ${config.server}`,
|
|
71
|
-
`ssh_url: ${config.server}:4444`,
|
|
72
|
-
`secret_key: ${config.token}`,
|
|
73
|
-
"disable_dashboard: true",
|
|
74
|
-
"disable_tui: true",
|
|
75
|
-
"tunnels:",
|
|
76
|
-
` - name: ${config.subdomain}`,
|
|
77
|
-
` subdomain: ${config.subdomain}`,
|
|
78
|
-
` port: ${config.port}`,
|
|
79
|
-
"",
|
|
80
|
-
].join("\n"));
|
|
81
|
-
}
|
|
82
|
-
/** Launch `portr start` as a child process (caller supervises + kills it). */
|
|
83
|
-
export function startPortr(config, env) {
|
|
84
|
-
// Reclaim the subdomain from any portr left by a previous boot in this container.
|
|
85
|
-
spawnSync("pkill", ["-x", "portr"], { stdio: "ignore" });
|
|
86
|
-
logger.info(`portr tunneling https://${config.subdomain}.${config.server} -> :${config.port}`);
|
|
87
|
-
return spawn("portr", ["start"], { env, stdio: "inherit" });
|
|
88
|
-
}
|
|
89
|
-
/** True when the installed portr binary path exists (post-install sanity). */
|
|
90
|
-
export function portrInstalled(env) {
|
|
91
|
-
return existsSync(join(env.HOME, ".portr", "bin", "portr"));
|
|
92
|
-
}
|
|
93
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9ydHIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvcG9ydHIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7O0dBV0c7QUFFSCxPQUFPLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ3RELE9BQU8sRUFBRSxVQUFVLEVBQUUsU0FBUyxFQUFFLGFBQWEsRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUMvRCxPQUFPLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUM1QyxPQUFPLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQ2xELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUU3QyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0FBVTFDOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGtCQUFrQixDQUFDLElBS2xDO0lBQ0MsbUVBQW1FO0lBQ25FLE1BQU0sS0FBSyxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxhQUFhLENBQUMsQ0FBQztJQUNwRCxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztJQUNoRSxJQUFJLENBQUMsS0FBSztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQzdCLElBQUksU0FBUyxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7SUFDL0IsSUFBSSxNQUEwQixDQUFDO0lBQy9CLElBQUksTUFBTSxFQUFFLENBQUM7UUFDWCxTQUFTLEtBQUssTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNuQyxNQUFNLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsT0FBTyxD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LEVBQUUsQ0FBQyxPQUFPLENBQUMsRUFBRSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztBQUM5RCxDQUFDO0FBRUQsOEVBQThFO0FBQzlFLE1BQU0sVUFBVSxjQUFjLENBQUMsR0FBc0I7SUFDbkQsT0FBTyxVQUFVLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFLLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO0FBQy9ELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIHBvcnRyIGluc3RhbGwgKyBjb25maWcgKyBsYXVuY2ggZm9yIHRoZSB0dW5uZWwgQ0xJLlxuICpcbiAqIFNvbWUgaG9zdHMgKERhdGFicmlja3MgQXBwcyBhbW9uZyB0aGVtKSBtb3VudCB0aGUgY29udGFpbmVyJ3MgYCRIT01FYCByZWFkLW9ubHlcbiAqIG9uIGNvbGQgc3RhcnQsIHNvIHRoZSBwb3J0ciBiaW5hcnkgYW5kIGl0cyBjb25maWcgYXJlIHBsYWNlZCB1bmRlciBhIHdyaXRhYmxlLFxuICogY3dkLXJvb3RlZCBgLmhvbWVgIHVuY29uZGl0aW9uYWxseSAtIGl0IGNvc3RzIG5vdGhpbmcgd2hlcmUgYCRIT01FYCBpcyB3cml0YWJsZS5cbiAqIFRoZSBpbnN0YWxsIGlzIGlkZW1wb3RlbnQgKHRoZSBpbnN0YWxsZXIgc2tpcHMgd2hlbiB0aGUgb24tUEFUSCBiaW5hcnkgaXNcbiAqIGN1cnJlbnQpLiBUaGUgY29uZmlnIGlzIHJlbmRlcmVkIGZyb20gYFRVTk5FTF9QVUJMSUNfRE9NQUlOYCAoYDxzdWJkb21haW4+LjxzZXJ2ZXI+YClcbiAqICsgYFBPUlRSX1RPS0VOYCBhbmQgcG9pbnRzIHBvcnRyIGF0IHRoZSBQVUJMSUMgcG9ydCAodGhlIHByb3h5IGxpc3RlbnMgdGhlcmUpLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBzcGF3biwgc3Bhd25TeW5jIH0gZnJvbSBcIm5vZGU6Y2hpbGRfcHJvY2Vzc1wiO1xuaW1wb3J0IHsgZXhpc3RzU3luYywgbWtkaXJTeW5jLCB3cml0ZUZpbGVTeW5jIH0gZnJvbSBcIm5vZGU6ZnNcIjtcbmltcG9ydCB7IGRlbGltaXRlciwgam9pbiB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB7IGVudiwgbG9nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IFBVQkxJQ19ET01BSU5fRU5WIH0gZnJvbSBcIi4vZW52LnRzXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJ0dW5uZWw6cG9ydHJcIik7XG5cbi8qKiBSZXNvbHZlZCBwb3J0ciB3aXJpbmcsIG9yIGB1bmRlZmluZWRgIHdoZW4gbm8gdHVubmVsIGlzIGNvbmZpZ3VyZWQuICovXG5leHBvcnQgaW50ZXJmYWNlIFBvcnRyQ29uZmlnIHtcbiAgc3ViZG9tYWluOiBzdHJpbmc7XG4gIHNlcnZlcjogc3RyaW5nO1xuICB0b2tlbjogc3RyaW5nO1xuICBwb3J0OiBudW1iZXI7XG59XG5cbi8qKlxuICogUmVzb2x2ZSBwb3J0ciBjb25maWcgZnJvbSBgVFVOTkVMX1BVQkxJQ19ET01BSU5gICsgYFBPUlRSX1RPS0VOYCwgb3IgYW5cbiAqIGV4cGxpY2l0IGBzdWJkb21haW5gLiBUaGUgZG9tYWluIGlzIGA8c3ViZG9tYWluPi48c2VydmVyPmAgKGUuZy5cbiAqIGBkZW1vLmFwcHMuZGJ4LnRvb2xzYCkuIFJldHVybnMgYHVuZGVmaW5lZGAgKG5vIHR1bm5lbCkgd2hlbiB0aGUgdG9rZW4gb3IgYVxuICogdXNhYmxlIGRvbWFpbiBpcyBhYnNlbnQuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlUG9ydHJDb25maWcob3B0czoge1xuICBwdWJsaWNEb21haW4/OiBzdHJpbmc7XG4gIHN1YmRvbWFpbj86IHN0cmluZztcbiAgdG9rZW4/OiBzdHJpbmc7XG4gIHBvcnQ6IG51bWJlcjtcbn0pOiBQb3J0ckNvbmZpZyB8IHVuZGVmaW5lZCB7XG4gIC8vIFBPUlRSXyogaXMgdXBzdHJlYW0gcG9ydHIncyBvd24gbmFtZXNwYWNlLCBzbyBpdCBrZWVwcyBpdHMgbmFtZS5cbiAgY29uc3QgdG9rZW4gPSBlbnYuc3RyaW5nKG9wdHMudG9rZW4sIFwiUE9SVFJfVE9LRU5cIik7XG4gIGNvbnN0IGRvbWFpbiA9IGVudi5zdHJpbmcob3B0cy5wdWJsaWNEb21haW4sIFBVQkxJQ19ET01BSU5fRU5WKTtcbiAgaWYgKCF0b2tlbikgcmV0dXJuIHVuZGVmaW5lZDtcbiAgbGV0IHN1YmRvbWFpbiA9IG9wdHMuc3ViZG9tYWluO1xuICBsZXQgc2VydmVyOiBzdHJpbmcgfCB1bmRlZmluZWQ7XG4gIGlmIChkb21haW4pIHtcbiAgICBzdWJkb21haW4gPz89IGRvbWFpbi5zcGxpdChcIi5cIilbMF07XG4gICAgc2VydmVyID0gZG9tYWluLnNsaWNlKGRvbWFpbi5pbmRleE9mKFwiLlwiKSArIDEpO1xuICB9XG4gIHNlcnZlciA/Pz0gZW52LnRleHQoXCJQT1JUUl9TRVJWRVJcIikgPz8gdW5kZWZpbmVkO1xuICBpZiAoIXN1YmRvbWFpbiB8fCAhc2VydmVyIHx8IHNlcnZlciA9PT0gZG9tYWluKSByZXR1cm4gdW5kZWZpbmVkO1xuICByZXR1cm4geyBzdWJkb21haW4sIHNlcnZlciwgdG9rZW4sIHBvcnQ6IG9wdHMucG9ydCB9O1xufVxuXG4vKiogVGhlIHdyaXRhYmxlIGhvbWUgcG9ydHIgaW5zdGFsbHMgKyBjb25maWd1cmVzIHVuZGVyIChBcHBzIGAkSE9NRWAgaXMgcmVhZC1vbmx5KS4gKi9cbmZ1bmN0aW9uIHBvcnRySG9tZSgpOiBzdHJpbmcge1xuICBjb25zdCBob21lID0gam9pbihwcm9jZXNzLmN3ZCgpLCBcIi5ob21lXCIpO1xuICBta2RpclN5bmMoam9pbihob21lLCBcIi5wb3J0clwiLCBcImJpblwiKSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gIHJldHVybiBob21lO1xufVxuXG4vKiogSW5zdGFsbCBwb3J0ciAoaWRlbXBvdGVudCkgaW50byB0aGUgY3dkLXJvb3RlZCBob21lIGFuZCByZXR1cm4gdGhlIGNoaWxkIGVudi4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpbnN0YWxsUG9ydHIoKTogTm9kZUpTLlByb2Nlc3NFbnYge1xuICBjb25zdCBob21lID0gcG9ydHJIb21lKCk7XG4gIGNvbnN0IGVudjogTm9kZUpTLlByb2Nlc3NFbnYgPSB7XG4gICAgLi4ucHJvY2Vzcy5lbnYsXG4gICAgSE9NRTogaG9tZSxcbiAgICBQT1JUUl9BVVRPX0FERF9QQVRIOiBcIm5vXCIsXG4gICAgUEFUSDogW2pvaW4oaG9tZSwgXCIucG9ydHJcIiwgXCJiaW5cIiksIHByb2Nlc3MuZW52LlBBVEggPz8gXCJcIl0uam9pbihkZWxpbWl0ZXIpLFxuICB9O1xuICBsb2dnZXIuaW5mbyhcImluc3RhbGxpbmcgcG9ydHIgKGlkZW1wb3RlbnQpXCIpO1xuICBjb25zdCByZXMgPSBzcGF3blN5bmMoXCJiYXNoXCIsIFtcIi1jXCIsIFwiY3VybCAtc1NmIGh0dHBzOi8vaW5zdGFsbC5wb3J0ci5kZXYgfCBzaFwiXSwge1xuICAgIGVudixcbiAgICBzdGRpbzogXCJpbmhlcml0XCIsXG4gIH0pO1xuICBpZiAocmVzLnN0YXR1cyAhPT0gMCkgdGhyb3cgbmV3IEVycm9yKFwicG9ydHIgaW5zdGFsbCBmYWlsZWRcIik7XG4gIHJldHVybiBlbnY7XG59XG5cbi8qKiBSZW5kZXIgYH4vLnBvcnRyL2NvbmZpZy55YW1sYCBmb3IgdGhlIHJlc29sdmVkIHR1bm5lbC4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3cml0ZVBvcnRyQ29uZmlnKGNvbmZpZzogUG9ydHJDb25maWcsIGVudjogTm9kZUpTLlByb2Nlc3NFbnYpOiB2b2lkIHtcbiAgY29uc3QgcGF0aCA9IGpvaW4oZW52LkhPTUUhLCBcIi5wb3J0clwiLCBcImNvbmZpZy55YW1sXCIpO1xuICB3cml0ZUZpbGVTeW5jKFxuICAgIHBhdGgsXG4gICAgW1xuICAgICAgYHNlcnZlcl91cmw6ICR7Y29uZmlnLnNlcnZlcn1gLFxuICAgICAgYHNzaF91cmw6ICR7Y29uZmlnLnNlcnZlcn06NDQ0NGAsXG4gICAgICBgc2VjcmV0X2tleTogJHtjb25maWcudG9rZW59YCxcbiAgICAgIFwiZGlzYWJsZV9kYXNoYm9hcmQ6IHRydWVcIixcbiAgICAgIFwiZGlzYWJsZV90dWk6IHRydWVcIixcbiAgICAgIFwidHVubmVsczpcIixcbiAgICAgIGAgIC0gbmFtZTogJHtjb25maWcuc3ViZG9tYWlufWAsXG4gICAgICBgICAgIHN1YmRvbWFpbjogJHtjb25maWcuc3ViZG9tYWlufWAsXG4gICAgICBgICAgIHBvcnQ6ICR7Y29uZmlnLnBvcnR9YCxcbiAgICAgIFwiXCIsXG4gICAgXS5qb2luKFwiXFxuXCIpLFxuICApO1xufVxuXG4vKiogTGF1bmNoIGBwb3J0ciBzdGFydGAgYXMgYSBjaGlsZCBwcm9jZXNzIChjYWxsZXIgc3VwZXJ2aXNlcyArIGtpbGxzIGl0KS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzdGFydFBvcnRyKGNvbmZpZzogUG9ydHJDb25maWcsIGVudjogTm9kZUpTLlByb2Nlc3NFbnYpOiBSZXR1cm5UeXBlPHR5cGVvZiBzcGF3bj4ge1xuICAvLyBSZWNsYWltIHRoZSBzdWJkb21haW4gZnJvbSBhbnkgcG9ydHIgbGVmdCBieSBhIHByZXZpb3VzIGJvb3QgaW4gdGhpcyBjb250YWluZXIuXG4gIHNwYXduU3luYyhcInBraWxsXCIsIFtcIi14XCIsIFwicG9ydHJcIl0sIHsgc3RkaW86IFwiaWdub3JlXCIgfSk7XG4gIGxvZ2dlci5pbmZvKGBwb3J0ciB0dW5uZWxpbmcgaHR0cHM6Ly8ke2NvbmZpZy5zdWJkb21haW59LiR7Y29uZmlnLnNlcnZlcn0gLT4gOiR7Y29uZmlnLnBvcnR9YCk7XG4gIHJldHVybiBzcGF3bihcInBvcnRyXCIsIFtcInN0YXJ0XCJdLCB7IGVudiwgc3RkaW86IFwiaW5oZXJpdFwiIH0pO1xufVxuXG4vKiogVHJ1ZSB3aGVuIHRoZSBpbnN0YWxsZWQgcG9ydHIgYmluYXJ5IHBhdGggZXhpc3RzIChwb3N0LWluc3RhbGwgc2FuaXR5KS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwb3J0ckluc3RhbGxlZChlbnY6IE5vZGVKUy5Qcm9jZXNzRW52KTogYm9vbGVhbiB7XG4gIHJldHVybiBleGlzdHNTeW5jKGpvaW4oZW52LkhPTUUhLCBcIi5wb3J0clwiLCBcImJpblwiLCBcInBvcnRyXCIpKTtcbn1cbiJdfQ==
|
package/lib/src/rate-limit.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A small in-memory fixed-window rate limiter for the email-OTP gate.
|
|
3
|
-
*
|
|
4
|
-
* Keyed by an arbitrary string (an email address or a client IP). Each key gets
|
|
5
|
-
* `max` hits per `windowMs`; the window resets on first use after it elapses.
|
|
6
|
-
* `hit()` returns whether the call is allowed and, when not, how many seconds
|
|
7
|
-
* until the window resets so a caller can surface a cooldown.
|
|
8
|
-
*
|
|
9
|
-
* In-memory is intentional and sufficient for a single-app-instance gate: an
|
|
10
|
-
* app behind a portr tunnel serves from one process. It is NOT a distributed
|
|
11
|
-
* limiter; a multi-replica deployment would need shared state. Entries are
|
|
12
|
-
* pruned lazily on access, so an idle key costs nothing after its window.
|
|
13
|
-
*
|
|
14
|
-
* @module
|
|
15
|
-
*/
|
|
16
|
-
/** A fixed-window rate limiter over string keys. */
|
|
17
|
-
export declare class RateLimiter {
|
|
18
|
-
private readonly max;
|
|
19
|
-
private readonly windowMs;
|
|
20
|
-
private readonly windows;
|
|
21
|
-
constructor(max: number, windowMs: number);
|
|
22
|
-
/**
|
|
23
|
-
* Record a hit for `key`. Returns `{ allowed }`, plus `retryAfter` (seconds)
|
|
24
|
-
* when the limit is exceeded. A limit of `<= 0` disables limiting (always
|
|
25
|
-
* allowed), which lets a config turn it off without special-casing callers.
|
|
26
|
-
*/
|
|
27
|
-
hit(key: string, now?: number): {
|
|
28
|
-
allowed: boolean;
|
|
29
|
-
retryAfter?: number;
|
|
30
|
-
};
|
|
31
|
-
/** Forget a key (e.g. clear a caller's window after a successful verify). */
|
|
32
|
-
reset(key: string): void;
|
|
33
|
-
/** Drop every window (tests). */
|
|
34
|
-
clear(): void;
|
|
35
|
-
}
|
package/lib/src/rate-limit.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A small in-memory fixed-window rate limiter for the email-OTP gate.
|
|
3
|
-
*
|
|
4
|
-
* Keyed by an arbitrary string (an email address or a client IP). Each key gets
|
|
5
|
-
* `max` hits per `windowMs`; the window resets on first use after it elapses.
|
|
6
|
-
* `hit()` returns whether the call is allowed and, when not, how many seconds
|
|
7
|
-
* until the window resets so a caller can surface a cooldown.
|
|
8
|
-
*
|
|
9
|
-
* In-memory is intentional and sufficient for a single-app-instance gate: an
|
|
10
|
-
* app behind a portr tunnel serves from one process. It is NOT a distributed
|
|
11
|
-
* limiter; a multi-replica deployment would need shared state. Entries are
|
|
12
|
-
* pruned lazily on access, so an idle key costs nothing after its window.
|
|
13
|
-
*
|
|
14
|
-
* @module
|
|
15
|
-
*/
|
|
16
|
-
/** A fixed-window rate limiter over string keys. */
|
|
17
|
-
export class RateLimiter {
|
|
18
|
-
max;
|
|
19
|
-
windowMs;
|
|
20
|
-
windows = new Map();
|
|
21
|
-
constructor(max, windowMs) {
|
|
22
|
-
this.max = max;
|
|
23
|
-
this.windowMs = windowMs;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Record a hit for `key`. Returns `{ allowed }`, plus `retryAfter` (seconds)
|
|
27
|
-
* when the limit is exceeded. A limit of `<= 0` disables limiting (always
|
|
28
|
-
* allowed), which lets a config turn it off without special-casing callers.
|
|
29
|
-
*/
|
|
30
|
-
hit(key, now = Date.now()) {
|
|
31
|
-
if (this.max <= 0)
|
|
32
|
-
return { allowed: true };
|
|
33
|
-
const existing = this.windows.get(key);
|
|
34
|
-
if (!existing || now >= existing.resetAt) {
|
|
35
|
-
this.windows.set(key, { count: 1, resetAt: now + this.windowMs });
|
|
36
|
-
return { allowed: true };
|
|
37
|
-
}
|
|
38
|
-
if (existing.count < this.max) {
|
|
39
|
-
existing.count += 1;
|
|
40
|
-
return { allowed: true };
|
|
41
|
-
}
|
|
42
|
-
return { allowed: false, retryAfter: Math.ceil((existing.resetAt - now) / 1000) };
|
|
43
|
-
}
|
|
44
|
-
/** Forget a key (e.g. clear a caller's window after a successful verify). */
|
|
45
|
-
reset(key) {
|
|
46
|
-
this.windows.delete(key);
|
|
47
|
-
}
|
|
48
|
-
/** Drop every window (tests). */
|
|
49
|
-
clear() {
|
|
50
|
-
this.windows.clear();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmF0ZS1saW1pdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yYXRlLWxpbWl0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBT0gsb0RBQW9EO0FBQ3BELE1BQU0sT0FBTyxXQUFXO0lBSUg7SUFDQTtJQUpGLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQztJQUVyRCxZQUNtQixHQUFXLEVBQ1gsUUFBZ0I7UUFEaEIsUUFBRyxHQUFILEdBQUcsQ0FBUTtRQUNYLGFBQVEsR0FBUixRQUFRLENBQVE7SUFDaEMsQ0FBQztJQUVKOzs7O09BSUc7SUFDSCxHQUFHLENBQUMsR0FBVyxFQUFFLE1BQWMsSUFBSSxDQUFDLEdBQUcsRUFBRTtRQUN2QyxJQUFJLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQztZQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDNUMsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDdkMsSUFBSSxDQUFDLFFBQVEsSUFBSSxHQUFHLElBQUksUUFBUSxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ3pDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxFQUFFLEtBQUssRUFBRSxDQUFDLEVBQUUsT0FBTyxFQUFFLEdBQUcsR0FBRyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztZQUNsRSxPQUFPLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxDQUFDO1FBQzNCLENBQUM7UUFDRCxJQUFJLFFBQVEsQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1lBQzlCLFFBQVEsQ0FBQyxLQUFLLElBQUksQ0FBQyxDQUFDO1lBQ3BCLE9BQU8sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDM0IsQ0FBQztRQUNELE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sR0FBRyxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUMsRUFBRSxDQUFDO0lBQ3BGLENBQUM7SUFFRCw2RUFBNkU7SUFDN0UsS0FBSyxDQUFDLEdBQVc7UUFDZixJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUMzQixDQUFDO0lBRUQsaUNBQWlDO0lBQ2pDLEtBQUs7UUFDSCxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ3ZCLENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQSBzbWFsbCBpbi1tZW1vcnkgZml4ZWQtd2luZG93IHJhdGUgbGltaXRlciBmb3IgdGhlIGVtYWlsLU9UUCBnYXRlLlxuICpcbiAqIEtleWVkIGJ5IGFuIGFyYml0cmFyeSBzdHJpbmcgKGFuIGVtYWlsIGFkZHJlc3Mgb3IgYSBjbGllbnQgSVApLiBFYWNoIGtleSBnZXRzXG4gKiBgbWF4YCBoaXRzIHBlciBgd2luZG93TXNgOyB0aGUgd2luZG93IHJlc2V0cyBvbiBmaXJzdCB1c2UgYWZ0ZXIgaXQgZWxhcHNlcy5cbiAqIGBoaXQoKWAgcmV0dXJucyB3aGV0aGVyIHRoZSBjYWxsIGlzIGFsbG93ZWQgYW5kLCB3aGVuIG5vdCwgaG93IG1hbnkgc2Vjb25kc1xuICogdW50aWwgdGhlIHdpbmRvdyByZXNldHMgc28gYSBjYWxsZXIgY2FuIHN1cmZhY2UgYSBjb29sZG93bi5cbiAqXG4gKiBJbi1tZW1vcnkgaXMgaW50ZW50aW9uYWwgYW5kIHN1ZmZpY2llbnQgZm9yIGEgc2luZ2xlLWFwcC1pbnN0YW5jZSBnYXRlOiBhblxuICogYXBwIGJlaGluZCBhIHBvcnRyIHR1bm5lbCBzZXJ2ZXMgZnJvbSBvbmUgcHJvY2Vzcy4gSXQgaXMgTk9UIGEgZGlzdHJpYnV0ZWRcbiAqIGxpbWl0ZXI7IGEgbXVsdGktcmVwbGljYSBkZXBsb3ltZW50IHdvdWxkIG5lZWQgc2hhcmVkIHN0YXRlLiBFbnRyaWVzIGFyZVxuICogcHJ1bmVkIGxhemlseSBvbiBhY2Nlc3MsIHNvIGFuIGlkbGUga2V5IGNvc3RzIG5vdGhpbmcgYWZ0ZXIgaXRzIHdpbmRvdy5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW50ZXJmYWNlIFdpbmRvdyB7XG4gIGNvdW50OiBudW1iZXI7XG4gIHJlc2V0QXQ6IG51bWJlcjtcbn1cblxuLyoqIEEgZml4ZWQtd2luZG93IHJhdGUgbGltaXRlciBvdmVyIHN0cmluZyBrZXlzLiAqL1xuZXhwb3J0IGNsYXNzIFJhdGVMaW1pdGVyIHtcbiAgcHJpdmF0ZSByZWFkb25seSB3aW5kb3dzID0gbmV3IE1hcDxzdHJpbmcsIFdpbmRvdz4oKTtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHJlYWRvbmx5IG1heDogbnVtYmVyLFxuICAgIHByaXZhdGUgcmVhZG9ubHkgd2luZG93TXM6IG51bWJlcixcbiAgKSB7fVxuXG4gIC8qKlxuICAgKiBSZWNvcmQgYSBoaXQgZm9yIGBrZXlgLiBSZXR1cm5zIGB7IGFsbG93ZWQgfWAsIHBsdXMgYHJldHJ5QWZ0ZXJgIChzZWNvbmRzKVxuICAgKiB3aGVuIHRoZSBsaW1pdCBpcyBleGNlZWRlZC4gQSBsaW1pdCBvZiBgPD0gMGAgZGlzYWJsZXMgbGltaXRpbmcgKGFsd2F5c1xuICAgKiBhbGxvd2VkKSwgd2hpY2ggbGV0cyBhIGNvbmZpZyB0dXJuIGl0IG9mZiB3aXRob3V0IHNwZWNpYWwtY2FzaW5nIGNhbGxlcnMuXG4gICAqL1xuICBoaXQoa2V5OiBzdHJpbmcsIG5vdzogbnVtYmVyID0gRGF0ZS5ub3coKSk6IHsgYWxsb3dlZDogYm9vbGVhbjsgcmV0cnlBZnRlcj86IG51bWJlciB9IHtcbiAgICBpZiAodGhpcy5tYXggPD0gMCkgcmV0dXJuIHsgYWxsb3dlZDogdHJ1ZSB9O1xuICAgIGNvbnN0IGV4aXN0aW5nID0gdGhpcy53aW5kb3dzLmdldChrZXkpO1xuICAgIGlmICghZXhpc3RpbmcgfHwgbm93ID49IGV4aXN0aW5nLnJlc2V0QXQpIHtcbiAgICAgIHRoaXMud2luZG93cy5zZXQoa2V5LCB7IGNvdW50OiAxLCByZXNldEF0OiBub3cgKyB0aGlzLndpbmRvd01zIH0pO1xuICAgICAgcmV0dXJuIHsgYWxsb3dlZDogdHJ1ZSB9O1xuICAgIH1cbiAgICBpZiAoZXhpc3RpbmcuY291bnQgPCB0aGlzLm1heCkge1xuICAgICAgZXhpc3RpbmcuY291bnQgKz0gMTtcbiAgICAgIHJldHVybiB7IGFsbG93ZWQ6IHRydWUgfTtcbiAgICB9XG4gICAgcmV0dXJuIHsgYWxsb3dlZDogZmFsc2UsIHJldHJ5QWZ0ZXI6IE1hdGguY2VpbCgoZXhpc3RpbmcucmVzZXRBdCAtIG5vdykgLyAxMDAwKSB9O1xuICB9XG5cbiAgLyoqIEZvcmdldCBhIGtleSAoZS5nLiBjbGVhciBhIGNhbGxlcidzIHdpbmRvdyBhZnRlciBhIHN1Y2Nlc3NmdWwgdmVyaWZ5KS4gKi9cbiAgcmVzZXQoa2V5OiBzdHJpbmcpOiB2b2lkIHtcbiAgICB0aGlzLndpbmRvd3MuZGVsZXRlKGtleSk7XG4gIH1cblxuICAvKiogRHJvcCBldmVyeSB3aW5kb3cgKHRlc3RzKS4gKi9cbiAgY2xlYXIoKTogdm9pZCB7XG4gICAgdGhpcy53aW5kb3dzLmNsZWFyKCk7XG4gIH1cbn1cbiJdfQ==
|