@dbx-tools/tunnel 0.6.60
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 +373 -0
- package/index.ts +28 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +21 -0
- package/lib/src/allowlist.d.ts +32 -0
- package/lib/src/allowlist.js +57 -0
- package/lib/src/app.d.ts +130 -0
- package/lib/src/app.js +258 -0
- package/lib/src/env.d.ts +57 -0
- package/lib/src/env.js +60 -0
- package/lib/src/headers.d.ts +108 -0
- package/lib/src/headers.js +140 -0
- package/lib/src/interceptor.d.ts +56 -0
- package/lib/src/interceptor.js +84 -0
- package/lib/src/otp.d.ts +49 -0
- package/lib/src/otp.js +124 -0
- package/lib/src/plugin.d.ts +147 -0
- package/lib/src/plugin.js +138 -0
- package/lib/src/portr.d.ts +40 -0
- package/lib/src/portr.js +93 -0
- package/lib/src/proxy.d.ts +49 -0
- package/lib/src/proxy.js +247 -0
- package/lib/src/rate-limit.d.ts +35 -0
- package/lib/src/rate-limit.js +53 -0
- package/lib/src/signing-key.d.ts +86 -0
- package/lib/src/signing-key.js +170 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +70 -0
- package/src/allowlist.ts +60 -0
- package/src/app.ts +292 -0
- package/src/env.ts +72 -0
- package/src/headers.ts +155 -0
- package/src/interceptor.ts +105 -0
- package/src/otp.ts +137 -0
- package/src/plugin.ts +269 -0
- package/src/portr.ts +113 -0
- package/src/proxy.ts +299 -0
- package/src/rate-limit.ts +59 -0
- package/src/signing-key.ts +201 -0
package/lib/src/app.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tunnel gate's tiny AppKit "app" - `createApp` WITHOUT a `server()` plugin.
|
|
3
|
+
*
|
|
4
|
+
* There is no HTTP server here: the tunnel proxy is the server, and it calls the
|
|
5
|
+
* gate handlers in-process. `createApp` is used only for what it auto-wires:
|
|
6
|
+
* - `CacheManager` (Lakebase when this process can reach it, else memory) -
|
|
7
|
+
* which the OTP `CodeStore` and the session signing key use for storage + TTL
|
|
8
|
+
* eviction;
|
|
9
|
+
* - the `email()` transport (SMTP / outbox), primed from env, so `sendCode` can
|
|
10
|
+
* deliver the one-time code;
|
|
11
|
+
* - dbx-tools auto-configuration + branding via `@dbx-tools/appkit`.
|
|
12
|
+
*
|
|
13
|
+
* It returns the {@link AuthGateApi} the proxy drives. `sendCode` is wired here
|
|
14
|
+
* (the plugin can't resolve HOW to send on its own) to the email plugin's
|
|
15
|
+
* transport. A sign-in code is SYSTEM mail - no on-behalf-of user asked for it
|
|
16
|
+
* and no reply to it reaches anyone - so it sends from the email config's
|
|
17
|
+
* do-not-reply address (`no-reply@EMAIL_DOMAIN` unless EMAIL_SYSTEM_FROM names
|
|
18
|
+
* another).
|
|
19
|
+
*
|
|
20
|
+
* FAIL FAST: a gate that can't email a code is useless, so if email does not
|
|
21
|
+
* resolve to SMTP mode (real delivery), this throws - unless `insecure` is set
|
|
22
|
+
* (`--insecure` / `TUNNEL_INSECURE=true`), in which case the caller runs the
|
|
23
|
+
* tunnel OPEN with no gate.
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
import { type AuthGateApi, type AuthGateConfig, type SendCodeOptions } from "./plugin.ts";
|
|
28
|
+
/**
|
|
29
|
+
* A code TTL as the plain phrase the email states ("10 minutes", "45 seconds").
|
|
30
|
+
*
|
|
31
|
+
* Whole minutes read as minutes; anything else stays in seconds rather than
|
|
32
|
+
* rounding, so a 90-second TTL is not advertised as "1 minute" and a recipient is
|
|
33
|
+
* never told the code lives longer than it does.
|
|
34
|
+
*/
|
|
35
|
+
export declare function expiresIn(seconds: number): string;
|
|
36
|
+
/** The parts of {@link SendCodeOptions} the code email's copy is built from. */
|
|
37
|
+
type CodeCopy = Pick<SendCodeOptions, "message" | "codeTtlSeconds">;
|
|
38
|
+
/**
|
|
39
|
+
* The HTML part's source: the full branded template, with the code as a large
|
|
40
|
+
* styled heading (`## ` is what makes it prominent in an inbox).
|
|
41
|
+
*/
|
|
42
|
+
export declare function codeEmailHtmlBody(code: string, opts: CodeCopy): string;
|
|
43
|
+
/**
|
|
44
|
+
* The `text/plain` part, supplied EXPLICITLY rather than rendered from the tree
|
|
45
|
+
* above. Both parts say the same thing; only the line layout differs.
|
|
46
|
+
*
|
|
47
|
+
* The prompt and the code share ONE line ("Your verification code is: 123456").
|
|
48
|
+
* That single-line shape is what iOS, Gmail, Outlook, and Android code detection
|
|
49
|
+
* keys on most reliably - the heuristics look for a code in the same sentence as
|
|
50
|
+
* a recognized prompt, so splitting them across lines makes detection dependent
|
|
51
|
+
* on the client, and any blank line between them defeats it outright.
|
|
52
|
+
*
|
|
53
|
+
* The GENERATED text part cannot hold that shape at all: it is a rendering of the
|
|
54
|
+
* HTML, so it carries the brand header/footer and turns the code heading's CSS
|
|
55
|
+
* margin into blank lines, arriving as `prompt\n\n\ncode`.
|
|
56
|
+
*
|
|
57
|
+
* The code is visible text in BOTH parts, never an image, so a client scraping
|
|
58
|
+
* either one finds it. No trailer line follows the copy - Apple's domain-bound
|
|
59
|
+
* `@domain #code` footer is deliberately NOT emitted, since it constrains the
|
|
60
|
+
* code to one origin and is not what the broadly-compatible shape needs.
|
|
61
|
+
*/
|
|
62
|
+
export declare function codeEmailTextBody(code: string, opts: CodeCopy): string;
|
|
63
|
+
/**
|
|
64
|
+
* The SUBJECT line, with the code in it: `"123456 is your verification code"`.
|
|
65
|
+
*
|
|
66
|
+
* The code has to be here, not only in the body, because of what mobile autofill
|
|
67
|
+
* actually reads. iOS offers a code from an incoming NOTIFICATION - natively for
|
|
68
|
+
* Messages and Mail, and since iOS 26 for any app's notification text, which is
|
|
69
|
+
* what finally made Gmail work - and a notification contains the sender, the
|
|
70
|
+
* subject, and a short snippet. Nothing else. A code that lives in the body is
|
|
71
|
+
* invisible to it, however cleanly the body is formatted, which is why a perfectly
|
|
72
|
+
* shaped `text/plain` part still produced no autofill prompt in Gmail.
|
|
73
|
+
*
|
|
74
|
+
* `<code> is your <thing>` rather than `<thing>: <code>` because the leading code
|
|
75
|
+
* survives TRUNCATION: a notification and an inbox list both cut the subject, and
|
|
76
|
+
* the platform heuristics want the code in the same sentence as a recognized
|
|
77
|
+
* prompt ("code", "verification"). Putting it first keeps both intact no matter
|
|
78
|
+
* where the cut lands.
|
|
79
|
+
*
|
|
80
|
+
* `subject` is the configured line ("Your verification code"), lower-cased at its
|
|
81
|
+
* first word so the sentence reads naturally, and left ALONE when it does not look
|
|
82
|
+
* like the conventional phrasing - an operator who set a deliberate subject gets
|
|
83
|
+
* theirs with the code prefixed, not a mangled hybrid.
|
|
84
|
+
*/
|
|
85
|
+
export declare function codeEmailSubject(code: string, subject: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* The PREHEADER: the snippet beside the subject in an inbox list, and the body of
|
|
88
|
+
* the push notification. Carries the code for the same reason the subject does -
|
|
89
|
+
* it is the other half of what a notification shows - and repeats the prompt
|
|
90
|
+
* wording so a heuristic scanning the snippet alone finds a code next to a phrase
|
|
91
|
+
* it recognizes.
|
|
92
|
+
*/
|
|
93
|
+
export declare function codeEmailPreview(code: string, opts: CodeCopy): string;
|
|
94
|
+
/**
|
|
95
|
+
* Fill in the Lakebase connection env AppKit's cache needs, so `CacheManager`
|
|
96
|
+
* chooses PERSISTENT storage instead of memory.
|
|
97
|
+
*
|
|
98
|
+
* This is what makes the gate's session signing key and outstanding one-time
|
|
99
|
+
* codes survive a restart. `applyLakebaseEnv` is the SHARED helper AppKit
|
|
100
|
+
* auto-configuration uses, so the gate gets exactly the env a pool needs -
|
|
101
|
+
* `LAKEBASE_ENDPOINT`, `PGHOST`, `PGDATABASE`, and `PGUSER` - rather than a
|
|
102
|
+
* hand-rolled subset. All four matter: `createLakebasePool()` throws without any
|
|
103
|
+
* one of them, and a Databricks App `postgres` resource binding supplies only the
|
|
104
|
+
* first. Without them the pool cannot be built, the cache silently degrades to
|
|
105
|
+
* in-memory, and every redeploy signs out every user (the exact symptom this
|
|
106
|
+
* exists to prevent).
|
|
107
|
+
*
|
|
108
|
+
* It is called here rather than left to `createApp`'s `autoConfigure` because that
|
|
109
|
+
* gates on a `lakebase()` plugin being registered - and this app registers none,
|
|
110
|
+
* having no server to mount Lakebase routes on. Calling the helper directly also
|
|
111
|
+
* lets the gate be stricter than `autoConfigure` is:
|
|
112
|
+
*
|
|
113
|
+
* - It runs ONLY when a Lakebase env var is present. A tunnel is a wrapper
|
|
114
|
+
* around someone else's app and must not invent infrastructure, so with
|
|
115
|
+
* nothing bound it skips rather than falling through the resolver's
|
|
116
|
+
* list-or-CREATE-a-project path.
|
|
117
|
+
* - `autoCreate: false` for the same reason, in case a project happens to exist.
|
|
118
|
+
* - Every failure is a WARNING, never a throw. The cache is an optimization for
|
|
119
|
+
* session durability; admission still requires a code delivered to an
|
|
120
|
+
* allow-listed address, so a gate with a memory cache is safe, just forgetful.
|
|
121
|
+
*/
|
|
122
|
+
export declare function resolveCacheStorageEnv(): Promise<boolean>;
|
|
123
|
+
/**
|
|
124
|
+
* Boot the gate app and return the API the proxy calls. Throws when email is not
|
|
125
|
+
* in SMTP mode (no way to deliver a code) so a misconfigured gate fails fast at
|
|
126
|
+
* startup rather than silently accepting nobody; the caller may catch this and
|
|
127
|
+
* fall back to insecure/open mode when the operator opted in.
|
|
128
|
+
*/
|
|
129
|
+
export declare function startGateApp(config: AuthGateConfig): Promise<AuthGateApi>;
|
|
130
|
+
export {};
|
package/lib/src/app.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tunnel gate's tiny AppKit "app" - `createApp` WITHOUT a `server()` plugin.
|
|
3
|
+
*
|
|
4
|
+
* There is no HTTP server here: the tunnel proxy is the server, and it calls the
|
|
5
|
+
* gate handlers in-process. `createApp` is used only for what it auto-wires:
|
|
6
|
+
* - `CacheManager` (Lakebase when this process can reach it, else memory) -
|
|
7
|
+
* which the OTP `CodeStore` and the session signing key use for storage + TTL
|
|
8
|
+
* eviction;
|
|
9
|
+
* - the `email()` transport (SMTP / outbox), primed from env, so `sendCode` can
|
|
10
|
+
* deliver the one-time code;
|
|
11
|
+
* - dbx-tools auto-configuration + branding via `@dbx-tools/appkit`.
|
|
12
|
+
*
|
|
13
|
+
* It returns the {@link AuthGateApi} the proxy drives. `sendCode` is wired here
|
|
14
|
+
* (the plugin can't resolve HOW to send on its own) to the email plugin's
|
|
15
|
+
* transport. A sign-in code is SYSTEM mail - no on-behalf-of user asked for it
|
|
16
|
+
* and no reply to it reaches anyone - so it sends from the email config's
|
|
17
|
+
* do-not-reply address (`no-reply@EMAIL_DOMAIN` unless EMAIL_SYSTEM_FROM names
|
|
18
|
+
* another).
|
|
19
|
+
*
|
|
20
|
+
* FAIL FAST: a gate that can't email a code is useless, so if email does not
|
|
21
|
+
* resolve to SMTP mode (real delivery), this throws - unless `insecure` is set
|
|
22
|
+
* (`--insecure` / `TUNNEL_INSECURE=true`), in which case the caller runs the
|
|
23
|
+
* tunnel OPEN with no gate.
|
|
24
|
+
*
|
|
25
|
+
* @module
|
|
26
|
+
*/
|
|
27
|
+
import { createApp as createAppNs, lakebaseResolver } from "@dbx-tools/appkit";
|
|
28
|
+
import { brand as nodeBrand } from "@dbx-tools/core";
|
|
29
|
+
import { brand as emailBrand, email, sender, transport } from "@dbx-tools/email";
|
|
30
|
+
import { env, log, string } from "@dbx-tools/shared-core";
|
|
31
|
+
import { authGate } from "./plugin.js";
|
|
32
|
+
const logger = log.logger("tunnel:app");
|
|
33
|
+
/**
|
|
34
|
+
* A code TTL as the plain phrase the email states ("10 minutes", "45 seconds").
|
|
35
|
+
*
|
|
36
|
+
* Whole minutes read as minutes; anything else stays in seconds rather than
|
|
37
|
+
* rounding, so a 90-second TTL is not advertised as "1 minute" and a recipient is
|
|
38
|
+
* never told the code lives longer than it does.
|
|
39
|
+
*/
|
|
40
|
+
export function expiresIn(seconds) {
|
|
41
|
+
return seconds >= 60 && seconds % 60 === 0
|
|
42
|
+
? string.pluralize(seconds / 60, "minute")
|
|
43
|
+
: string.pluralize(seconds, "second");
|
|
44
|
+
}
|
|
45
|
+
/** The reassurance line closing both parts. */
|
|
46
|
+
const IGNORE_LINE = "If you did not request this code, you can ignore this email.";
|
|
47
|
+
/**
|
|
48
|
+
* The HTML part's source: the full branded template, with the code as a large
|
|
49
|
+
* styled heading (`## ` is what makes it prominent in an inbox).
|
|
50
|
+
*/
|
|
51
|
+
export function codeEmailHtmlBody(code, opts) {
|
|
52
|
+
return [
|
|
53
|
+
opts.message,
|
|
54
|
+
"",
|
|
55
|
+
`## ${code}`,
|
|
56
|
+
"",
|
|
57
|
+
`This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
|
|
58
|
+
"",
|
|
59
|
+
IGNORE_LINE,
|
|
60
|
+
].join("\n");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The `text/plain` part, supplied EXPLICITLY rather than rendered from the tree
|
|
64
|
+
* above. Both parts say the same thing; only the line layout differs.
|
|
65
|
+
*
|
|
66
|
+
* The prompt and the code share ONE line ("Your verification code is: 123456").
|
|
67
|
+
* That single-line shape is what iOS, Gmail, Outlook, and Android code detection
|
|
68
|
+
* keys on most reliably - the heuristics look for a code in the same sentence as
|
|
69
|
+
* a recognized prompt, so splitting them across lines makes detection dependent
|
|
70
|
+
* on the client, and any blank line between them defeats it outright.
|
|
71
|
+
*
|
|
72
|
+
* The GENERATED text part cannot hold that shape at all: it is a rendering of the
|
|
73
|
+
* HTML, so it carries the brand header/footer and turns the code heading's CSS
|
|
74
|
+
* margin into blank lines, arriving as `prompt\n\n\ncode`.
|
|
75
|
+
*
|
|
76
|
+
* The code is visible text in BOTH parts, never an image, so a client scraping
|
|
77
|
+
* either one finds it. No trailer line follows the copy - Apple's domain-bound
|
|
78
|
+
* `@domain #code` footer is deliberately NOT emitted, since it constrains the
|
|
79
|
+
* code to one origin and is not what the broadly-compatible shape needs.
|
|
80
|
+
*/
|
|
81
|
+
export function codeEmailTextBody(code, opts) {
|
|
82
|
+
return [
|
|
83
|
+
`${opts.message} ${code}`,
|
|
84
|
+
`This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
|
|
85
|
+
"",
|
|
86
|
+
IGNORE_LINE,
|
|
87
|
+
].join("\n");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The SUBJECT line, with the code in it: `"123456 is your verification code"`.
|
|
91
|
+
*
|
|
92
|
+
* The code has to be here, not only in the body, because of what mobile autofill
|
|
93
|
+
* actually reads. iOS offers a code from an incoming NOTIFICATION - natively for
|
|
94
|
+
* Messages and Mail, and since iOS 26 for any app's notification text, which is
|
|
95
|
+
* what finally made Gmail work - and a notification contains the sender, the
|
|
96
|
+
* subject, and a short snippet. Nothing else. A code that lives in the body is
|
|
97
|
+
* invisible to it, however cleanly the body is formatted, which is why a perfectly
|
|
98
|
+
* shaped `text/plain` part still produced no autofill prompt in Gmail.
|
|
99
|
+
*
|
|
100
|
+
* `<code> is your <thing>` rather than `<thing>: <code>` because the leading code
|
|
101
|
+
* survives TRUNCATION: a notification and an inbox list both cut the subject, and
|
|
102
|
+
* the platform heuristics want the code in the same sentence as a recognized
|
|
103
|
+
* prompt ("code", "verification"). Putting it first keeps both intact no matter
|
|
104
|
+
* where the cut lands.
|
|
105
|
+
*
|
|
106
|
+
* `subject` is the configured line ("Your verification code"), lower-cased at its
|
|
107
|
+
* first word so the sentence reads naturally, and left ALONE when it does not look
|
|
108
|
+
* like the conventional phrasing - an operator who set a deliberate subject gets
|
|
109
|
+
* theirs with the code prefixed, not a mangled hybrid.
|
|
110
|
+
*/
|
|
111
|
+
export function codeEmailSubject(code, subject) {
|
|
112
|
+
const trimmed = subject.trim();
|
|
113
|
+
const conventional = /^your\s+/i.exec(trimmed);
|
|
114
|
+
const rest = conventional ? trimmed.slice(conventional[0].length) : trimmed;
|
|
115
|
+
return conventional ? `${code} is your ${rest}` : `${code} - ${trimmed}`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The PREHEADER: the snippet beside the subject in an inbox list, and the body of
|
|
119
|
+
* the push notification. Carries the code for the same reason the subject does -
|
|
120
|
+
* it is the other half of what a notification shows - and repeats the prompt
|
|
121
|
+
* wording so a heuristic scanning the snippet alone finds a code next to a phrase
|
|
122
|
+
* it recognizes.
|
|
123
|
+
*/
|
|
124
|
+
export function codeEmailPreview(code, opts) {
|
|
125
|
+
return `${opts.message} ${code}`;
|
|
126
|
+
}
|
|
127
|
+
const { createApp } = createAppNs;
|
|
128
|
+
const { applyLakebaseEnv } = lakebaseResolver;
|
|
129
|
+
const { resolveSystemSenderAddress } = sender;
|
|
130
|
+
const { getEmailRuntime, sendEmail } = transport;
|
|
131
|
+
const { emailBrandFromContext } = emailBrand;
|
|
132
|
+
const { loadBrandContext } = nodeBrand;
|
|
133
|
+
/**
|
|
134
|
+
* Env vars whose presence means a Lakebase database was bound to this deployment.
|
|
135
|
+
* `LAKEBASE_ENDPOINT` is what a Databricks App `postgres` resource binding sets;
|
|
136
|
+
* `PGHOST` is the local/manual spelling.
|
|
137
|
+
*/
|
|
138
|
+
const LAKEBASE_ENV = ["LAKEBASE_ENDPOINT", "PGHOST"];
|
|
139
|
+
/**
|
|
140
|
+
* Upper bound on resolving the Lakebase connection. The gate is in the request
|
|
141
|
+
* path for every visitor, so a slow workspace API must not hold up boot
|
|
142
|
+
* indefinitely; a memory cache is a degraded gate, an unbooted one is no gate.
|
|
143
|
+
*/
|
|
144
|
+
const LAKEBASE_RESOLVE_TIMEOUT_MS = 60_000;
|
|
145
|
+
/**
|
|
146
|
+
* Fill in the Lakebase connection env AppKit's cache needs, so `CacheManager`
|
|
147
|
+
* chooses PERSISTENT storage instead of memory.
|
|
148
|
+
*
|
|
149
|
+
* This is what makes the gate's session signing key and outstanding one-time
|
|
150
|
+
* codes survive a restart. `applyLakebaseEnv` is the SHARED helper AppKit
|
|
151
|
+
* auto-configuration uses, so the gate gets exactly the env a pool needs -
|
|
152
|
+
* `LAKEBASE_ENDPOINT`, `PGHOST`, `PGDATABASE`, and `PGUSER` - rather than a
|
|
153
|
+
* hand-rolled subset. All four matter: `createLakebasePool()` throws without any
|
|
154
|
+
* one of them, and a Databricks App `postgres` resource binding supplies only the
|
|
155
|
+
* first. Without them the pool cannot be built, the cache silently degrades to
|
|
156
|
+
* in-memory, and every redeploy signs out every user (the exact symptom this
|
|
157
|
+
* exists to prevent).
|
|
158
|
+
*
|
|
159
|
+
* It is called here rather than left to `createApp`'s `autoConfigure` because that
|
|
160
|
+
* gates on a `lakebase()` plugin being registered - and this app registers none,
|
|
161
|
+
* having no server to mount Lakebase routes on. Calling the helper directly also
|
|
162
|
+
* lets the gate be stricter than `autoConfigure` is:
|
|
163
|
+
*
|
|
164
|
+
* - It runs ONLY when a Lakebase env var is present. A tunnel is a wrapper
|
|
165
|
+
* around someone else's app and must not invent infrastructure, so with
|
|
166
|
+
* nothing bound it skips rather than falling through the resolver's
|
|
167
|
+
* list-or-CREATE-a-project path.
|
|
168
|
+
* - `autoCreate: false` for the same reason, in case a project happens to exist.
|
|
169
|
+
* - Every failure is a WARNING, never a throw. The cache is an optimization for
|
|
170
|
+
* session durability; admission still requires a code delivered to an
|
|
171
|
+
* allow-listed address, so a gate with a memory cache is safe, just forgetful.
|
|
172
|
+
*/
|
|
173
|
+
export async function resolveCacheStorageEnv() {
|
|
174
|
+
if (!env.text(LAKEBASE_ENV)) {
|
|
175
|
+
logger.info(`no ${env.name(LAKEBASE_ENV)} - the gate cache stays in memory; sessions and codes will not survive a restart`);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
const { resolved, user } = await applyLakebaseEnv({ autoCreate: false }, AbortSignal.timeout(LAKEBASE_RESOLVE_TIMEOUT_MS));
|
|
180
|
+
logger.info("lakebase resolved for the gate cache", {
|
|
181
|
+
host: resolved.host,
|
|
182
|
+
database: resolved.database,
|
|
183
|
+
endpoint: resolved.endpoint,
|
|
184
|
+
user,
|
|
185
|
+
});
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
logger.warn("could not resolve lakebase - the gate cache stays in memory; sessions and codes will not survive a restart", { error });
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Boot the gate app and return the API the proxy calls. Throws when email is not
|
|
195
|
+
* in SMTP mode (no way to deliver a code) so a misconfigured gate fails fast at
|
|
196
|
+
* startup rather than silently accepting nobody; the caller may catch this and
|
|
197
|
+
* fall back to insecure/open mode when the operator opted in.
|
|
198
|
+
*/
|
|
199
|
+
export async function startGateApp(config) {
|
|
200
|
+
// The host app's own brand (`branding/brand.yaml` discovered from cwd) or the
|
|
201
|
+
// dbx-tools default. This is the ONE brand source for the gate: it styles the
|
|
202
|
+
// code email (accent band, font, logo) via the email plugin AND supplies the
|
|
203
|
+
// display name the copy uses, so a deployment that themes its app themes its
|
|
204
|
+
// sign-in email with it. An explicit `brandName` still wins (see below).
|
|
205
|
+
const context = await loadBrandContext();
|
|
206
|
+
// Before `createApp`, because AppKit resolves the cache's storage during it and
|
|
207
|
+
// reads the connection out of `process.env` when it does. See the function's
|
|
208
|
+
// docs for why the gate resolves this itself.
|
|
209
|
+
await resolveCacheStorageEnv();
|
|
210
|
+
// `sendCode` delivers the OTP through the email plugin's SHARED transport, which
|
|
211
|
+
// the `email()` plugin primes during its `setup()` (awaited by `createApp`
|
|
212
|
+
// below). Using the module-level `sendEmail` avoids a circular dependency on the
|
|
213
|
+
// app handle's inferred type. The `From` is the SYSTEM sender: a verification
|
|
214
|
+
// code is machine-generated and unanswerable, so it must not arrive from a
|
|
215
|
+
// person's address inviting a reply.
|
|
216
|
+
const sendCode = async (to, code, opts) => {
|
|
217
|
+
const from = resolveSystemSenderAddress(getEmailRuntime().config);
|
|
218
|
+
await sendEmail({
|
|
219
|
+
to: [to],
|
|
220
|
+
// The code rides in the SUBJECT and the preheader, not just the body:
|
|
221
|
+
// those two strings are the whole of a push notification, and a
|
|
222
|
+
// notification is what mobile autofill reads. See `codeEmailSubject`.
|
|
223
|
+
subject: codeEmailSubject(code, opts.subject),
|
|
224
|
+
body: codeEmailHtmlBody(code, opts),
|
|
225
|
+
}, from, undefined, { text: codeEmailTextBody(code, opts), preview: codeEmailPreview(code, opts) });
|
|
226
|
+
};
|
|
227
|
+
// `createApp` namespaces each plugin's exports on the handle by manifest name;
|
|
228
|
+
// `handle.authGate` is the in-process gate API the proxy drives.
|
|
229
|
+
//
|
|
230
|
+
// `autoConfigure: false` because {@link resolveCacheStorageEnv} above already
|
|
231
|
+
// ran the one piece of it this app wants (`applyLakebaseEnv`), on this app's
|
|
232
|
+
// terms - no auto-create, and a warning instead of a throw.
|
|
233
|
+
const handle = await createApp({
|
|
234
|
+
autoConfigure: false,
|
|
235
|
+
plugins: [
|
|
236
|
+
// Brand the code email from the resolved context, the same bridge every
|
|
237
|
+
// other dbx-tools email surface uses (accent + font inlined, logo only when
|
|
238
|
+
// it is a fetchable URL - a package-export path cannot load in an inbox).
|
|
239
|
+
email({ brand: emailBrandFromContext(context) }),
|
|
240
|
+
// `brandName` falls back to the resolved context's display name, so the
|
|
241
|
+
// email copy names the app rather than a generic placeholder. Spelled as an
|
|
242
|
+
// explicit `??` rather than a key before `...config`: commander sets
|
|
243
|
+
// `brandName` on the options object whether or not the flag was passed, so
|
|
244
|
+
// spreading it would clobber the context name with `undefined`.
|
|
245
|
+
authGate({ ...config, brandName: config.brandName ?? context.name, sendCode }),
|
|
246
|
+
],
|
|
247
|
+
});
|
|
248
|
+
// Fail fast: the gate needs SMTP to email codes. `getEmailRuntime()` resolves to
|
|
249
|
+
// `mode: "file"` (outbox) or throws when no SMTP creds are configured - neither
|
|
250
|
+
// can deliver to a real inbox, so refuse to bring up a gate that lets nobody in.
|
|
251
|
+
if (getEmailRuntime().config.mode !== "smtp") {
|
|
252
|
+
throw new Error("email is not configured for SMTP delivery - the OTP gate cannot send codes. " +
|
|
253
|
+
"Set SMTP_HOST/SMTP_USER/SMTP_PASSWORD, or pass --insecure / TUNNEL_INSECURE=true to run the tunnel open.");
|
|
254
|
+
}
|
|
255
|
+
logger.info("gate app ready (no server; proxy-driven)");
|
|
256
|
+
return handle.authGate;
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXlCRztBQUVILE9BQU8sRUFBRSxTQUFTLElBQUksV0FBVyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDL0UsT0FBTyxFQUFFLEtBQUssSUFBSSxTQUFTLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsS0FBSyxJQUFJLFVBQVUsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pGLE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzFELE9BQU8sRUFBRSxRQUFRLEVBQStELE1BQU0sYUFBYSxDQUFDO0FBRXBHLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7QUFFeEM7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLFNBQVMsQ0FBQyxPQUFlO0lBQ3ZDLE9BQU8sT0FBTyxJQUFJLEVBQUUsSUFBSSxPQUFPLEdBQUcsRUFBRSxLQUFLLENBQUM7UUFDeEMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsT0FBTyxHQUFHLEVBQUUsRUFBRSxRQUFRLENBQUM7UUFDMUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDO0FBQzFDLENBQUM7QUFLRCwrQ0FBK0M7QUFDL0MsTUFBTSxXQUFXLEdBQUcsOERBQThELENBQUM7QUFFbkY7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUFDLElBQVksRUFBRSxJQUFjO0lBQzVELE9BQU87UUFDTCxJQUFJLENBQUMsT0FBTztRQUNaLEVBQUU7UUFDRixNQUFNLElBQUksRUFBRTtRQUNa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RSxJQUFJLENBQUMsRUFBRSxDQUMvRSxDQUFDO0lBQ0osQ0FBQyxDQUFDO0lBRUYsK0VBQStFO0lBQy9FLGlFQUFpRTtJQUNqRSxFQUFFO0lBQ0YsOEVBQThFO0lBQzlFLDZFQUE2RTtJQUM3RSw0REFBNEQ7SUFDNUQsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLENBQUM7UUFDN0IsYUFBYSxFQUFFLEtBQUs7UUFDcEIsT0FBTyxFQUFFO1lBQ1Asd0VBQXdFO1lBQ3hFLDRFQUE0RTtZQUM1RSwwRUFBMEU7WUFDMUUsS0FBSyxDQUFDLEVBQUUsS0FBSyxFQUFFLHFCQUFxQixDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7WUFDaEQsd0VBQXdFO1lBQ3hFLDRFQUE0RTtZQUM1RSxxRUFBcUU7WUFDckUsMkVBQTJFO1lBQzNFLGdFQUFnRTtZQUNoRSxRQUFRLENBQUMsRUFBRSxHQUFHLE1BQU0sRUFBRSxTQUFTLEVBQUUsTUFBTSxDQUFDLFNBQVMsSUFBSSxPQUFPLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDO1NBQy9FO0tBQ0YsQ0FBQyxDQUFDO0lBRUgsaUZBQWlGO0lBQ2pGLGdGQUFnRjtJQUNoRixpRkFBaUY7SUFDakYsSUFBSSxlQUFlLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxLQUFLLE1BQU0sRUFBRSxDQUFDO1FBQzdDLE1BQU0sSUFBSSxLQUFLLENBQ2IsOEVBQThFO1lBQzVFLDBHQUEwRyxDQUM3RyxDQUFDO0lBQ0osQ0FBQztJQUVELE1BQU0sQ0FBQyxJQUFJLENBQUMsMENBQTBDLENBQUMsQ0FBQztJQUN4RCxPQUFPLE1BQU0sQ0FBQyxRQUF1QixDQUFDO0FBQ3hDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFRoZSB0dW5uZWwgZ2F0ZSdzIHRpbnkgQXBwS2l0IFwiYXBwXCIgLSBgY3JlYXRlQXBwYCBXSVRIT1VUIGEgYHNlcnZlcigpYCBwbHVnaW4uXG4gKlxuICogVGhlcmUgaXMgbm8gSFRUUCBzZXJ2ZXIgaGVyZTogdGhlIHR1bm5lbCBwcm94eSBpcyB0aGUgc2VydmVyLCBhbmQgaXQgY2FsbHMgdGhlXG4gKiBnYXRlIGhhbmRsZXJzIGluLXByb2Nlc3MuIGBjcmVhdGVBcHBgIGlzIHVzZWQgb25seSBmb3Igd2hhdCBpdCBhdXRvLXdpcmVzOlxuICogICAtIGBDYWNoZU1hbmFnZXJgIChMYWtlYmFzZSB3aGVuIHRoaXMgcHJvY2VzcyBjYW4gcmVhY2ggaXQsIGVsc2UgbWVtb3J5KSAtXG4gKiAgICAgd2hpY2ggdGhlIE9UUCBgQ29kZVN0b3JlYCBhbmQgdGhlIHNlc3Npb24gc2lnbmluZyBrZXkgdXNlIGZvciBzdG9yYWdlICsgVFRMXG4gKiAgICAgZXZpY3Rpb247XG4gKiAgIC0gdGhlIGBlbWFpbCgpYCB0cmFuc3BvcnQgKFNNVFAgLyBvdXRib3gpLCBwcmltZWQgZnJvbSBlbnYsIHNvIGBzZW5kQ29kZWAgY2FuXG4gKiAgICAgZGVsaXZlciB0aGUgb25lLXRpbWUgY29kZTtcbiAqICAgLSBkYngtdG9vbHMgYXV0by1jb25maWd1cmF0aW9uICsgYnJhbmRpbmcgdmlhIGBAZGJ4LXRvb2xzL2FwcGtpdGAuXG4gKlxuICogSXQgcmV0dXJucyB0aGUge0BsaW5rIEF1dGhHYXRlQXBpfSB0aGUgcHJveHkgZHJpdmVzLiBgc2VuZENvZGVgIGlzIHdpcmVkIGhlcmVcbiAqICh0aGUgcGx1Z2luIGNhbid0IHJlc29sdmUgSE9XIHRvIHNlbmQgb24gaXRzIG93bikgdG8gdGhlIGVtYWlsIHBsdWdpbidzXG4gKiB0cmFuc3BvcnQuIEEgc2lnbi1pbiBjb2RlIGlzIFNZU1RFTSBtYWlsIC0gbm8gb24tYmVoYWxmLW9mIHVzZXIgYXNrZWQgZm9yIGl0XG4gKiBhbmQgbm8gcmVwbHkgdG8gaXQgcmVhY2hlcyBhbnlvbmUgLSBzbyBpdCBzZW5kcyBmcm9tIHRoZSBlbWFpbCBjb25maWcnc1xuICogZG8tbm90LXJlcGx5IGFkZHJlc3MgKGBuby1yZXBseUBFTUFJTF9ET01BSU5gIHVubGVzcyBFTUFJTF9TWVNURU1fRlJPTSBuYW1lc1xuICogYW5vdGhlcikuXG4gKlxuICogRkFJTCBGQVNUOiBhIGdhdGUgdGhhdCBjYW4ndCBlbWFpbCBhIGNvZGUgaXMgdXNlbGVzcywgc28gaWYgZW1haWwgZG9lcyBub3RcbiAqIHJlc29sdmUgdG8gU01UUCBtb2RlIChyZWFsIGRlbGl2ZXJ5KSwgdGhpcyB0aHJvd3MgLSB1bmxlc3MgYGluc2VjdXJlYCBpcyBzZXRcbiAqIChgLS1pbnNlY3VyZWAgLyBgVFVOTkVMX0lOU0VDVVJFPXRydWVgKSwgaW4gd2hpY2ggY2FzZSB0aGUgY2FsbGVyIHJ1bnMgdGhlXG4gKiB0dW5uZWwgT1BFTiB3aXRoIG5vIGdhdGUuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IGNyZWF0ZUFwcCBhcyBjcmVhdGVBcHBOcywgbGFrZWJhc2VSZXNvbHZlciB9IGZyb20gXCJAZGJ4LXRvb2xzL2FwcGtpdFwiO1xuaW1wb3J0IHsgYnJhbmQgYXMgbm9kZUJyYW5kIH0gZnJvbSBcIkBkYngtdG9vbHMvY29yZVwiO1xuaW1wb3J0IHsgYnJhbmQgYXMgZW1haWxCcmFuZCwgZW1haWwsIHNlbmRlciwgdHJhbnNwb3J0IH0gZnJvbSBcIkBkYngtdG9vbHMvZW1haWxcIjtcbmltcG9ydCB7IGVudiwgbG9nLCBzdHJpbmcgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgYXV0aEdhdGUsIHR5cGUgQXV0aEdhdGVBcGksIHR5cGUgQXV0aEdhdGVDb25maWcsIHR5cGUgU2VuZENvZGVPcHRpb25zIH0gZnJvbSBcIi4vcGx1Z2luLnRzXCI7XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJ0dW5uZWw6YXBwXCIpO1xuXG4vKipcbiAqIEEgY29kZSBUVEwgYXMgdGhlIHBsYWluIHBocmFzZSB0aGUgZW1haWwgc3RhdGVzIChcIjEwIG1pbnV0ZXNcIiwgXCI0NSBzZWNvbmRzXCIpLlxuICpcbiAqIFdob2xlIG1pbnV0ZXMgcmVhZCBhcyBtaW51dGVzOyBhbnl0aGluZyBlbHNlIHN0YXlzIGluIHNlY29uZHMgcmF0aGVyIHRoYW5cbiAqIHJvdW5kaW5nLCBzbyBhIDkwLXNlY29uZCBUVEwgaXMgbm90IGFkdmVydGlzZWQgYXMgXCIxIG1pbnV0ZVwiIGFuZCBhIHJlY2lwaWVudCBpc1xuICogbmV2ZXIgdG9sZCB0aGUgY29kZSBsaXZlcyBsb25nZXIgdGhhbiBpdCBkb2VzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZXhwaXJlc0luKHNlY29uZHM6IG51bWJlcik6IHN0cmluZyB7XG4gIHJldHVybiBzZWNvbmRzID49IDYwICYmIHNlY29uZHMgJSA2MCA9PT0gMFxuICAgID8gc3RyaW5nLnBsdXJhbGl6ZShzZWNvbmRzIC8gNjAsIFwibWludXRlXCIpXG4gICAgOiBzdHJpbmcucGx1cmFsaXplKHNlY29uZHMsIFwic2Vjb25kXCIpO1xufVxuXG4vKiogVGhlIHBhcnRzIG9mIHtAbGluayBTZW5kQ29kZU9wdGlvbnN9IHRoZSBjb2RlIGVtYWlsJ3MgY29weSBpcyBidWlsdCBmcm9tLiAqL1xudHlwZSBDb2RlQ29weSA9IFBpY2s8U2VuZENvZGVPcHRpb25zLCBcIm1lc3NhZ2VcIiB8IFwiY29kZVR0bFNlY29uZHNcIj47XG5cbi8qKiBUaGUgcmVhc3N1cmFuY2UgbGluZSBjbG9zaW5nIGJvdGggcGFydHMuICovXG5jb25zdCBJR05PUkVfTElORSA9IFwiSWYgeW91IGRpZCBub3QgcmVxdWVzdCB0aGlzIGNvZGUsIHlvdSBjYW4gaWdub3JlIHRoaXMgZW1haWwuXCI7XG5cbi8qKlxuICogVGhlIEhUTUwgcGFydCdzIHNvdXJjZTogdGhlIGZ1bGwgYnJhbmRlZCB0ZW1wbGF0ZSwgd2l0aCB0aGUgY29kZSBhcyBhIGxhcmdlXG4gKiBzdHlsZWQgaGVhZGluZyAoYCMjIGAgaXMgd2hhdCBtYWtlcyBpdCBwcm9taW5lbnQgaW4gYW4gaW5ib3gpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY29kZUVtYWlsSHRtbEJvZHkoY29kZTogc3RyaW5nLCBvcHRzOiBDb2RlQ29weSk6IHN0cmluZyB7XG4gIHJldHVybiBbXG4gICAgb3B0cy5tZXNzYWdlLFxuICAgIFwiXCIsXG4gICAgYCMjICR7Y29kZX1gLFxuICAgIFwiXCIsXG4gICAgYFRoaXMgY29kZSBleHBpcmVzIGluICR7ZXhwaXJlc0luKG9wdHMuY29kZVR0bFNlY29uZHMpfS5gLFxuICAgIFwiXCIsXG4gICAgSUdOT1JFX0xJTkUsXG4gIF0uam9pbihcIlxcblwiKTtcbn1cblxuLyoqXG4gKiBUaGUgYHRleHQvcGxhaW5gIHBhcnQsIHN1cHBsaWVkIEVYUExJQ0lUTFkgcmF0aGVyIHRoYW4gcmVuZGVyZWQgZnJvbSB0aGUgdHJlZVxuICogYWJvdmUuIEJvdGggcGFydHMgc2F5IHRoZSBzYW1lIHRoaW5nOyBvbmx5IHRoZSBsaW5lIGxheW91dCBkaWZmZXJzLlxuICpcbiAqIFRoZSBwcm9tcHQgYW5kIHRoZSBjb2RlIHNoYXJlIE9ORSBsaW5lIChcIllvdXIgdmVyaWZpY2F0aW9uIGNvZGUgaXM6IDEyMzQ1NlwiKS5cbiAqIFRoYXQgc2luZ2xlLWxpbmUgc2hhcGUgaXMgd2hhdCBpT1MsIEdtYWlsLCBPdXRsb29rLCBhbmQgQW5kcm9pZCBjb2RlIGRldGVjdGlvblxuICoga2V5cyBvbiBtb3N0IHJlbGlhYmx5IC0gdGhlIGhldXJpc3RpY3MgbG9vayBmb3IgYSBjb2RlIGluIHRoZSBzYW1lIHNlbnRlbmNlIGFzXG4gKiBhIHJlY29nbml6ZWQgcHJvbXB0LCBzbyBzcGxpdHRpbmcgdGhlbSBhY3Jvc3MgbGluZXMgbWFrZXMgZGV0ZWN0aW9uIGRlcGVuZGVudFxuICogb24gdGhlIGNsaWVudCwgYW5kIGFueSBibGFuayBsaW5lIGJldHdlZW4gdGhlbSBkZWZlYXRzIGl0IG91dHJpZ2h0LlxuICpcbiAqIFRoZSBHRU5FUkFURUQgdGV4dCBwYXJ0IGNhbm5vdCBob2xkIHRoYXQgc2hhcGUgYXQgYWxsOiBpdCBpcyBhIHJlbmRlcmluZyBvZiB0aGVcbiAqIEhUTUwsIHNvIGl0IGNhcnJpZXMgdGhlIGJyYW5kIGhlYWRlci9mb290ZXIgYW5kIHR1cm5zIHRoZSBjb2RlIGhlYWRpbmcncyBDU1NcbiAqIG1hcmdpbiBpbnRvIGJsYW5rIGxpbmVzLCBhcnJpdmluZyBhcyBgcHJvbXB0XFxuXFxuXFxuY29kZWAuXG4gKlxuICogVGhlIGNvZGUgaXMgdmlzaWJsZSB0ZXh0IGluIEJPVEggcGFydHMsIG5ldmVyIGFuIGltYWdlLCBzbyBhIGNsaWVudCBzY3JhcGluZ1xuICogZWl0aGVyIG9uZSBmaW5kcyBpdC4gTm8gdHJhaWxlciBsaW5lIGZvbGxvd3MgdGhlIGNvcHkgLSBBcHBsZSdzIGRvbWFpbi1ib3VuZFxuICogYEBkb21haW4gI2NvZGVgIGZvb3RlciBpcyBkZWxpYmVyYXRlbHkgTk9UIGVtaXR0ZWQsIHNpbmNlIGl0IGNvbnN0cmFpbnMgdGhlXG4gKiBjb2RlIHRvIG9uZSBvcmlnaW4gYW5kIGlzIG5vdCB3aGF0IHRoZSBicm9hZGx5LWNvbXBhdGlibGUgc2hhcGUgbmVlZHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjb2RlRW1haWxUZXh0Qm9keShjb2RlOiBzdHJpbmcsIG9wdHM6IENvZGVDb3B5KTogc3RyaW5nIHtcbiAgcmV0dXJuIFtcbiAgICBgJHtvcHRzLm1lc3NhZ2V9ICR7Y29kZX1gLFxuICAgIGBUaGlzIGNvZGUgZXhwaXJlcyBpbiAke2V4cGlyZXNJbihvcHRzLmNvZGVUdGxTZWNvbmRzKX0uYCxcbiAgICBcIlwiLFxuICAgIElHTk9SRV9MSU5FLFxuICBdLmpvaW4oXCJcXG5cIik7XG59XG5cbi8qKlxuICogVGhlIFNVQkpFQ1QgbGluZSwgd2l0aCB0aGUgY29kZSBpbiBpdDogYFwiMTIzNDU2IGlzIHlvdXIgdmVyaWZpY2F0aW9uIGNvZGVcImAuXG4gKlxuICogVGhlIGNvZGUgaGFzIHRvIGJlIGhlcmUsIG5vdCBvbmx5IGluIHRoZSBib2R5LCBiZWNhdXNlIG9mIHdoYXQgbW9iaWxlIGF1dG9maWxsXG4gKiBhY3R1YWxseSByZWFkcy4gaU9TIG9mZmVycyBhIGNvZGUgZnJvbSBhbiBpbmNvbWluZyBOT1RJRklDQVRJT04gLSBuYXRpdmVseSBmb3JcbiAqIE1lc3NhZ2VzIGFuZCBNYWlsLCBhbmQgc2luY2UgaU9TIDI2IGZvciBhbnkgYXBwJ3Mgbm90aWZpY2F0aW9uIHRleHQsIHdoaWNoIGlzXG4gKiB3aGF0IGZpbmFsbHkgbWFkZSBHbWFpbCB3b3JrIC0gYW5kIGEgbm90aWZpY2F0aW9uIGNvbnRhaW5zIHRoZSBzZW5kZXIsIHRoZVxuICogc3ViamVjdCwgYW5kIGEgc2hvcnQgc25pcHBldC4gTm90aGluZyBlbHNlLiBBIGNvZGUgdGhhdCBsaXZlcyBpbiB0aGUgYm9keSBpc1xuICogaW52aXNpYmxlIHRvIGl0LCBob3dldmVyIGNsZWFubHkgdGhlIGJvZHkgaXMgZm9ybWF0dGVkLCB3aGljaCBpcyB3aHkgYSBwZXJmZWN0bHlcbiAqIHNoYXBlZCBgdGV4dC9wbGFpbmAgcGFydCBzdGlsbCBwcm9kdWNlZCBubyBhdXRvZmlsbCBwcm9tcHQgaW4gR21haWwuXG4gKlxuICogYDxjb2RlPiBpcyB5b3VyIDx0aGluZz5gIHJhdGhlciB0aGFuIGA8dGhpbmc+OiA8Y29kZT5gIGJlY2F1c2UgdGhlIGxlYWRpbmcgY29kZVxuICogc3Vydml2ZXMgVFJVTkNBVElPTjogYSBub3RpZmljYXRpb24gYW5kIGFuIGluYm94IGxpc3QgYm90aCBjdXQgdGhlIHN1YmplY3QsIGFuZFxuICogdGhlIHBsYXRmb3JtIGhldXJpc3RpY3Mgd2FudCB0aGUgY29kZSBpbiB0aGUgc2FtZSBzZW50ZW5jZSBhcyBhIHJlY29nbml6ZWRcbiAqIHByb21wdCAoXCJjb2RlXCIsIFwidmVyaWZpY2F0aW9uXCIpLiBQdXR0aW5nIGl0IGZpcnN0IGtlZXBzIGJvdGggaW50YWN0IG5vIG1hdHRlclxuICogd2hlcmUgdGhlIGN1dCBsYW5kcy5cbiAqXG4gKiBgc3ViamVjdGAgaXMgdGhlIGNvbmZpZ3VyZWQgbGluZSAoXCJZb3VyIHZlcmlmaWNhdGlvbiBjb2RlXCIpLCBsb3dlci1jYXNlZCBhdCBpdHNcbiAqIGZpcnN0IHdvcmQgc28gdGhlIHNlbnRlbmNlIHJlYWRzIG5hdHVyYWxseSwgYW5kIGxlZnQgQUxPTkUgd2hlbiBpdCBkb2VzIG5vdCBsb29rXG4gKiBsaWtlIHRoZSBjb252ZW50aW9uYWwgcGhyYXNpbmcgLSBhbiBvcGVyYXRvciB3aG8gc2V0IGEgZGVsaWJlcmF0ZSBzdWJqZWN0IGdldHNcbiAqIHRoZWlycyB3aXRoIHRoZSBjb2RlIHByZWZpeGVkLCBub3QgYSBtYW5nbGVkIGh5YnJpZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNvZGVFbWFpbFN1YmplY3QoY29kZTogc3RyaW5nLCBzdWJqZWN0OiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCB0cmltbWVkID0gc3ViamVjdC50cmltKCk7XG4gIGNvbnN0IGNvbnZlbnRpb25hbCA9IC9eeW91clxccysvaS5leGVjKHRyaW1tZWQpO1xuICBjb25zdCByZXN0ID0gY29udmVudGlvbmFsID8gdHJpbW1lZC5zbGljZShjb252ZW50aW9uYWxbMF0ubGVuZ3RoKSA6IHRyaW1tZWQ7XG4gIHJldHVybiBjb252ZW50aW9uYWwgPyBgJHtjb2RlfSBpcyB5b3VyICR7cmVzdH1gIDogYCR7Y29kZX0gLSAke3RyaW1tZWR9YDtcbn1cblxuLyoqXG4gKiBUaGUgUFJFSEVBREVSOiB0aGUgc25pcHBldCBiZXNpZGUgdGhlIHN1YmplY3QgaW4gYW4gaW5ib3ggbGlzdCwgYW5kIHRoZSBib2R5IG9mXG4gKiB0aGUgcHVzaCBub3RpZmljYXRpb24uIENhcnJpZXMgdGhlIGNvZGUgZm9yIHRoZSBzYW1lIHJlYXNvbiB0aGUgc3ViamVjdCBkb2VzIC1cbiAqIGl0IGlzIHRoZSBvdGhlciBoYWxmIG9mIHdoYXQgYSBub3RpZmljYXRpb24gc2hvd3MgLSBhbmQgcmVwZWF0cyB0aGUgcHJvbXB0XG4gKiB3b3JkaW5nIHNvIGEgaGV1cmlzdGljIHNjYW5uaW5nIHRoZSBzbmlwcGV0IGFsb25lIGZpbmRzIGEgY29kZSBuZXh0IHRvIGEgcGhyYXNlXG4gKiBpdCByZWNvZ25pemVzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY29kZUVtYWlsUHJldmlldyhjb2RlOiBzdHJpbmcsIG9wdHM6IENvZGVDb3B5KTogc3RyaW5nIHtcbiAgcmV0dXJuIGAke29wdHMubWVzc2FnZX0gJHtjb2RlfWA7XG59XG5cbmNvbnN0IHsgY3JlYXRlQXBwIH0gPSBjcmVhdGVBcHBOcztcbmNvbnN0IHsgYXBwbHlMYWtlYmFzZUVudiB9ID0gbGFrZWJhc2VSZXNvbHZlcjtcbmNvbnN0IHsgcmVzb2x2ZVN5c3RlbVNlbmRlckFkZHJlc3MgfSA9IHNlbmRlcjtcbmNvbnN0IHsgZ2V0RW1haWxSdW50aW1lLCBzZW5kRW1haWwgfSA9IHRyYW5zcG9ydDtcbmNvbnN0IHsgZW1haWxCcmFuZEZyb21Db250ZXh0IH0gPSBlbWFpbEJyYW5kO1xuY29uc3QgeyBsb2FkQnJhbmRDb250ZXh0IH0gPSBub2RlQnJhbmQ7XG5cbi8qKlxuICogRW52IHZhcnMgd2hvc2UgcHJlc2VuY2UgbWVhbnMgYSBMYWtlYmFzZSBkYXRhYmFzZSB3YXMgYm91bmQgdG8gdGhpcyBkZXBsb3ltZW50LlxuICogYExBS0VCQVNFX0VORFBPSU5UYCBpcyB3aGF0IGEgRGF0YWJyaWNrcyBBcHAgYHBvc3RncmVzYCByZXNvdXJjZSBiaW5kaW5nIHNldHM7XG4gKiBgUEdIT1NUYCBpcyB0aGUgbG9jYWwvbWFudWFsIHNwZWxsaW5nLlxuICovXG5jb25zdCBMQUtFQkFTRV9FTlYgPSBbXCJMQUtFQkFTRV9FTkRQT0lOVFwiLCBcIlBHSE9TVFwiXSBhcyBjb25zdDtcblxuLyoqXG4gKiBVcHBlciBib3VuZCBvbiByZXNvbHZpbmcgdGhlIExha2ViYXNlIGNvbm5lY3Rpb24uIFRoZSBnYXRlIGlzIGluIHRoZSByZXF1ZXN0XG4gKiBwYXRoIGZvciBldmVyeSB2aXNpdG9yLCBzbyBhIHNsb3cgd29ya3NwYWNlIEFQSSBtdXN0IG5vdCBob2xkIHVwIGJvb3RcbiAqIGluZGVmaW5pdGVseTsgYSBtZW1vcnkgY2FjaGUgaXMgYSBkZWdyYWRlZCBnYXRlLCBhbiB1bmJvb3RlZCBvbmUgaXMgbm8gZ2F0ZS5cbiAqL1xuY29uc3QgTEFLRUJBU0VfUkVTT0xWRV9USU1FT1VUX01TID0gNjBfMDAwO1xuXG4vKipcbiAqIEZpbGwgaW4gdGhlIExha2ViYXNlIGNvbm5lY3Rpb24gZW52IEFwcEtpdCdzIGNhY2hlIG5lZWRzLCBzbyBgQ2FjaGVNYW5hZ2VyYFxuICogY2hvb3NlcyBQRVJTSVNURU5UIHN0b3JhZ2UgaW5zdGVhZCBvZiBtZW1vcnkuXG4gKlxuICogVGhpcyBpcyB3aGF0IG1ha2VzIHRoZSBnYXRlJ3Mgc2Vzc2lvbiBzaWduaW5nIGtleSBhbmQgb3V0c3RhbmRpbmcgb25lLXRpbWVcbiAqIGNvZGVzIHN1cnZpdmUgYSByZXN0YXJ0LiBgYXBwbHlMYWtlYmFzZUVudmAgaXMgdGhlIFNIQVJFRCBoZWxwZXIgQXBwS2l0XG4gKiBhdXRvLWNvbmZpZ3VyYXRpb24gdXNlcywgc28gdGhlIGdhdGUgZ2V0cyBleGFjdGx5IHRoZSBlbnYgYSBwb29sIG5lZWRzIC1cbiAqIGBMQUtFQkFTRV9FTkRQT0lOVGAsIGBQR0hPU1RgLCBgUEdEQVRBQkFTRWAsIGFuZCBgUEdVU0VSYCAtIHJhdGhlciB0aGFuIGFcbiAqIGhhbmQtcm9sbGVkIHN1YnNldC4gQWxsIGZvdXIgbWF0dGVyOiBgY3JlYXRlTGFrZWJhc2VQb29sKClgIHRocm93cyB3aXRob3V0IGFueVxuICogb25lIG9mIHRoZW0sIGFuZCBhIERhdGFicmlja3MgQXBwIGBwb3N0Z3Jlc2AgcmVzb3VyY2UgYmluZGluZyBzdXBwbGllcyBvbmx5IHRoZVxuICogZmlyc3QuIFdpdGhvdXQgdGhlbSB0aGUgcG9vbCBjYW5ub3QgYmUgYnVpbHQsIHRoZSBjYWNoZSBzaWxlbnRseSBkZWdyYWRlcyB0b1xuICogaW4tbWVtb3J5LCBhbmQgZXZlcnkgcmVkZXBsb3kgc2lnbnMgb3V0IGV2ZXJ5IHVzZXIgKHRoZSBleGFjdCBzeW1wdG9tIHRoaXNcbiAqIGV4aXN0cyB0byBwcmV2ZW50KS5cbiAqXG4gKiBJdCBpcyBjYWxsZWQgaGVyZSByYXRoZXIgdGhhbiBsZWZ0IHRvIGBjcmVhdGVBcHBgJ3MgYGF1dG9Db25maWd1cmVgIGJlY2F1c2UgdGhhdFxuICogZ2F0ZXMgb24gYSBgbGFrZWJhc2UoKWAgcGx1Z2luIGJlaW5nIHJlZ2lzdGVyZWQgLSBhbmQgdGhpcyBhcHAgcmVnaXN0ZXJzIG5vbmUsXG4gKiBoYXZpbmcgbm8gc2VydmVyIHRvIG1vdW50IExha2ViYXNlIHJvdXRlcyBvbi4gQ2FsbGluZyB0aGUgaGVscGVyIGRpcmVjdGx5IGFsc29cbiAqIGxldHMgdGhlIGdhdGUgYmUgc3RyaWN0ZXIgdGhhbiBgYXV0b0NvbmZpZ3VyZWAgaXM6XG4gKlxuICogICAtIEl0IHJ1bnMgT05MWSB3aGVuIGEgTGFrZWJhc2UgZW52IHZhciBpcyBwcmVzZW50LiBBIHR1bm5lbCBpcyBhIHdyYXBwZXJcbiAqICAgICBhcm91bmQgc29tZW9uZSBlbHNlJ3MgYXBwIGFuZCBtdXN0IG5vdCBpbnZlbnQgaW5mcmFzdHJ1Y3R1cmUsIHNvIHdpdGhcbiAqICAgICBub3RoaW5nIGJvdW5kIGl0IHNraXBzIHJhdGhlciB0aGFuIGZhbGxpbmcgdGhyb3VnaCB0aGUgcmVzb2x2ZXInc1xuICogICAgIGxpc3Qtb3ItQ1JFQVRFLWEtcHJvamVjdCBwYXRoLlxuICogICAtIGBhdXRvQ3JlYXRlOiBmYWxzZWAgZm9yIHRoZSBzYW1lIHJlYXNvbiwgaW4gY2FzZSBhIHByb2plY3QgaGFwcGVucyB0byBleGlzdC5cbiAqICAgLSBFdmVyeSBmYWlsdXJlIGlzIGEgV0FSTklORywgbmV2ZXIgYSB0aHJvdy4gVGhlIGNhY2hlIGlzIGFuIG9wdGltaXphdGlvbiBmb3JcbiAqICAgICBzZXNzaW9uIGR1cmFiaWxpdHk7IGFkbWlzc2lvbiBzdGlsbCByZXF1aXJlcyBhIGNvZGUgZGVsaXZlcmVkIHRvIGFuXG4gKiAgICAgYWxsb3ctbGlzdGVkIGFkZHJlc3MsIHNvIGEgZ2F0ZSB3aXRoIGEgbWVtb3J5IGNhY2hlIGlzIHNhZmUsIGp1c3QgZm9yZ2V0ZnVsLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVzb2x2ZUNhY2hlU3RvcmFnZUVudigpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgaWYgKCFlbnYudGV4dChMQUtFQkFTRV9FTlYpKSB7XG4gICAgbG9nZ2VyLmluZm8oXG4gICAgICBgbm8gJHtlbnYubmFtZShMQUtFQkFTRV9FTlYpfSAtIHRoZSBnYXRlIGNhY2hlIHN0YXlzIGluIG1lbW9yeTsgc2Vzc2lvbnMgYW5kIGNvZGVzIHdpbGwgbm90IHN1cnZpdmUgYSByZXN0YXJ0YCxcbiAgICApO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICB0cnkge1xuICAgIGNvbnN0IHsgcmVzb2x2ZWQsIHVzZXIgfSA9IGF3YWl0IGFwcGx5TGFrZWJhc2VFbnYoXG4gICAgICB7IGF1dG9DcmVhdGU6IGZhbHNlIH0sXG4gICAgICBBYm9ydFNpZ25hbC50aW1lb3V0KExBS0VCQVNFX1JFU09MVkVfVElNRU9VVF9NUyksXG4gICAgKTtcbiAgICBsb2dnZXIuaW5mbyhcImxha2ViYXNlIHJlc29sdmVkIGZvciB0aGUgZ2F0ZSBjYWNoZVwiLCB7XG4gICAgICBob3N0OiByZXNvbHZlZC5ob3N0LFxuICAgICAgZGF0YWJhc2U6IHJlc29sdmVkLmRhdGFiYXNlLFxuICAgICAgZW5kcG9pbnQ6IHJlc29sdmVkLmVuZHBvaW50LFxuICAgICAgdXNlcixcbiAgICB9KTtcbiAgICByZXR1cm4gdHJ1ZTtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBsb2dnZXIud2FybihcbiAgICAgIFwiY291bGQgbm90IHJlc29sdmUgbGFrZWJhc2UgLSB0aGUgZ2F0ZSBjYWNoZSBzdGF5cyBpbiBtZW1vcnk7IHNlc3Npb25zIGFuZCBjb2RlcyB3aWxsIG5vdCBzdXJ2aXZlIGEgcmVzdGFydFwiLFxuICAgICAgeyBlcnJvciB9LFxuICAgICk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG59XG5cbi8qKlxuICogQm9vdCB0aGUgZ2F0ZSBhcHAgYW5kIHJldHVybiB0aGUgQVBJIHRoZSBwcm94eSBjYWxscy4gVGhyb3dzIHdoZW4gZW1haWwgaXMgbm90XG4gKiBpbiBTTVRQIG1vZGUgKG5vIHdheSB0byBkZWxpdmVyIGEgY29kZSkgc28gYSBtaXNjb25maWd1cmVkIGdhdGUgZmFpbHMgZmFzdCBhdFxuICogc3RhcnR1cCByYXRoZXIgdGhhbiBzaWxlbnRseSBhY2NlcHRpbmcgbm9ib2R5OyB0aGUgY2FsbGVyIG1heSBjYXRjaCB0aGlzIGFuZFxuICogZmFsbCBiYWNrIHRvIGluc2VjdXJlL29wZW4gbW9kZSB3aGVuIHRoZSBvcGVyYXRvciBvcHRlZCBpbi5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHN0YXJ0R2F0ZUFwcChjb25maWc6IEF1dGhHYXRlQ29uZmlnKTogUHJvbWlzZTxBdXRoR2F0ZUFwaT4ge1xuICAvLyBUaGUgaG9zdCBhcHAncyBvd24gYnJhbmQgKGBicmFuZGluZy9icmFuZC55YW1sYCBkaXNjb3ZlcmVkIGZyb20gY3dkKSBvciB0aGVcbiAgLy8gZGJ4LXRvb2xzIGRlZmF1bHQuIFRoaXMgaXMgdGhlIE9ORSBicmFuZCBzb3VyY2UgZm9yIHRoZSBnYXRlOiBpdCBzdHlsZXMgdGhlXG4gIC8vIGNvZGUgZW1haWwgKGFjY2VudCBiYW5kLCBmb250LCBsb2dvKSB2aWEgdGhlIGVtYWlsIHBsdWdpbiBBTkQgc3VwcGxpZXMgdGhlXG4gIC8vIGRpc3BsYXkgbmFtZSB0aGUgY29weSB1c2VzLCBzbyBhIGRlcGxveW1lbnQgdGhhdCB0aGVtZXMgaXRzIGFwcCB0aGVtZXMgaXRzXG4gIC8vIHNpZ24taW4gZW1haWwgd2l0aCBpdC4gQW4gZXhwbGljaXQgYGJyYW5kTmFtZWAgc3RpbGwgd2lucyAoc2VlIGJlbG93KS5cbiAgY29uc3QgY29udGV4dCA9IGF3YWl0IGxvYWRCcmFuZENvbnRleHQoKTtcblxuICAvLyBCZWZvcmUgYGNyZWF0ZUFwcGAsIGJlY2F1c2UgQXBwS2l0IHJlc29sdmVzIHRoZSBjYWNoZSdzIHN0b3JhZ2UgZHVyaW5nIGl0IGFuZFxuICAvLyByZWFkcyB0aGUgY29ubmVjdGlvbiBvdXQgb2YgYHByb2Nlc3MuZW52YCB3aGVuIGl0IGRvZXMuIFNlZSB0aGUgZnVuY3Rpb24nc1xuICAvLyBkb2NzIGZvciB3aHkgdGhlIGdhdGUgcmVzb2x2ZXMgdGhpcyBpdHNlbGYuXG4gIGF3YWl0IHJlc29sdmVDYWNoZVN0b3JhZ2VFbnYoKTtcblxuICAvLyBgc2VuZENvZGVgIGRlbGl2ZXJzIHRoZSBPVFAgdGhyb3VnaCB0aGUgZW1haWwgcGx1Z2luJ3MgU0hBUkVEIHRyYW5zcG9ydCwgd2hpY2hcbiAgLy8gdGhlIGBlbWFpbCgpYCBwbHVnaW4gcHJpbWVzIGR1cmluZyBpdHMgYHNldHVwKClgIChhd2FpdGVkIGJ5IGBjcmVhdGVBcHBgXG4gIC8vIGJlbG93KS4gVXNpbmcgdGhlIG1vZHVsZS1sZXZlbCBgc2VuZEVtYWlsYCBhdm9pZHMgYSBjaXJjdWxhciBkZXBlbmRlbmN5IG9uIHRoZVxuICAvLyBhcHAgaGFuZGxlJ3MgaW5mZXJyZWQgdHlwZS4gVGhlIGBGcm9tYCBpcyB0aGUgU1lTVEVNIHNlbmRlcjogYSB2ZXJpZmljYXRpb25cbiAgLy8gY29kZSBpcyBtYWNoaW5lLWdlbmVyYXRlZCBhbmQgdW5hbnN3ZXJhYmxlLCBzbyBpdCBtdXN0IG5vdCBhcnJpdmUgZnJvbSBhXG4gIC8vIHBlcnNvbidzIGFkZHJlc3MgaW52aXRpbmcgYSByZXBseS5cbiAgY29uc3Qgc2VuZENvZGU6IE5vbk51bGxhYmxlPEF1dGhHYXRlQ29uZmlnW1wic2VuZENvZGVcIl0+ID0gYXN5bmMgKHRvLCBjb2RlLCBvcHRzKSA9PiB7XG4gICAgY29uc3QgZnJvbSA9IHJlc29sdmVTeXN0ZW1TZW5kZXJBZGRyZXNzKGdldEVtYWlsUnVudGltZSgpLmNvbmZpZyk7XG4gICAgYXdhaXQgc2VuZEVtYWlsKFxuICAgICAge1xuICAgICAgICB0bzogW3RvXSxcbiAgICAgICAgLy8gVGhlIGNvZGUgcmlkZXMgaW4gdGhlIFNVQkpFQ1QgYW5kIHRoZSBwcmVoZWFkZXIsIG5vdCBqdXN0IHRoZSBib2R5OlxuICAgICAgICAvLyB0aG9zZSB0d28gc3RyaW5ncyBhcmUgdGhlIHdob2xlIG9mIGEgcHVzaCBub3RpZmljYXRpb24sIGFuZCBhXG4gICAgICAgIC8vIG5vdGlmaWNhdGlvbiBpcyB3aGF0IG1vYmlsZSBhdXRvZmlsbCByZWFkcy4gU2VlIGBjb2RlRW1haWxTdWJqZWN0YC5cbiAgICAgICAgc3ViamVjdDogY29kZUVtYWlsU3ViamVjdChjb2RlLCBvcHRzLnN1YmplY3QpLFxuICAgICAgICBib2R5OiBjb2RlRW1haWxIdG1sQm9keShjb2RlLCBvcHRzKSxcbiAgICAgIH0sXG4gICAgICBmcm9tLFxuICAgICAgdW5kZWZpbmVkLFxuICAgICAgeyB0ZXh0OiBjb2RlRW1haWxUZXh0Qm9keShjb2RlLCBvcHRzKSwgcHJldmlldzogY29kZUVtYWlsUHJldmlldyhjb2RlLCBvcHRzKSB9LFxuICAgICk7XG4gIH07XG5cbiAgLy8gYGNyZWF0ZUFwcGAgbmFtZXNwYWNlcyBlYWNoIHBsdWdpbidzIGV4cG9ydHMgb24gdGhlIGhhbmRsZSBieSBtYW5pZmVzdCBuYW1lO1xuICAvLyBgaGFuZGxlLmF1dGhHYXRlYCBpcyB0aGUgaW4tcHJvY2VzcyBnYXRlIEFQSSB0aGUgcHJveHkgZHJpdmVzLlxuICAvL1xuICAvLyBgYXV0b0NvbmZpZ3VyZTogZmFsc2VgIGJlY2F1c2Uge0BsaW5rIHJlc29sdmVDYWNoZVN0b3JhZ2VFbnZ9IGFib3ZlIGFscmVhZHlcbiAgLy8gcmFuIHRoZSBvbmUgcGllY2Ugb2YgaXQgdGhpcyBhcHAgd2FudHMgKGBhcHBseUxha2ViYXNlRW52YCksIG9uIHRoaXMgYXBwJ3NcbiAgLy8gdGVybXMgLSBubyBhdXRvLWNyZWF0ZSwgYW5kIGEgd2FybmluZyBpbnN0ZWFkIG9mIGEgdGhyb3cuXG4gIGNvbnN0IGhhbmRsZSA9IGF3YWl0IGNyZWF0ZUFwcCh7XG4gICAgYXV0b0NvbmZpZ3VyZTogZmFsc2UsXG4gICAgcGx1Z2luczogW1xuICAgICAgLy8gQnJhbmQgdGhlIGNvZGUgZW1haWwgZnJvbSB0aGUgcmVzb2x2ZWQgY29udGV4dCwgdGhlIHNhbWUgYnJpZGdlIGV2ZXJ5XG4gICAgICAvLyBvdGhlciBkYngtdG9vbHMgZW1haWwgc3VyZmFjZSB1c2VzIChhY2NlbnQgKyBmb250IGlubGluZWQsIGxvZ28gb25seSB3aGVuXG4gICAgICAvLyBpdCBpcyBhIGZldGNoYWJsZSBVUkwgLSBhIHBhY2thZ2UtZXhwb3J0IHBhdGggY2Fubm90IGxvYWQgaW4gYW4gaW5ib3gpLlxuICAgICAgZW1haWwoeyBicmFuZDogZW1haWxCcmFuZEZyb21Db250ZXh0KGNvbnRleHQpIH0pLFxuICAgICAgLy8gYGJyYW5kTmFtZWAgZmFsbHMgYmFjayB0byB0aGUgcmVzb2x2ZWQgY29udGV4dCdzIGRpc3BsYXkgbmFtZSwgc28gdGhlXG4gICAgICAvLyBlbWFpbCBjb3B5IG5hbWVzIHRoZSBhcHAgcmF0aGVyIHRoYW4gYSBnZW5lcmljIHBsYWNlaG9sZGVyLiBTcGVsbGVkIGFzIGFuXG4gICAgICAvLyBleHBsaWNpdCBgPz9gIHJhdGhlciB0aGFuIGEga2V5IGJlZm9yZSBgLi4uY29uZmlnYDogY29tbWFuZGVyIHNldHNcbiAgICAgIC8vIGBicmFuZE5hbWVgIG9uIHRoZSBvcHRpb25zIG9iamVjdCB3aGV0aGVyIG9yIG5vdCB0aGUgZmxhZyB3YXMgcGFzc2VkLCBzb1xuICAgICAgLy8gc3ByZWFkaW5nIGl0IHdvdWxkIGNsb2JiZXIgdGhlIGNvbnRleHQgbmFtZSB3aXRoIGB1bmRlZmluZWRgLlxuICAgICAgYXV0aEdhdGUoeyAuLi5jb25maWcsIGJyYW5kTmFtZTogY29uZmlnLmJyYW5kTmFtZSA/PyBjb250ZXh0Lm5hbWUsIHNlbmRDb2RlIH0pLFxuICAgIF0sXG4gIH0pO1xuXG4gIC8vIEZhaWwgZmFzdDogdGhlIGdhdGUgbmVlZHMgU01UUCB0byBlbWFpbCBjb2Rlcy4gYGdldEVtYWlsUnVudGltZSgpYCByZXNvbHZlcyB0b1xuICAvLyBgbW9kZTogXCJmaWxlXCJgIChvdXRib3gpIG9yIHRocm93cyB3aGVuIG5vIFNNVFAgY3JlZHMgYXJlIGNvbmZpZ3VyZWQgLSBuZWl0aGVyXG4gIC8vIGNhbiBkZWxpdmVyIHRvIGEgcmVhbCBpbmJveCwgc28gcmVmdXNlIHRvIGJyaW5nIHVwIGEgZ2F0ZSB0aGF0IGxldHMgbm9ib2R5IGluLlxuICBpZiAoZ2V0RW1haWxSdW50aW1lKCkuY29uZmlnLm1vZGUgIT09IFwic210cFwiKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgXCJlbWFpbCBpcyBub3QgY29uZmlndXJlZCBmb3IgU01UUCBkZWxpdmVyeSAtIHRoZSBPVFAgZ2F0ZSBjYW5ub3Qgc2VuZCBjb2Rlcy4gXCIgK1xuICAgICAgICBcIlNldCBTTVRQX0hPU1QvU01UUF9VU0VSL1NNVFBfUEFTU1dPUkQsIG9yIHBhc3MgLS1pbnNlY3VyZSAvIFRVTk5FTF9JTlNFQ1VSRT10cnVlIHRvIHJ1biB0aGUgdHVubmVsIG9wZW4uXCIsXG4gICAgKTtcbiAgfVxuXG4gIGxvZ2dlci5pbmZvKFwiZ2F0ZSBhcHAgcmVhZHkgKG5vIHNlcnZlcjsgcHJveHktZHJpdmVuKVwiKTtcbiAgcmV0dXJuIGhhbmRsZS5hdXRoR2F0ZSBhcyBBdXRoR2F0ZUFwaTtcbn1cbiJdfQ==
|
package/lib/src/env.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment-variable names the tunnel reads.
|
|
3
|
+
*
|
|
4
|
+
* Every setting is `TUNNEL_`-prefixed, matching the repo convention of naming a
|
|
5
|
+
* variable after the package that owns it (`MASTRA_*`, `TEAMS_*`,
|
|
6
|
+
* `WEB_SEARCH_*`). The gate's original names were unprefixed (`AUTH_SUBJECT`,
|
|
7
|
+
* `PUBLIC_DOMAIN`, ...), which is a real hazard for a package that runs as a
|
|
8
|
+
* WRAPPER: the tunnel and the app it wraps share one environment, so a generic
|
|
9
|
+
* name is one the wrapped app may already use for something else, and
|
|
10
|
+
* `PUBLIC_DOMAIN` in particular reads like an app-wide setting rather than a
|
|
11
|
+
* portr detail. `EMAIL_AUTH_ALLOW` was worse than generic - it sat in
|
|
12
|
+
* `@dbx-tools/email`'s `EMAIL_*` namespace while configuring the gate, not email.
|
|
13
|
+
*
|
|
14
|
+
* Each entry is an {@link EnvKey} list, EARLIEST-WINS, whose first element is the
|
|
15
|
+
* current name and whose remaining elements are the deprecated originals. A
|
|
16
|
+
* deployment set up against the old names keeps working; nothing needs a
|
|
17
|
+
* coordinated rename. Read them through `env.string` / `env.positiveInt` /
|
|
18
|
+
* `env.list`, which accept the list directly.
|
|
19
|
+
*
|
|
20
|
+
* Not renamed:
|
|
21
|
+
*
|
|
22
|
+
* - `DATABRICKS_APP_PORT` - the Databricks Apps runtime contract. The platform
|
|
23
|
+
* sets it; the gate honours it.
|
|
24
|
+
* - `PORTR_TOKEN` / `PORTR_SERVER` / `PORTR_AUTO_ADD_PATH` - upstream
|
|
25
|
+
* [portr](https://github.com/amalshaji/portr)'s own namespace, and
|
|
26
|
+
* `PORTR_AUTO_ADD_PATH` is passed straight to that binary. Renaming these
|
|
27
|
+
* would rename someone else's contract.
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
import type { EnvKey } from "@dbx-tools/shared-core";
|
|
32
|
+
/** Access allow-list patterns (domain / glob / `/regex/`). */
|
|
33
|
+
export declare const ALLOW_ENV: EnvKey;
|
|
34
|
+
/** Subject line for the code email. */
|
|
35
|
+
export declare const SUBJECT_ENV: EnvKey;
|
|
36
|
+
/** Display name used in the code email copy. */
|
|
37
|
+
export declare const BRAND_NAME_ENV: EnvKey;
|
|
38
|
+
/** Line shown immediately above the code in the email. */
|
|
39
|
+
export declare const MESSAGE_ENV: EnvKey;
|
|
40
|
+
/** Session lifetime, in seconds. */
|
|
41
|
+
export declare const SESSION_TTL_ENV: EnvKey;
|
|
42
|
+
/** One-time-code lifetime, in seconds. */
|
|
43
|
+
export declare const CODE_TTL_ENV: EnvKey;
|
|
44
|
+
/** HS256 signing secret for the session JWT. */
|
|
45
|
+
export declare const JWT_SECRET_ENV: EnvKey;
|
|
46
|
+
/**
|
|
47
|
+
* Force-clear cutoff: every session issued BEFORE it stops verifying. Anything
|
|
48
|
+
* `object.toDate` accepts - a date, an ISO instant, epoch seconds/millis, or a
|
|
49
|
+
* relative duration (`-30d`, `7 days ago`).
|
|
50
|
+
*/
|
|
51
|
+
export declare const SESSION_CUTOFF_ENV: EnvKey;
|
|
52
|
+
/** The public `<subdomain>.<server>` portr should serve on. */
|
|
53
|
+
export declare const PUBLIC_DOMAIN_ENV: EnvKey;
|
|
54
|
+
/** Run the tunnel OPEN, with no gate. Ignored unless truthy. */
|
|
55
|
+
export declare const INSECURE_ENV: EnvKey;
|
|
56
|
+
/** Extra `x-` request headers tunnel traffic may forward. */
|
|
57
|
+
export declare const FORWARD_HEADERS_ENV: EnvKey;
|
package/lib/src/env.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment-variable names the tunnel reads.
|
|
3
|
+
*
|
|
4
|
+
* Every setting is `TUNNEL_`-prefixed, matching the repo convention of naming a
|
|
5
|
+
* variable after the package that owns it (`MASTRA_*`, `TEAMS_*`,
|
|
6
|
+
* `WEB_SEARCH_*`). The gate's original names were unprefixed (`AUTH_SUBJECT`,
|
|
7
|
+
* `PUBLIC_DOMAIN`, ...), which is a real hazard for a package that runs as a
|
|
8
|
+
* WRAPPER: the tunnel and the app it wraps share one environment, so a generic
|
|
9
|
+
* name is one the wrapped app may already use for something else, and
|
|
10
|
+
* `PUBLIC_DOMAIN` in particular reads like an app-wide setting rather than a
|
|
11
|
+
* portr detail. `EMAIL_AUTH_ALLOW` was worse than generic - it sat in
|
|
12
|
+
* `@dbx-tools/email`'s `EMAIL_*` namespace while configuring the gate, not email.
|
|
13
|
+
*
|
|
14
|
+
* Each entry is an {@link EnvKey} list, EARLIEST-WINS, whose first element is the
|
|
15
|
+
* current name and whose remaining elements are the deprecated originals. A
|
|
16
|
+
* deployment set up against the old names keeps working; nothing needs a
|
|
17
|
+
* coordinated rename. Read them through `env.string` / `env.positiveInt` /
|
|
18
|
+
* `env.list`, which accept the list directly.
|
|
19
|
+
*
|
|
20
|
+
* Not renamed:
|
|
21
|
+
*
|
|
22
|
+
* - `DATABRICKS_APP_PORT` - the Databricks Apps runtime contract. The platform
|
|
23
|
+
* sets it; the gate honours it.
|
|
24
|
+
* - `PORTR_TOKEN` / `PORTR_SERVER` / `PORTR_AUTO_ADD_PATH` - upstream
|
|
25
|
+
* [portr](https://github.com/amalshaji/portr)'s own namespace, and
|
|
26
|
+
* `PORTR_AUTO_ADD_PATH` is passed straight to that binary. Renaming these
|
|
27
|
+
* would rename someone else's contract.
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
/** Access allow-list patterns (domain / glob / `/regex/`). */
|
|
32
|
+
export const ALLOW_ENV = ["TUNNEL_AUTH_ALLOW", "EMAIL_AUTH_ALLOW"];
|
|
33
|
+
/** Subject line for the code email. */
|
|
34
|
+
export const SUBJECT_ENV = ["TUNNEL_AUTH_SUBJECT", "AUTH_SUBJECT"];
|
|
35
|
+
/** Display name used in the code email copy. */
|
|
36
|
+
export const BRAND_NAME_ENV = ["TUNNEL_AUTH_BRAND_NAME", "AUTH_BRAND_NAME"];
|
|
37
|
+
/** Line shown immediately above the code in the email. */
|
|
38
|
+
export const MESSAGE_ENV = ["TUNNEL_AUTH_MESSAGE", "AUTH_MESSAGE"];
|
|
39
|
+
/** Session lifetime, in seconds. */
|
|
40
|
+
export const SESSION_TTL_ENV = ["TUNNEL_AUTH_SESSION_TTL", "AUTH_SESSION_TTL"];
|
|
41
|
+
/** One-time-code lifetime, in seconds. */
|
|
42
|
+
export const CODE_TTL_ENV = ["TUNNEL_AUTH_CODE_TTL", "AUTH_CODE_TTL"];
|
|
43
|
+
/** HS256 signing secret for the session JWT. */
|
|
44
|
+
export const JWT_SECRET_ENV = ["TUNNEL_AUTH_JWT_SECRET", "AUTH_JWT_SECRET"];
|
|
45
|
+
/**
|
|
46
|
+
* Force-clear cutoff: every session issued BEFORE it stops verifying. Anything
|
|
47
|
+
* `object.toDate` accepts - a date, an ISO instant, epoch seconds/millis, or a
|
|
48
|
+
* relative duration (`-30d`, `7 days ago`).
|
|
49
|
+
*/
|
|
50
|
+
export const SESSION_CUTOFF_ENV = [
|
|
51
|
+
"TUNNEL_AUTH_SESSION_CUTOFF",
|
|
52
|
+
"TUNNEL_AUTH_SESSION_EPOCH",
|
|
53
|
+
];
|
|
54
|
+
/** The public `<subdomain>.<server>` portr should serve on. */
|
|
55
|
+
export const PUBLIC_DOMAIN_ENV = ["TUNNEL_PUBLIC_DOMAIN", "PUBLIC_DOMAIN"];
|
|
56
|
+
/** Run the tunnel OPEN, with no gate. Ignored unless truthy. */
|
|
57
|
+
export const INSECURE_ENV = "TUNNEL_INSECURE";
|
|
58
|
+
/** Extra `x-` request headers tunnel traffic may forward. */
|
|
59
|
+
export const FORWARD_HEADERS_ENV = "TUNNEL_FORWARD_HEADERS";
|
|
60
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2Vudi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2Qkc7QUFJSCw4REFBOEQ7QUFDOUQsTUFBTSxDQUFDLE1BQU0sU0FBUyxHQUFXLENBQUMsbUJBQW1CLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztBQUUzRSx1Q0FBdUM7QUFDdkMsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFXLENBQUMscUJBQXFCLEVBQUUsY0FBYyxDQUFDLENBQUM7QUFFM0UsZ0RBQWdEO0FBQ2hELE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBVyxDQUFDLHdCQUF3QixFQUFFLGlCQUFpQixDQUFDLENBQUM7QUFFcEYsMERBQTBEO0FBQzFELE1BQU0sQ0FBQyxNQUFNLFdBQVcsR0FBVyxDQUFDLHFCQUFxQixFQUFFLGNBQWMsQ0FBQyxDQUFDO0FBRTNFLG9DQUFvQztBQUNwQyxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQVcsQ0FBQyx5QkFBeUIsRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO0FBRXZGLDBDQUEwQztBQUMxQyxNQUFNLENBQUMsTUFBTSxZQUFZLEdBQVcsQ0FBQyxzQkFBc0IsRUFBRSxlQUFlLENBQUMsQ0FBQztBQUU5RSxnREFBZ0Q7QUFDaEQsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFXLENBQUMsd0JBQXdCLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztBQUVwRjs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQVc7SUFDeEMsNEJBQTRCO0lBQzVCLDJCQUEyQjtDQUM1QixDQUFDO0FBRUYsK0RBQStEO0FBQy9ELE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFXLENBQUMsc0JBQXNCLEVBQUUsZUFBZSxDQUFDLENBQUM7QUFFbkYsZ0VBQWdFO0FBQ2hFLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBVyxpQkFBaUIsQ0FBQztBQUV0RCw2REFBNkQ7QUFDN0QsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQVcsd0JBQXdCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEVudmlyb25tZW50LXZhcmlhYmxlIG5hbWVzIHRoZSB0dW5uZWwgcmVhZHMuXG4gKlxuICogRXZlcnkgc2V0dGluZyBpcyBgVFVOTkVMX2AtcHJlZml4ZWQsIG1hdGNoaW5nIHRoZSByZXBvIGNvbnZlbnRpb24gb2YgbmFtaW5nIGFcbiAqIHZhcmlhYmxlIGFmdGVyIHRoZSBwYWNrYWdlIHRoYXQgb3ducyBpdCAoYE1BU1RSQV8qYCwgYFRFQU1TXypgLFxuICogYFdFQl9TRUFSQ0hfKmApLiBUaGUgZ2F0ZSdzIG9yaWdpbmFsIG5hbWVzIHdlcmUgdW5wcmVmaXhlZCAoYEFVVEhfU1VCSkVDVGAsXG4gKiBgUFVCTElDX0RPTUFJTmAsIC4uLiksIHdoaWNoIGlzIGEgcmVhbCBoYXphcmQgZm9yIGEgcGFja2FnZSB0aGF0IHJ1bnMgYXMgYVxuICogV1JBUFBFUjogdGhlIHR1bm5lbCBhbmQgdGhlIGFwcCBpdCB3cmFwcyBzaGFyZSBvbmUgZW52aXJvbm1lbnQsIHNvIGEgZ2VuZXJpY1xuICogbmFtZSBpcyBvbmUgdGhlIHdyYXBwZWQgYXBwIG1heSBhbHJlYWR5IHVzZSBmb3Igc29tZXRoaW5nIGVsc2UsIGFuZFxuICogYFBVQkxJQ19ET01BSU5gIGluIHBhcnRpY3VsYXIgcmVhZHMgbGlrZSBhbiBhcHAtd2lkZSBzZXR0aW5nIHJhdGhlciB0aGFuIGFcbiAqIHBvcnRyIGRldGFpbC4gYEVNQUlMX0FVVEhfQUxMT1dgIHdhcyB3b3JzZSB0aGFuIGdlbmVyaWMgLSBpdCBzYXQgaW5cbiAqIGBAZGJ4LXRvb2xzL2VtYWlsYCdzIGBFTUFJTF8qYCBuYW1lc3BhY2Ugd2hpbGUgY29uZmlndXJpbmcgdGhlIGdhdGUsIG5vdCBlbWFpbC5cbiAqXG4gKiBFYWNoIGVudHJ5IGlzIGFuIHtAbGluayBFbnZLZXl9IGxpc3QsIEVBUkxJRVNULVdJTlMsIHdob3NlIGZpcnN0IGVsZW1lbnQgaXMgdGhlXG4gKiBjdXJyZW50IG5hbWUgYW5kIHdob3NlIHJlbWFpbmluZyBlbGVtZW50cyBhcmUgdGhlIGRlcHJlY2F0ZWQgb3JpZ2luYWxzLiBBXG4gKiBkZXBsb3ltZW50IHNldCB1cCBhZ2FpbnN0IHRoZSBvbGQgbmFtZXMga2VlcHMgd29ya2luZzsgbm90aGluZyBuZWVkcyBhXG4gKiBjb29yZGluYXRlZCByZW5hbWUuIFJlYWQgdGhlbSB0aHJvdWdoIGBlbnYuc3RyaW5nYCAvIGBlbnYucG9zaXRpdmVJbnRgIC9cbiAqIGBlbnYubGlzdGAsIHdoaWNoIGFjY2VwdCB0aGUgbGlzdCBkaXJlY3RseS5cbiAqXG4gKiBOb3QgcmVuYW1lZDpcbiAqXG4gKiAgIC0gYERBVEFCUklDS1NfQVBQX1BPUlRgIC0gdGhlIERhdGFicmlja3MgQXBwcyBydW50aW1lIGNvbnRyYWN0LiBUaGUgcGxhdGZvcm1cbiAqICAgICBzZXRzIGl0OyB0aGUgZ2F0ZSBob25vdXJzIGl0LlxuICogICAtIGBQT1JUUl9UT0tFTmAgLyBgUE9SVFJfU0VSVkVSYCAvIGBQT1JUUl9BVVRPX0FERF9QQVRIYCAtIHVwc3RyZWFtXG4gKiAgICAgW3BvcnRyXShodHRwczovL2dpdGh1Yi5jb20vYW1hbHNoYWppL3BvcnRyKSdzIG93biBuYW1lc3BhY2UsIGFuZFxuICogICAgIGBQT1JUUl9BVVRPX0FERF9QQVRIYCBpcyBwYXNzZWQgc3RyYWlnaHQgdG8gdGhhdCBiaW5hcnkuIFJlbmFtaW5nIHRoZXNlXG4gKiAgICAgd291bGQgcmVuYW1lIHNvbWVvbmUgZWxzZSdzIGNvbnRyYWN0LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgdHlwZSB7IEVudktleSB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5cbi8qKiBBY2Nlc3MgYWxsb3ctbGlzdCBwYXR0ZXJucyAoZG9tYWluIC8gZ2xvYiAvIGAvcmVnZXgvYCkuICovXG5leHBvcnQgY29uc3QgQUxMT1dfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9BTExPV1wiLCBcIkVNQUlMX0FVVEhfQUxMT1dcIl07XG5cbi8qKiBTdWJqZWN0IGxpbmUgZm9yIHRoZSBjb2RlIGVtYWlsLiAqL1xuZXhwb3J0IGNvbnN0IFNVQkpFQ1RfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9TVUJKRUNUXCIsIFwiQVVUSF9TVUJKRUNUXCJdO1xuXG4vKiogRGlzcGxheSBuYW1lIHVzZWQgaW4gdGhlIGNvZGUgZW1haWwgY29weS4gKi9cbmV4cG9ydCBjb25zdCBCUkFORF9OQU1FX0VOVjogRW52S2V5ID0gW1wiVFVOTkVMX0FVVEhfQlJBTkRfTkFNRVwiLCBcIkFVVEhfQlJBTkRfTkFNRVwiXTtcblxuLyoqIExpbmUgc2hvd24gaW1tZWRpYXRlbHkgYWJvdmUgdGhlIGNvZGUgaW4gdGhlIGVtYWlsLiAqL1xuZXhwb3J0IGNvbnN0IE1FU1NBR0VfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9NRVNTQUdFXCIsIFwiQVVUSF9NRVNTQUdFXCJdO1xuXG4vKiogU2Vzc2lvbiBsaWZldGltZSwgaW4gc2Vjb25kcy4gKi9cbmV4cG9ydCBjb25zdCBTRVNTSU9OX1RUTF9FTlY6IEVudktleSA9IFtcIlRVTk5FTF9BVVRIX1NFU1NJT05fVFRMXCIsIFwiQVVUSF9TRVNTSU9OX1RUTFwiXTtcblxuLyoqIE9uZS10aW1lLWNvZGUgbGlmZXRpbWUsIGluIHNlY29uZHMuICovXG5leHBvcnQgY29uc3QgQ09ERV9UVExfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9DT0RFX1RUTFwiLCBcIkFVVEhfQ09ERV9UVExcIl07XG5cbi8qKiBIUzI1NiBzaWduaW5nIHNlY3JldCBmb3IgdGhlIHNlc3Npb24gSldULiAqL1xuZXhwb3J0IGNvbnN0IEpXVF9TRUNSRVRfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9KV1RfU0VDUkVUXCIsIFwiQVVUSF9KV1RfU0VDUkVUXCJdO1xuXG4vKipcbiAqIEZvcmNlLWNsZWFyIGN1dG9mZjogZXZlcnkgc2Vzc2lvbiBpc3N1ZWQgQkVGT1JFIGl0IHN0b3BzIHZlcmlmeWluZy4gQW55dGhpbmdcbiAqIGBvYmplY3QudG9EYXRlYCBhY2NlcHRzIC0gYSBkYXRlLCBhbiBJU08gaW5zdGFudCwgZXBvY2ggc2Vjb25kcy9taWxsaXMsIG9yIGFcbiAqIHJlbGF0aXZlIGR1cmF0aW9uIChgLTMwZGAsIGA3IGRheXMgYWdvYCkuXG4gKi9cbmV4cG9ydCBjb25zdCBTRVNTSU9OX0NVVE9GRl9FTlY6IEVudktleSA9IFtcbiAgXCJUVU5ORUxfQVVUSF9TRVNTSU9OX0NVVE9GRlwiLFxuICBcIlRVTk5FTF9BVVRIX1NFU1NJT05fRVBPQ0hcIixcbl07XG5cbi8qKiBUaGUgcHVibGljIGA8c3ViZG9tYWluPi48c2VydmVyPmAgcG9ydHIgc2hvdWxkIHNlcnZlIG9uLiAqL1xuZXhwb3J0IGNvbnN0IFBVQkxJQ19ET01BSU5fRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfUFVCTElDX0RPTUFJTlwiLCBcIlBVQkxJQ19ET01BSU5cIl07XG5cbi8qKiBSdW4gdGhlIHR1bm5lbCBPUEVOLCB3aXRoIG5vIGdhdGUuIElnbm9yZWQgdW5sZXNzIHRydXRoeS4gKi9cbmV4cG9ydCBjb25zdCBJTlNFQ1VSRV9FTlY6IEVudktleSA9IFwiVFVOTkVMX0lOU0VDVVJFXCI7XG5cbi8qKiBFeHRyYSBgeC1gIHJlcXVlc3QgaGVhZGVycyB0dW5uZWwgdHJhZmZpYyBtYXkgZm9yd2FyZC4gKi9cbmV4cG9ydCBjb25zdCBGT1JXQVJEX0hFQURFUlNfRU5WOiBFbnZLZXkgPSBcIlRVTk5FTF9GT1JXQVJEX0hFQURFUlNcIjtcbiJdfQ==
|