@dbx-tools/cli-tunnel 0.6.54 → 0.6.55

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 CHANGED
@@ -29,7 +29,19 @@ address against an allow-list, verified by a code sent over
29
29
  code input carries `autocomplete="one-time-code"`. iOS, Gmail, Outlook, and
30
30
  Android detect a code from that shape and offer it directly from the
31
31
  notification; novel phrasing is what breaks the detection. Both MIME parts carry
32
- the code as visible text, never an image.
32
+ the code as visible text, never an image, and no trailer follows the copy.
33
+ - The two MIME parts are built separately, on purpose. The HTML part is the full
34
+ branded template with the code as a large styled heading; the `text/plain` part
35
+ is authored directly, keeping the prompt and the code on ONE line
36
+ (`Your verification code is: 123456`) because that is the shape client code
37
+ detection reads most reliably. A generated text part cannot hold it - the text
38
+ part is a rendering of the HTML, so the heading's CSS margin arrives as blank
39
+ lines and autofill stops being offered while the HTML still looks perfect. See
40
+ `codeEmailTextBody` in the `app` module.
41
+ - The sign-in code is SYSTEM mail: it sends from `no-reply@EMAIL_DOMAIN` (or
42
+ `EMAIL_SYSTEM_FROM`), never a person's address, since a reply to a
43
+ machine-generated code reaches nobody. `EMAIL_FROM` is not required - see
44
+ [`@dbx-tools/email`](../../node/email#sender-addresses).
33
45
  - Email one-time-code gate: a 6-digit code stored as a SHA-256 hash with an
34
46
  attempt counter, verified in constant time, in AppKit's `CacheManager` (memory
35
47
  by default, Lakebase when the app configures persistent `CacheStorage`) so TTL
package/lib/src/app.d.ts CHANGED
@@ -11,8 +11,10 @@
11
11
  *
12
12
  * It returns the {@link AuthGateApi} the proxy drives. `sendCode` is wired here
13
13
  * (the plugin can't resolve HOW to send on its own) to the email plugin's
14
- * transport, resolving the `From` from the email runtime config (EMAIL_FROM /
15
- * EMAIL_DOMAIN) since an OTP request has no on-behalf-of user.
14
+ * transport. A sign-in code is SYSTEM mail - no on-behalf-of user asked for it
15
+ * and no reply to it reaches anyone - so it sends from the email config's
16
+ * do-not-reply address (`no-reply@EMAIL_DOMAIN` unless EMAIL_SYSTEM_FROM names
17
+ * another).
16
18
  *
17
19
  * FAIL FAST: a gate that can't email a code is useless, so if email does not
18
20
  * resolve to SMTP mode (real delivery), this throws - unless `insecure` is set
@@ -21,7 +23,7 @@
21
23
  *
22
24
  * @module
23
25
  */
24
- import { type AuthGateApi, type AuthGateConfig } from "./plugin.ts";
26
+ import { type AuthGateApi, type AuthGateConfig, type SendCodeOptions } from "./plugin.ts";
25
27
  /**
26
28
  * A code TTL as the plain phrase the email states ("10 minutes", "45 seconds").
27
29
  *
@@ -30,6 +32,33 @@ import { type AuthGateApi, type AuthGateConfig } from "./plugin.ts";
30
32
  * never told the code lives longer than it does.
31
33
  */
32
34
  export declare function expiresIn(seconds: number): string;
35
+ /** The parts of {@link SendCodeOptions} the code email's copy is built from. */
36
+ type CodeCopy = Pick<SendCodeOptions, "message" | "codeTtlSeconds">;
37
+ /**
38
+ * The HTML part's source: the full branded template, with the code as a large
39
+ * styled heading (`## ` is what makes it prominent in an inbox).
40
+ */
41
+ export declare function codeEmailHtmlBody(code: string, opts: CodeCopy): string;
42
+ /**
43
+ * The `text/plain` part, supplied EXPLICITLY rather than rendered from the tree
44
+ * above. Both parts say the same thing; only the line layout differs.
45
+ *
46
+ * The prompt and the code share ONE line ("Your verification code is: 123456").
47
+ * That single-line shape is what iOS, Gmail, Outlook, and Android code detection
48
+ * keys on most reliably - the heuristics look for a code in the same sentence as
49
+ * a recognized prompt, so splitting them across lines makes detection dependent
50
+ * on the client, and any blank line between them defeats it outright.
51
+ *
52
+ * The GENERATED text part cannot hold that shape at all: it is a rendering of the
53
+ * HTML, so it carries the brand header/footer and turns the code heading's CSS
54
+ * margin into blank lines, arriving as `prompt\n\n\ncode`.
55
+ *
56
+ * The code is visible text in BOTH parts, never an image, so a client scraping
57
+ * either one finds it. No trailer line follows the copy - Apple's domain-bound
58
+ * `@domain #code` footer is deliberately NOT emitted, since it constrains the
59
+ * code to one origin and is not what the broadly-compatible shape needs.
60
+ */
61
+ export declare function codeEmailTextBody(code: string, opts: CodeCopy): string;
33
62
  /**
34
63
  * Boot the gate app and return the API the proxy calls. Throws when email is not
35
64
  * in SMTP mode (no way to deliver a code) so a misconfigured gate fails fast at
@@ -37,3 +66,4 @@ export declare function expiresIn(seconds: number): string;
37
66
  * fall back to insecure/open mode when the operator opted in.
38
67
  */
39
68
  export declare function startGateApp(config: AuthGateConfig): Promise<AuthGateApi>;
69
+ export {};
package/lib/src/app.js CHANGED
@@ -11,8 +11,10 @@
11
11
  *
12
12
  * It returns the {@link AuthGateApi} the proxy drives. `sendCode` is wired here
13
13
  * (the plugin can't resolve HOW to send on its own) to the email plugin's
14
- * transport, resolving the `From` from the email runtime config (EMAIL_FROM /
15
- * EMAIL_DOMAIN) since an OTP request has no on-behalf-of user.
14
+ * transport. A sign-in code is SYSTEM mail - no on-behalf-of user asked for it
15
+ * and no reply to it reaches anyone - so it sends from the email config's
16
+ * do-not-reply address (`no-reply@EMAIL_DOMAIN` unless EMAIL_SYSTEM_FROM names
17
+ * another).
16
18
  *
17
19
  * FAIL FAST: a gate that can't email a code is useless, so if email does not
18
20
  * resolve to SMTP mode (real delivery), this throws - unless `insecure` is set
@@ -39,8 +41,52 @@ export function expiresIn(seconds) {
39
41
  ? string.pluralize(seconds / 60, "minute")
40
42
  : string.pluralize(seconds, "second");
41
43
  }
44
+ /** The reassurance line closing both parts. */
45
+ const IGNORE_LINE = "If you did not request this code, you can ignore this email.";
46
+ /**
47
+ * The HTML part's source: the full branded template, with the code as a large
48
+ * styled heading (`## ` is what makes it prominent in an inbox).
49
+ */
50
+ export function codeEmailHtmlBody(code, opts) {
51
+ return [
52
+ opts.message,
53
+ "",
54
+ `## ${code}`,
55
+ "",
56
+ `This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
57
+ "",
58
+ IGNORE_LINE,
59
+ ].join("\n");
60
+ }
61
+ /**
62
+ * The `text/plain` part, supplied EXPLICITLY rather than rendered from the tree
63
+ * above. Both parts say the same thing; only the line layout differs.
64
+ *
65
+ * The prompt and the code share ONE line ("Your verification code is: 123456").
66
+ * That single-line shape is what iOS, Gmail, Outlook, and Android code detection
67
+ * keys on most reliably - the heuristics look for a code in the same sentence as
68
+ * a recognized prompt, so splitting them across lines makes detection dependent
69
+ * on the client, and any blank line between them defeats it outright.
70
+ *
71
+ * The GENERATED text part cannot hold that shape at all: it is a rendering of the
72
+ * HTML, so it carries the brand header/footer and turns the code heading's CSS
73
+ * margin into blank lines, arriving as `prompt\n\n\ncode`.
74
+ *
75
+ * The code is visible text in BOTH parts, never an image, so a client scraping
76
+ * either one finds it. No trailer line follows the copy - Apple's domain-bound
77
+ * `@domain #code` footer is deliberately NOT emitted, since it constrains the
78
+ * code to one origin and is not what the broadly-compatible shape needs.
79
+ */
80
+ export function codeEmailTextBody(code, opts) {
81
+ return [
82
+ `${opts.message} ${code}`,
83
+ `This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
84
+ "",
85
+ IGNORE_LINE,
86
+ ].join("\n");
87
+ }
42
88
  const { createApp } = createAppNs;
43
- const { resolveSenderAddress } = sender;
89
+ const { resolveSystemSenderAddress } = sender;
44
90
  const { getEmailRuntime, sendEmail } = transport;
45
91
  const { emailBrandFromContext } = emailBrand;
46
92
  const { loadBrandContext } = nodeBrand;
@@ -60,30 +106,16 @@ export async function startGateApp(config) {
60
106
  // `sendCode` delivers the OTP through the email plugin's SHARED transport, which
61
107
  // the `email()` plugin primes during its `setup()` (awaited by `createApp`
62
108
  // below). Using the module-level `sendEmail` avoids a circular dependency on the
63
- // app handle's inferred type. `From` is the app's configured sender (EMAIL_FROM /
64
- // EMAIL_DOMAIN); the code email has no OBO user, so it resolves with `undefined`.
109
+ // app handle's inferred type. The `From` is the SYSTEM sender: a verification
110
+ // code is machine-generated and unanswerable, so it must not arrive from a
111
+ // person's address inviting a reply.
65
112
  const sendCode = async (to, code, opts) => {
66
- const from = resolveSenderAddress(getEmailRuntime().config, undefined);
113
+ const from = resolveSystemSenderAddress(getEmailRuntime().config);
67
114
  await sendEmail({
68
115
  to: [to],
69
116
  subject: opts.subject,
70
- // Deliberately the conventional one-time-code layout: the prompt line,
71
- // then the bare code ALONE on the next line, then the expiry. iOS, Gmail,
72
- // Outlook, and Android all detect a code from this shape and offer to
73
- // autofill it, and anything more decorative is what breaks that. The code
74
- // stays visible TEXT in both MIME parts (the email plugin renders the
75
- // HTML and plain-text alternatives from one React Email tree), never an
76
- // image, so a client that scrapes the text part still finds it.
77
- body: [
78
- opts.message,
79
- "",
80
- `## ${code}`,
81
- "",
82
- `This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
83
- "",
84
- "If you did not request this code, you can ignore this email.",
85
- ].join("\n"),
86
- }, from);
117
+ body: codeEmailHtmlBody(code, opts),
118
+ }, from, undefined, { text: codeEmailTextBody(code, opts) });
87
119
  };
88
120
  // `createApp` namespaces each plugin's exports on the handle by manifest name;
89
121
  // `handle.authGate` is the in-process gate API the proxy drives.
@@ -111,4 +143,4 @@ export async function startGateApp(config) {
111
143
  logger.info("gate app ready (no server; proxy-driven)");
112
144
  return handle.authGate;
113
145
  }
114
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCRztBQUVILE9BQU8sRUFBRSxTQUFTLElBQUksV0FBVyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDN0QsT0FBTyxFQUFFLEtBQUssSUFBSSxTQUFTLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsS0FBSyxJQUFJLFVBQVUsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQ2pGLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxFQUFFLFFBQVEsRUFBeUMsTUFBTSxhQUFhLENBQUM7QUFFOUUsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUV4Qzs7Ozs7O0dBTUc7QUFDSCxNQUFNLFVBQVUsU0FBUyxDQUFDLE9BQWU7SUFDdkMsT0FBTyxPQUFPLElBQUksRUFBRSxJQUFJLE9BQU8sR0FBRyxFQUFFLEtBQUssQ0FBQztRQUN4QyxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxPQUFPLEdBQUcsRUFBRSxFQUFFLFFBQVEsQ0FBQztRQUMxQyxDQUFDLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxPQUFPLEVBQUUsUUFBUSxDQUFDLENBQUM7QUFDMUMsQ0FBQztBQUVELE1BQU0sRUFBRSxTQUFTLEVBQUUsR0FBRyxXQUFXLENBQUM7QUFDbEMsTUFBTSxFQUFFLG9CQUFvQixFQUFFLEdBQUcsTUFBTSxDQUFDO0FBQ3hDLE1BQU0sRUFBRSxlQUFlLEVBQUUsU0FBUyxFQUFFLEdBQUcsU0FBUyxDQUFDO0FBQ2pELE1BQU0sRUFBRSxxQkFBcUIsRUFBRSxHQUFHLFVBQVUsQ0FBQztBQUM3QyxNQUFNLEVBQUUsZ0JBQWdCLEVBQUUsR0FBRyxTQUFTLENBQUM7QUFFdkM7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFlBQVksQ0FBQyxNQUFzQjtJQUN2RCw4RUFBOEU7SUFDOUUsOEVBQThFO0lBQzlFLDZFQUE2RTtJQUM3RSw2RUFBNkU7SUFDN0UseUVBQXlFO0lBQ3pFLE1BQU0sT0FBTyxHQUFHLE1BQU0sZ0JBQWdCLEVBQUUsQ0FBQztJQUV6QyxpRkFBaUY7SUFDakYsMkVBQTJFO0lBQzNFLGlGQUFpRjtJQUNqRixrRkFBa0Y7SUFDbEYsa0ZBQWtGO0lBQ2xGLE1BQU0sUUFBUSxHQUE0QyxLQUFLLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsRUFBRTtRQUNqRixNQUFNLElBQUksR0FBRyxvQkFBb0IsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxNQUFNLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDdkUsTUFBTSxTQUFTLENBQ2I7WUFDRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUM7WUFDUixPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87WUFDckIsdUVBQXVFO1lBQ3ZFLDBFQUEwRTtZQUMxRSxzRUFBc0U7WUFDdEUsMEVBQTBFO1lBQzFFLHNFQUFzRTtZQUN0RSx3RUFBd0U7WUFDeEUsZ0VBQWdFO1lBQ2hFLElBQUksRUFBRTtnQkFDSixJQUFJLENBQUMsT0FBTztnQkFDWixFQUFFO2dCQUNGLE1BQU0sSUFBSSxFQUFFO2dCQUNaLEVBQUU7Z0JBQ0Ysd0JBQXdCLFNBQVMsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUc7Z0JBQ3pELEVBQUU7Z0JBQ0YsOERBQThEO2FBQy9ELENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQztTQUNiLEVBQ0QsSUFBSSxDQUNMLENBQUM7SUFDSixDQUFDLENBQUM7SUFFRiwrRUFBK0U7SUFDL0UsaUVBQWlFO0lBQ2pFLE1BQU0sTUFBTSxHQUFHLE1BQU0sU0FBUyxDQUFDO1FBQzdCLE9BQU8sRUFBRTtZQUNQLHdFQUF3RTtZQUN4RSw0RUFBNEU7WUFDNUUsMEVBQTBFO1lBQzFFLEtBQUssQ0FBQyxFQUFFLEtBQUssRUFBRSxxQkFBcUIsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO1lBQ2hELHdFQUF3RTtZQUN4RSw0RUFBNEU7WUFDNUUscUVBQXFFO1lBQ3JFLDJFQUEyRTtZQUMzRSxnRUFBZ0U7WUFDaEUsUUFBUSxDQUFDLEVBQUUsR0FBRyxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sQ0FBQyxTQUFTLElBQUksT0FBTyxDQUFDLElBQUksRUFBRSxRQUFRLEVBQUUsQ0FBQztTQUMvRTtLQUNGLENBQUMsQ0FBQztJQUVILGlGQUFpRjtJQUNqRixnRkFBZ0Y7SUFDaEYsaUZBQWlGO0lBQ2pGLElBQUksZUFBZSxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUUsQ0FBQztRQUM3QyxNQUFNLElBQUksS0FBSyxDQUNiLDhFQUE4RTtZQUM1RSwwR0FBMEcsQ0FDN0csQ0FBQztJQUNKLENBQUM7SUFFRCxNQUFNLENBQUMsSUFBSSxDQUFDLDBDQUEwQyxDQUFDLENBQUM7SUFDeEQsT0FBTyxNQUFNLENBQUMsUUFBdUIsQ0FBQztBQUN4QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGUgdHVubmVsIGdhdGUncyB0aW55IEFwcEtpdCBcImFwcFwiIC0gYGNyZWF0ZUFwcGAgV0lUSE9VVCBhIGBzZXJ2ZXIoKWAgcGx1Z2luLlxuICpcbiAqIFRoZXJlIGlzIG5vIEhUVFAgc2VydmVyIGhlcmU6IHRoZSB0dW5uZWwgcHJveHkgaXMgdGhlIHNlcnZlciwgYW5kIGl0IGNhbGxzIHRoZVxuICogZ2F0ZSBoYW5kbGVycyBpbi1wcm9jZXNzLiBgY3JlYXRlQXBwYCBpcyB1c2VkIG9ubHkgZm9yIHdoYXQgaXQgYXV0by13aXJlczpcbiAqICAgLSBgQ2FjaGVNYW5hZ2VyYCAobWVtb3J5LCBvciBMYWtlYmFzZSB3aGVuIHRoZSBkZXBsb3kgY29uZmlndXJlcyBwZXJzaXN0ZW50XG4gKiAgICAgY2FjaGUgc3RvcmFnZSkgLSB3aGljaCB0aGUgT1RQIGBDb2RlU3RvcmVgIHVzZXMgZm9yIHN0b3JhZ2UgKyBUVEwgZXZpY3Rpb247XG4gKiAgIC0gdGhlIGBlbWFpbCgpYCB0cmFuc3BvcnQgKFNNVFAgLyBvdXRib3gpLCBwcmltZWQgZnJvbSBlbnYsIHNvIGBzZW5kQ29kZWAgY2FuXG4gKiAgICAgZGVsaXZlciB0aGUgb25lLXRpbWUgY29kZTtcbiAqICAgLSBkYngtdG9vbHMgYXV0by1jb25maWd1cmF0aW9uICsgYnJhbmRpbmcgdmlhIGBAZGJ4LXRvb2xzL2FwcGtpdGAuXG4gKlxuICogSXQgcmV0dXJucyB0aGUge0BsaW5rIEF1dGhHYXRlQXBpfSB0aGUgcHJveHkgZHJpdmVzLiBgc2VuZENvZGVgIGlzIHdpcmVkIGhlcmVcbiAqICh0aGUgcGx1Z2luIGNhbid0IHJlc29sdmUgSE9XIHRvIHNlbmQgb24gaXRzIG93bikgdG8gdGhlIGVtYWlsIHBsdWdpbidzXG4gKiB0cmFuc3BvcnQsIHJlc29sdmluZyB0aGUgYEZyb21gIGZyb20gdGhlIGVtYWlsIHJ1bnRpbWUgY29uZmlnIChFTUFJTF9GUk9NIC9cbiAqIEVNQUlMX0RPTUFJTikgc2luY2UgYW4gT1RQIHJlcXVlc3QgaGFzIG5vIG9uLWJlaGFsZi1vZiB1c2VyLlxuICpcbiAqIEZBSUwgRkFTVDogYSBnYXRlIHRoYXQgY2FuJ3QgZW1haWwgYSBjb2RlIGlzIHVzZWxlc3MsIHNvIGlmIGVtYWlsIGRvZXMgbm90XG4gKiByZXNvbHZlIHRvIFNNVFAgbW9kZSAocmVhbCBkZWxpdmVyeSksIHRoaXMgdGhyb3dzIC0gdW5sZXNzIGBpbnNlY3VyZWAgaXMgc2V0XG4gKiAoYC0taW5zZWN1cmVgIC8gYFRVTk5FTF9JTlNFQ1VSRT10cnVlYCksIGluIHdoaWNoIGNhc2UgdGhlIGNhbGxlciBydW5zIHRoZVxuICogdHVubmVsIE9QRU4gd2l0aCBubyBnYXRlLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVBcHAgYXMgY3JlYXRlQXBwTnMgfSBmcm9tIFwiQGRieC10b29scy9hcHBraXRcIjtcbmltcG9ydCB7IGJyYW5kIGFzIG5vZGVCcmFuZCB9IGZyb20gXCJAZGJ4LXRvb2xzL2NvcmVcIjtcbmltcG9ydCB7IGJyYW5kIGFzIGVtYWlsQnJhbmQsIGVtYWlsLCBzZW5kZXIsIHRyYW5zcG9ydCB9IGZyb20gXCJAZGJ4LXRvb2xzL2VtYWlsXCI7XG5pbXBvcnQgeyBsb2csIHN0cmluZyB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5pbXBvcnQgeyBhdXRoR2F0ZSwgdHlwZSBBdXRoR2F0ZUFwaSwgdHlwZSBBdXRoR2F0ZUNvbmZpZyB9IGZyb20gXCIuL3BsdWdpbi50c1wiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwidHVubmVsOmFwcFwiKTtcblxuLyoqXG4gKiBBIGNvZGUgVFRMIGFzIHRoZSBwbGFpbiBwaHJhc2UgdGhlIGVtYWlsIHN0YXRlcyAoXCIxMCBtaW51dGVzXCIsIFwiNDUgc2Vjb25kc1wiKS5cbiAqXG4gKiBXaG9sZSBtaW51dGVzIHJlYWQgYXMgbWludXRlczsgYW55dGhpbmcgZWxzZSBzdGF5cyBpbiBzZWNvbmRzIHJhdGhlciB0aGFuXG4gKiByb3VuZGluZywgc28gYSA5MC1zZWNvbmQgVFRMIGlzIG5vdCBhZHZlcnRpc2VkIGFzIFwiMSBtaW51dGVcIiBhbmQgYSByZWNpcGllbnQgaXNcbiAqIG5ldmVyIHRvbGQgdGhlIGNvZGUgbGl2ZXMgbG9uZ2VyIHRoYW4gaXQgZG9lcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGV4cGlyZXNJbihzZWNvbmRzOiBudW1iZXIpOiBzdHJpbmcge1xuICByZXR1cm4gc2Vjb25kcyA+PSA2MCAmJiBzZWNvbmRzICUgNjAgPT09IDBcbiAgICA/IHN0cmluZy5wbHVyYWxpemUoc2Vjb25kcyAvIDYwLCBcIm1pbnV0ZVwiKVxuICAgIDogc3RyaW5nLnBsdXJhbGl6ZShzZWNvbmRzLCBcInNlY29uZFwiKTtcbn1cblxuY29uc3QgeyBjcmVhdGVBcHAgfSA9IGNyZWF0ZUFwcE5zO1xuY29uc3QgeyByZXNvbHZlU2VuZGVyQWRkcmVzcyB9ID0gc2VuZGVyO1xuY29uc3QgeyBnZXRFbWFpbFJ1bnRpbWUsIHNlbmRFbWFpbCB9ID0gdHJhbnNwb3J0O1xuY29uc3QgeyBlbWFpbEJyYW5kRnJvbUNvbnRleHQgfSA9IGVtYWlsQnJhbmQ7XG5jb25zdCB7IGxvYWRCcmFuZENvbnRleHQgfSA9IG5vZGVCcmFuZDtcblxuLyoqXG4gKiBCb290IHRoZSBnYXRlIGFwcCBhbmQgcmV0dXJuIHRoZSBBUEkgdGhlIHByb3h5IGNhbGxzLiBUaHJvd3Mgd2hlbiBlbWFpbCBpcyBub3RcbiAqIGluIFNNVFAgbW9kZSAobm8gd2F5IHRvIGRlbGl2ZXIgYSBjb2RlKSBzbyBhIG1pc2NvbmZpZ3VyZWQgZ2F0ZSBmYWlscyBmYXN0IGF0XG4gKiBzdGFydHVwIHJhdGhlciB0aGFuIHNpbGVudGx5IGFjY2VwdGluZyBub2JvZHk7IHRoZSBjYWxsZXIgbWF5IGNhdGNoIHRoaXMgYW5kXG4gKiBmYWxsIGJhY2sgdG8gaW5zZWN1cmUvb3BlbiBtb2RlIHdoZW4gdGhlIG9wZXJhdG9yIG9wdGVkIGluLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc3RhcnRHYXRlQXBwKGNvbmZpZzogQXV0aEdhdGVDb25maWcpOiBQcm9taXNlPEF1dGhHYXRlQXBpPiB7XG4gIC8vIFRoZSBob3N0IGFwcCdzIG93biBicmFuZCAoYGJyYW5kaW5nL2JyYW5kLnlhbWxgIGRpc2NvdmVyZWQgZnJvbSBjd2QpIG9yIHRoZVxuICAvLyBkYngtdG9vbHMgZGVmYXVsdC4gVGhpcyBpcyB0aGUgT05FIGJyYW5kIHNvdXJjZSBmb3IgdGhlIGdhdGU6IGl0IHN0eWxlcyB0aGVcbiAgLy8gY29kZSBlbWFpbCAoYWNjZW50IGJhbmQsIGZvbnQsIGxvZ28pIHZpYSB0aGUgZW1haWwgcGx1Z2luIEFORCBzdXBwbGllcyB0aGVcbiAgLy8gZGlzcGxheSBuYW1lIHRoZSBjb3B5IHVzZXMsIHNvIGEgZGVwbG95bWVudCB0aGF0IHRoZW1lcyBpdHMgYXBwIHRoZW1lcyBpdHNcbiAgLy8gc2lnbi1pbiBlbWFpbCB3aXRoIGl0LiBBbiBleHBsaWNpdCBgYnJhbmROYW1lYCBzdGlsbCB3aW5zIChzZWUgYmVsb3cpLlxuICBjb25zdCBjb250ZXh0ID0gYXdhaXQgbG9hZEJyYW5kQ29udGV4dCgpO1xuXG4gIC8vIGBzZW5kQ29kZWAgZGVsaXZlcnMgdGhlIE9UUCB0aHJvdWdoIHRoZSBlbWFpbCBwbHVnaW4ncyBTSEFSRUQgdHJhbnNwb3J0LCB3aGljaFxuICAvLyB0aGUgYGVtYWlsKClgIHBsdWdpbiBwcmltZXMgZHVyaW5nIGl0cyBgc2V0dXAoKWAgKGF3YWl0ZWQgYnkgYGNyZWF0ZUFwcGBcbiAgLy8gYmVsb3cpLiBVc2luZyB0aGUgbW9kdWxlLWxldmVsIGBzZW5kRW1haWxgIGF2b2lkcyBhIGNpcmN1bGFyIGRlcGVuZGVuY3kgb24gdGhlXG4gIC8vIGFwcCBoYW5kbGUncyBpbmZlcnJlZCB0eXBlLiBgRnJvbWAgaXMgdGhlIGFwcCdzIGNvbmZpZ3VyZWQgc2VuZGVyIChFTUFJTF9GUk9NIC9cbiAgLy8gRU1BSUxfRE9NQUlOKTsgdGhlIGNvZGUgZW1haWwgaGFzIG5vIE9CTyB1c2VyLCBzbyBpdCByZXNvbHZlcyB3aXRoIGB1bmRlZmluZWRgLlxuICBjb25zdCBzZW5kQ29kZTogTm9uTnVsbGFibGU8QXV0aEdhdGVDb25maWdbXCJzZW5kQ29kZVwiXT4gPSBhc3luYyAodG8sIGNvZGUsIG9wdHMpID0+IHtcbiAgICBjb25zdCBmcm9tID0gcmVzb2x2ZVNlbmRlckFkZHJlc3MoZ2V0RW1haWxSdW50aW1lKCkuY29uZmlnLCB1bmRlZmluZWQpO1xuICAgIGF3YWl0IHNlbmRFbWFpbChcbiAgICAgIHtcbiAgICAgICAgdG86IFt0b10sXG4gICAgICAgIHN1YmplY3Q6IG9wdHMuc3ViamVjdCxcbiAgICAgICAgLy8gRGVsaWJlcmF0ZWx5IHRoZSBjb252ZW50aW9uYWwgb25lLXRpbWUtY29kZSBsYXlvdXQ6IHRoZSBwcm9tcHQgbGluZSxcbiAgICAgICAgLy8gdGhlbiB0aGUgYmFyZSBjb2RlIEFMT05FIG9uIHRoZSBuZXh0IGxpbmUsIHRoZW4gdGhlIGV4cGlyeS4gaU9TLCBHbWFpbCxcbiAgICAgICAgLy8gT3V0bG9vaywgYW5kIEFuZHJvaWQgYWxsIGRldGVjdCBhIGNvZGUgZnJvbSB0aGlzIHNoYXBlIGFuZCBvZmZlciB0b1xuICAgICAgICAvLyBhdXRvZmlsbCBpdCwgYW5kIGFueXRoaW5nIG1vcmUgZGVjb3JhdGl2ZSBpcyB3aGF0IGJyZWFrcyB0aGF0LiBUaGUgY29kZVxuICAgICAgICAvLyBzdGF5cyB2aXNpYmxlIFRFWFQgaW4gYm90aCBNSU1FIHBhcnRzICh0aGUgZW1haWwgcGx1Z2luIHJlbmRlcnMgdGhlXG4gICAgICAgIC8vIEhUTUwgYW5kIHBsYWluLXRleHQgYWx0ZXJuYXRpdmVzIGZyb20gb25lIFJlYWN0IEVtYWlsIHRyZWUpLCBuZXZlciBhblxuICAgICAgICAvLyBpbWFnZSwgc28gYSBjbGllbnQgdGhhdCBzY3JhcGVzIHRoZSB0ZXh0IHBhcnQgc3RpbGwgZmluZHMgaXQuXG4gICAgICAgIGJvZHk6IFtcbiAgICAgICAgICBvcHRzLm1lc3NhZ2UsXG4gICAgICAgICAgXCJcIixcbiAgICAgICAgICBgIyMgJHtjb2RlfWAsXG4gICAgICAgICAgXCJcIixcbiAgICAgICAgICBgVGhpcyBjb2RlIGV4cGlyZXMgaW4gJHtleHBpcmVzSW4ob3B0cy5jb2RlVHRsU2Vjb25kcyl9LmAsXG4gICAgICAgICAgXCJcIixcbiAgICAgICAgICBcIklmIHlvdSBkaWQgbm90IHJlcXVlc3QgdGhpcyBjb2RlLCB5b3UgY2FuIGlnbm9yZSB0aGlzIGVtYWlsLlwiLFxuICAgICAgICBdLmpvaW4oXCJcXG5cIiksXG4gICAgICB9LFxuICAgICAgZnJvbSxcbiAgICApO1xuICB9O1xuXG4gIC8vIGBjcmVhdGVBcHBgIG5hbWVzcGFjZXMgZWFjaCBwbHVnaW4ncyBleHBvcnRzIG9uIHRoZSBoYW5kbGUgYnkgbWFuaWZlc3QgbmFtZTtcbiAgLy8gYGhhbmRsZS5hdXRoR2F0ZWAgaXMgdGhlIGluLXByb2Nlc3MgZ2F0ZSBBUEkgdGhlIHByb3h5IGRyaXZlcy5cbiAgY29uc3QgaGFuZGxlID0gYXdhaXQgY3JlYXRlQXBwKHtcbiAgICBwbHVnaW5zOiBbXG4gICAgICAvLyBCcmFuZCB0aGUgY29kZSBlbWFpbCBmcm9tIHRoZSByZXNvbHZlZCBjb250ZXh0LCB0aGUgc2FtZSBicmlkZ2UgZXZlcnlcbiAgICAgIC8vIG90aGVyIGRieC10b29scyBlbWFpbCBzdXJmYWNlIHVzZXMgKGFjY2VudCArIGZvbnQgaW5saW5lZCwgbG9nbyBvbmx5IHdoZW5cbiAgICAgIC8vIGl0IGlzIGEgZmV0Y2hhYmxlIFVSTCAtIGEgcGFja2FnZS1leHBvcnQgcGF0aCBjYW5ub3QgbG9hZCBpbiBhbiBpbmJveCkuXG4gICAgICBlbWFpbCh7IGJyYW5kOiBlbWFpbEJyYW5kRnJvbUNvbnRleHQoY29udGV4dCkgfSksXG4gICAgICAvLyBgYnJhbmROYW1lYCBmYWxscyBiYWNrIHRvIHRoZSByZXNvbHZlZCBjb250ZXh0J3MgZGlzcGxheSBuYW1lLCBzbyB0aGVcbiAgICAgIC8vIGVtYWlsIGNvcHkgbmFtZXMgdGhlIGFwcCByYXRoZXIgdGhhbiBhIGdlbmVyaWMgcGxhY2Vob2xkZXIuIFNwZWxsZWQgYXMgYW5cbiAgICAgIC8vIGV4cGxpY2l0IGA/P2AgcmF0aGVyIHRoYW4gYSBrZXkgYmVmb3JlIGAuLi5jb25maWdgOiBjb21tYW5kZXIgc2V0c1xuICAgICAgLy8gYGJyYW5kTmFtZWAgb24gdGhlIG9wdGlvbnMgb2JqZWN0IHdoZXRoZXIgb3Igbm90IHRoZSBmbGFnIHdhcyBwYXNzZWQsIHNvXG4gICAgICAvLyBzcHJlYWRpbmcgaXQgd291bGQgY2xvYmJlciB0aGUgY29udGV4dCBuYW1lIHdpdGggYHVuZGVmaW5lZGAuXG4gICAgICBhdXRoR2F0ZSh7IC4uLmNvbmZpZywgYnJhbmROYW1lOiBjb25maWcuYnJhbmROYW1lID8/IGNvbnRleHQubmFtZSwgc2VuZENvZGUgfSksXG4gICAgXSxcbiAgfSk7XG5cbiAgLy8gRmFpbCBmYXN0OiB0aGUgZ2F0ZSBuZWVkcyBTTVRQIHRvIGVtYWlsIGNvZGVzLiBgZ2V0RW1haWxSdW50aW1lKClgIHJlc29sdmVzIHRvXG4gIC8vIGBtb2RlOiBcImZpbGVcImAgKG91dGJveCkgb3IgdGhyb3dzIHdoZW4gbm8gU01UUCBjcmVkcyBhcmUgY29uZmlndXJlZCAtIG5laXRoZXJcbiAgLy8gY2FuIGRlbGl2ZXIgdG8gYSByZWFsIGluYm94LCBzbyByZWZ1c2UgdG8gYnJpbmcgdXAgYSBnYXRlIHRoYXQgbGV0cyBub2JvZHkgaW4uXG4gIGlmIChnZXRFbWFpbFJ1bnRpbWUoKS5jb25maWcubW9kZSAhPT0gXCJzbXRwXCIpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBcImVtYWlsIGlzIG5vdCBjb25maWd1cmVkIGZvciBTTVRQIGRlbGl2ZXJ5IC0gdGhlIE9UUCBnYXRlIGNhbm5vdCBzZW5kIGNvZGVzLiBcIiArXG4gICAgICAgIFwiU2V0IFNNVFBfSE9TVC9TTVRQX1VTRVIvU01UUF9QQVNTV09SRCwgb3IgcGFzcyAtLWluc2VjdXJlIC8gVFVOTkVMX0lOU0VDVVJFPXRydWUgdG8gcnVuIHRoZSB0dW5uZWwgb3Blbi5cIixcbiAgICApO1xuICB9XG5cbiAgbG9nZ2VyLmluZm8oXCJnYXRlIGFwcCByZWFkeSAobm8gc2VydmVyOyBwcm94eS1kcml2ZW4pXCIpO1xuICByZXR1cm4gaGFuZGxlLmF1dGhHYXRlIGFzIEF1dGhHYXRlQXBpO1xufVxuIl19
146
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBd0JHO0FBRUgsT0FBTyxFQUFFLFNBQVMsSUFBSSxXQUFXLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUM3RCxPQUFPLEVBQUUsS0FBSyxJQUFJLFNBQVMsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3JELE9BQU8sRUFBRSxLQUFLLElBQUksVUFBVSxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDakYsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUsUUFBUSxFQUErRCxNQUFNLGFBQWEsQ0FBQztBQUVwRyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBRXhDOzs7Ozs7R0FNRztBQUNILE1BQU0sVUFBVSxTQUFTLENBQUMsT0FBZTtJQUN2QyxPQUFPLE9BQU8sSUFBSSxFQUFFLElBQUksT0FBTyxHQUFHLEVBQUUsS0FBSyxDQUFDO1FBQ3hDLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLE9BQU8sR0FBRyxFQUFFLEVBQUUsUUFBUSxDQUFDO1FBQzFDLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLE9BQU8sRUFBRSxRQUFRLENBQUMsQ0FBQztBQUMxQyxDQUFDO0FBS0QsK0NBQStDO0FBQy9DLE1BQU0sV0FBVyxHQUFHLDhEQUE4RCxDQUFDO0FBRW5GOzs7R0FHRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxJQUFZLEVBQUUsSUFBYztJQUM1RCxPQUFPO1FBQ0wsSUFBSSxDQUFDLE9BQU87UUFDWixFQUFFO1FBQ0YsTUFBTSxJQUFJLEVBQUU7UUFDWixFQUFFO1FBQ0Ysd0JBQXdCLFNBQVMs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XG4gKiBUaGUgdHVubmVsIGdhdGUncyB0aW55IEFwcEtpdCBcImFwcFwiIC0gYGNyZWF0ZUFwcGAgV0lUSE9VVCBhIGBzZXJ2ZXIoKWAgcGx1Z2luLlxuICpcbiAqIFRoZXJlIGlzIG5vIEhUVFAgc2VydmVyIGhlcmU6IHRoZSB0dW5uZWwgcHJveHkgaXMgdGhlIHNlcnZlciwgYW5kIGl0IGNhbGxzIHRoZVxuICogZ2F0ZSBoYW5kbGVycyBpbi1wcm9jZXNzLiBgY3JlYXRlQXBwYCBpcyB1c2VkIG9ubHkgZm9yIHdoYXQgaXQgYXV0by13aXJlczpcbiAqICAgLSBgQ2FjaGVNYW5hZ2VyYCAobWVtb3J5LCBvciBMYWtlYmFzZSB3aGVuIHRoZSBkZXBsb3kgY29uZmlndXJlcyBwZXJzaXN0ZW50XG4gKiAgICAgY2FjaGUgc3RvcmFnZSkgLSB3aGljaCB0aGUgT1RQIGBDb2RlU3RvcmVgIHVzZXMgZm9yIHN0b3JhZ2UgKyBUVEwgZXZpY3Rpb247XG4gKiAgIC0gdGhlIGBlbWFpbCgpYCB0cmFuc3BvcnQgKFNNVFAgLyBvdXRib3gpLCBwcmltZWQgZnJvbSBlbnYsIHNvIGBzZW5kQ29kZWAgY2FuXG4gKiAgICAgZGVsaXZlciB0aGUgb25lLXRpbWUgY29kZTtcbiAqICAgLSBkYngtdG9vbHMgYXV0by1jb25maWd1cmF0aW9uICsgYnJhbmRpbmcgdmlhIGBAZGJ4LXRvb2xzL2FwcGtpdGAuXG4gKlxuICogSXQgcmV0dXJucyB0aGUge0BsaW5rIEF1dGhHYXRlQXBpfSB0aGUgcHJveHkgZHJpdmVzLiBgc2VuZENvZGVgIGlzIHdpcmVkIGhlcmVcbiAqICh0aGUgcGx1Z2luIGNhbid0IHJlc29sdmUgSE9XIHRvIHNlbmQgb24gaXRzIG93bikgdG8gdGhlIGVtYWlsIHBsdWdpbidzXG4gKiB0cmFuc3BvcnQuIEEgc2lnbi1pbiBjb2RlIGlzIFNZU1RFTSBtYWlsIC0gbm8gb24tYmVoYWxmLW9mIHVzZXIgYXNrZWQgZm9yIGl0XG4gKiBhbmQgbm8gcmVwbHkgdG8gaXQgcmVhY2hlcyBhbnlvbmUgLSBzbyBpdCBzZW5kcyBmcm9tIHRoZSBlbWFpbCBjb25maWcnc1xuICogZG8tbm90LXJlcGx5IGFkZHJlc3MgKGBuby1yZXBseUBFTUFJTF9ET01BSU5gIHVubGVzcyBFTUFJTF9TWVNURU1fRlJPTSBuYW1lc1xuICogYW5vdGhlcikuXG4gKlxuICogRkFJTCBGQVNUOiBhIGdhdGUgdGhhdCBjYW4ndCBlbWFpbCBhIGNvZGUgaXMgdXNlbGVzcywgc28gaWYgZW1haWwgZG9lcyBub3RcbiAqIHJlc29sdmUgdG8gU01UUCBtb2RlIChyZWFsIGRlbGl2ZXJ5KSwgdGhpcyB0aHJvd3MgLSB1bmxlc3MgYGluc2VjdXJlYCBpcyBzZXRcbiAqIChgLS1pbnNlY3VyZWAgLyBgVFVOTkVMX0lOU0VDVVJFPXRydWVgKSwgaW4gd2hpY2ggY2FzZSB0aGUgY2FsbGVyIHJ1bnMgdGhlXG4gKiB0dW5uZWwgT1BFTiB3aXRoIG5vIGdhdGUuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IGNyZWF0ZUFwcCBhcyBjcmVhdGVBcHBOcyB9IGZyb20gXCJAZGJ4LXRvb2xzL2FwcGtpdFwiO1xuaW1wb3J0IHsgYnJhbmQgYXMgbm9kZUJyYW5kIH0gZnJvbSBcIkBkYngtdG9vbHMvY29yZVwiO1xuaW1wb3J0IHsgYnJhbmQgYXMgZW1haWxCcmFuZCwgZW1haWwsIHNlbmRlciwgdHJhbnNwb3J0IH0gZnJvbSBcIkBkYngtdG9vbHMvZW1haWxcIjtcbmltcG9ydCB7IGxvZywgc3RyaW5nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IGF1dGhHYXRlLCB0eXBlIEF1dGhHYXRlQXBpLCB0eXBlIEF1dGhHYXRlQ29uZmlnLCB0eXBlIFNlbmRDb2RlT3B0aW9ucyB9IGZyb20gXCIuL3BsdWdpbi50c1wiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwidHVubmVsOmFwcFwiKTtcblxuLyoqXG4gKiBBIGNvZGUgVFRMIGFzIHRoZSBwbGFpbiBwaHJhc2UgdGhlIGVtYWlsIHN0YXRlcyAoXCIxMCBtaW51dGVzXCIsIFwiNDUgc2Vjb25kc1wiKS5cbiAqXG4gKiBXaG9sZSBtaW51dGVzIHJlYWQgYXMgbWludXRlczsgYW55dGhpbmcgZWxzZSBzdGF5cyBpbiBzZWNvbmRzIHJhdGhlciB0aGFuXG4gKiByb3VuZGluZywgc28gYSA5MC1zZWNvbmQgVFRMIGlzIG5vdCBhZHZlcnRpc2VkIGFzIFwiMSBtaW51dGVcIiBhbmQgYSByZWNpcGllbnQgaXNcbiAqIG5ldmVyIHRvbGQgdGhlIGNvZGUgbGl2ZXMgbG9uZ2VyIHRoYW4gaXQgZG9lcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGV4cGlyZXNJbihzZWNvbmRzOiBudW1iZXIpOiBzdHJpbmcge1xuICByZXR1cm4gc2Vjb25kcyA+PSA2MCAmJiBzZWNvbmRzICUgNjAgPT09IDBcbiAgICA/IHN0cmluZy5wbHVyYWxpemUoc2Vjb25kcyAvIDYwLCBcIm1pbnV0ZVwiKVxuICAgIDogc3RyaW5nLnBsdXJhbGl6ZShzZWNvbmRzLCBcInNlY29uZFwiKTtcbn1cblxuLyoqIFRoZSBwYXJ0cyBvZiB7QGxpbmsgU2VuZENvZGVPcHRpb25zfSB0aGUgY29kZSBlbWFpbCdzIGNvcHkgaXMgYnVpbHQgZnJvbS4gKi9cbnR5cGUgQ29kZUNvcHkgPSBQaWNrPFNlbmRDb2RlT3B0aW9ucywgXCJtZXNzYWdlXCIgfCBcImNvZGVUdGxTZWNvbmRzXCI+O1xuXG4vKiogVGhlIHJlYXNzdXJhbmNlIGxpbmUgY2xvc2luZyBib3RoIHBhcnRzLiAqL1xuY29uc3QgSUdOT1JFX0xJTkUgPSBcIklmIHlvdSBkaWQgbm90IHJlcXVlc3QgdGhpcyBjb2RlLCB5b3UgY2FuIGlnbm9yZSB0aGlzIGVtYWlsLlwiO1xuXG4vKipcbiAqIFRoZSBIVE1MIHBhcnQncyBzb3VyY2U6IHRoZSBmdWxsIGJyYW5kZWQgdGVtcGxhdGUsIHdpdGggdGhlIGNvZGUgYXMgYSBsYXJnZVxuICogc3R5bGVkIGhlYWRpbmcgKGAjIyBgIGlzIHdoYXQgbWFrZXMgaXQgcHJvbWluZW50IGluIGFuIGluYm94KS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNvZGVFbWFpbEh0bWxCb2R5KGNvZGU6IHN0cmluZywgb3B0czogQ29kZUNvcHkpOiBzdHJpbmcge1xuICByZXR1cm4gW1xuICAgIG9wdHMubWVzc2FnZSxcbiAgICBcIlwiLFxuICAgIGAjIyAke2NvZGV9YCxcbiAgICBcIlwiLFxuICAgIGBUaGlzIGNvZGUgZXhwaXJlcyBpbiAke2V4cGlyZXNJbihvcHRzLmNvZGVUdGxTZWNvbmRzKX0uYCxcbiAgICBcIlwiLFxuICAgIElHTk9SRV9MSU5FLFxuICBdLmpvaW4oXCJcXG5cIik7XG59XG5cbi8qKlxuICogVGhlIGB0ZXh0L3BsYWluYCBwYXJ0LCBzdXBwbGllZCBFWFBMSUNJVExZIHJhdGhlciB0aGFuIHJlbmRlcmVkIGZyb20gdGhlIHRyZWVcbiAqIGFib3ZlLiBCb3RoIHBhcnRzIHNheSB0aGUgc2FtZSB0aGluZzsgb25seSB0aGUgbGluZSBsYXlvdXQgZGlmZmVycy5cbiAqXG4gKiBUaGUgcHJvbXB0IGFuZCB0aGUgY29kZSBzaGFyZSBPTkUgbGluZSAoXCJZb3VyIHZlcmlmaWNhdGlvbiBjb2RlIGlzOiAxMjM0NTZcIikuXG4gKiBUaGF0IHNpbmdsZS1saW5lIHNoYXBlIGlzIHdoYXQgaU9TLCBHbWFpbCwgT3V0bG9vaywgYW5kIEFuZHJvaWQgY29kZSBkZXRlY3Rpb25cbiAqIGtleXMgb24gbW9zdCByZWxpYWJseSAtIHRoZSBoZXVyaXN0aWNzIGxvb2sgZm9yIGEgY29kZSBpbiB0aGUgc2FtZSBzZW50ZW5jZSBhc1xuICogYSByZWNvZ25pemVkIHByb21wdCwgc28gc3BsaXR0aW5nIHRoZW0gYWNyb3NzIGxpbmVzIG1ha2VzIGRldGVjdGlvbiBkZXBlbmRlbnRcbiAqIG9uIHRoZSBjbGllbnQsIGFuZCBhbnkgYmxhbmsgbGluZSBiZXR3ZWVuIHRoZW0gZGVmZWF0cyBpdCBvdXRyaWdodC5cbiAqXG4gKiBUaGUgR0VORVJBVEVEIHRleHQgcGFydCBjYW5ub3QgaG9sZCB0aGF0IHNoYXBlIGF0IGFsbDogaXQgaXMgYSByZW5kZXJpbmcgb2YgdGhlXG4gKiBIVE1MLCBzbyBpdCBjYXJyaWVzIHRoZSBicmFuZCBoZWFkZXIvZm9vdGVyIGFuZCB0dXJucyB0aGUgY29kZSBoZWFkaW5nJ3MgQ1NTXG4gKiBtYXJnaW4gaW50byBibGFuayBsaW5lcywgYXJyaXZpbmcgYXMgYHByb21wdFxcblxcblxcbmNvZGVgLlxuICpcbiAqIFRoZSBjb2RlIGlzIHZpc2libGUgdGV4dCBpbiBCT1RIIHBhcnRzLCBuZXZlciBhbiBpbWFnZSwgc28gYSBjbGllbnQgc2NyYXBpbmdcbiAqIGVpdGhlciBvbmUgZmluZHMgaXQuIE5vIHRyYWlsZXIgbGluZSBmb2xsb3dzIHRoZSBjb3B5IC0gQXBwbGUncyBkb21haW4tYm91bmRcbiAqIGBAZG9tYWluICNjb2RlYCBmb290ZXIgaXMgZGVsaWJlcmF0ZWx5IE5PVCBlbWl0dGVkLCBzaW5jZSBpdCBjb25zdHJhaW5zIHRoZVxuICogY29kZSB0byBvbmUgb3JpZ2luIGFuZCBpcyBub3Qgd2hhdCB0aGUgYnJvYWRseS1jb21wYXRpYmxlIHNoYXBlIG5lZWRzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY29kZUVtYWlsVGV4dEJvZHkoY29kZTogc3RyaW5nLCBvcHRzOiBDb2RlQ29weSk6IHN0cmluZyB7XG4gIHJldHVybiBbXG4gICAgYCR7b3B0cy5tZXNzYWdlfSAke2NvZGV9YCxcbiAgICBgVGhpcyBjb2RlIGV4cGlyZXMgaW4gJHtleHBpcmVzSW4ob3B0cy5jb2RlVHRsU2Vjb25kcyl9LmAsXG4gICAgXCJcIixcbiAgICBJR05PUkVfTElORSxcbiAgXS5qb2luKFwiXFxuXCIpO1xufVxuXG5jb25zdCB7IGNyZWF0ZUFwcCB9ID0gY3JlYXRlQXBwTnM7XG5jb25zdCB7IHJlc29sdmVTeXN0ZW1TZW5kZXJBZGRyZXNzIH0gPSBzZW5kZXI7XG5jb25zdCB7IGdldEVtYWlsUnVudGltZSwgc2VuZEVtYWlsIH0gPSB0cmFuc3BvcnQ7XG5jb25zdCB7IGVtYWlsQnJhbmRGcm9tQ29udGV4dCB9ID0gZW1haWxCcmFuZDtcbmNvbnN0IHsgbG9hZEJyYW5kQ29udGV4dCB9ID0gbm9kZUJyYW5kO1xuXG4vKipcbiAqIEJvb3QgdGhlIGdhdGUgYXBwIGFuZCByZXR1cm4gdGhlIEFQSSB0aGUgcHJveHkgY2FsbHMuIFRocm93cyB3aGVuIGVtYWlsIGlzIG5vdFxuICogaW4gU01UUCBtb2RlIChubyB3YXkgdG8gZGVsaXZlciBhIGNvZGUpIHNvIGEgbWlzY29uZmlndXJlZCBnYXRlIGZhaWxzIGZhc3QgYXRcbiAqIHN0YXJ0dXAgcmF0aGVyIHRoYW4gc2lsZW50bHkgYWNjZXB0aW5nIG5vYm9keTsgdGhlIGNhbGxlciBtYXkgY2F0Y2ggdGhpcyBhbmRcbiAqIGZhbGwgYmFjayB0byBpbnNlY3VyZS9vcGVuIG1vZGUgd2hlbiB0aGUgb3BlcmF0b3Igb3B0ZWQgaW4uXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBzdGFydEdhdGVBcHAoY29uZmlnOiBBdXRoR2F0ZUNvbmZpZyk6IFByb21pc2U8QXV0aEdhdGVBcGk+IHtcbiAgLy8gVGhlIGhvc3QgYXBwJ3Mgb3duIGJyYW5kIChgYnJhbmRpbmcvYnJhbmQueWFtbGAgZGlzY292ZXJlZCBmcm9tIGN3ZCkgb3IgdGhlXG4gIC8vIGRieC10b29scyBkZWZhdWx0LiBUaGlzIGlzIHRoZSBPTkUgYnJhbmQgc291cmNlIGZvciB0aGUgZ2F0ZTogaXQgc3R5bGVzIHRoZVxuICAvLyBjb2RlIGVtYWlsIChhY2NlbnQgYmFuZCwgZm9udCwgbG9nbykgdmlhIHRoZSBlbWFpbCBwbHVnaW4gQU5EIHN1cHBsaWVzIHRoZVxuICAvLyBkaXNwbGF5IG5hbWUgdGhlIGNvcHkgdXNlcywgc28gYSBkZXBsb3ltZW50IHRoYXQgdGhlbWVzIGl0cyBhcHAgdGhlbWVzIGl0c1xuICAvLyBzaWduLWluIGVtYWlsIHdpdGggaXQuIEFuIGV4cGxpY2l0IGBicmFuZE5hbWVgIHN0aWxsIHdpbnMgKHNlZSBiZWxvdykuXG4gIGNvbnN0IGNvbnRleHQgPSBhd2FpdCBsb2FkQnJhbmRDb250ZXh0KCk7XG5cbiAgLy8gYHNlbmRDb2RlYCBkZWxpdmVycyB0aGUgT1RQIHRocm91Z2ggdGhlIGVtYWlsIHBsdWdpbidzIFNIQVJFRCB0cmFuc3BvcnQsIHdoaWNoXG4gIC8vIHRoZSBgZW1haWwoKWAgcGx1Z2luIHByaW1lcyBkdXJpbmcgaXRzIGBzZXR1cCgpYCAoYXdhaXRlZCBieSBgY3JlYXRlQXBwYFxuICAvLyBiZWxvdykuIFVzaW5nIHRoZSBtb2R1bGUtbGV2ZWwgYHNlbmRFbWFpbGAgYXZvaWRzIGEgY2lyY3VsYXIgZGVwZW5kZW5jeSBvbiB0aGVcbiAgLy8gYXBwIGhhbmRsZSdzIGluZmVycmVkIHR5cGUuIFRoZSBgRnJvbWAgaXMgdGhlIFNZU1RFTSBzZW5kZXI6IGEgdmVyaWZpY2F0aW9uXG4gIC8vIGNvZGUgaXMgbWFjaGluZS1nZW5lcmF0ZWQgYW5kIHVuYW5zd2VyYWJsZSwgc28gaXQgbXVzdCBub3QgYXJyaXZlIGZyb20gYVxuICAvLyBwZXJzb24ncyBhZGRyZXNzIGludml0aW5nIGEgcmVwbHkuXG4gIGNvbnN0IHNlbmRDb2RlOiBOb25OdWxsYWJsZTxBdXRoR2F0ZUNvbmZpZ1tcInNlbmRDb2RlXCJdPiA9IGFzeW5jICh0bywgY29kZSwgb3B0cykgPT4ge1xuICAgIGNvbnN0IGZyb20gPSByZXNvbHZlU3lzdGVtU2VuZGVyQWRkcmVzcyhnZXRFbWFpbFJ1bnRpbWUoKS5jb25maWcpO1xuICAgIGF3YWl0IHNlbmRFbWFpbChcbiAgICAgIHtcbiAgICAgICAgdG86IFt0b10sXG4gICAgICAgIHN1YmplY3Q6IG9wdHMuc3ViamVjdCxcbiAgICAgICAgYm9keTogY29kZUVtYWlsSHRtbEJvZHkoY29kZSwgb3B0cyksXG4gICAgICB9LFxuICAgICAgZnJvbSxcbiAgICAgIHVuZGVmaW5lZCxcbiAgICAgIHsgdGV4dDogY29kZUVtYWlsVGV4dEJvZHkoY29kZSwgb3B0cykgfSxcbiAgICApO1xuICB9O1xuXG4gIC8vIGBjcmVhdGVBcHBgIG5hbWVzcGFjZXMgZWFjaCBwbHVnaW4ncyBleHBvcnRzIG9uIHRoZSBoYW5kbGUgYnkgbWFuaWZlc3QgbmFtZTtcbiAgLy8gYGhhbmRsZS5hdXRoR2F0ZWAgaXMgdGhlIGluLXByb2Nlc3MgZ2F0ZSBBUEkgdGhlIHByb3h5IGRyaXZlcy5cbiAgY29uc3QgaGFuZGxlID0gYXdhaXQgY3JlYXRlQXBwKHtcbiAgICBwbHVnaW5zOiBbXG4gICAgICAvLyBCcmFuZCB0aGUgY29kZSBlbWFpbCBmcm9tIHRoZSByZXNvbHZlZCBjb250ZXh0LCB0aGUgc2FtZSBicmlkZ2UgZXZlcnlcbiAgICAgIC8vIG90aGVyIGRieC10b29scyBlbWFpbCBzdXJmYWNlIHVzZXMgKGFjY2VudCArIGZvbnQgaW5saW5lZCwgbG9nbyBvbmx5IHdoZW5cbiAgICAgIC8vIGl0IGlzIGEgZmV0Y2hhYmxlIFVSTCAtIGEgcGFja2FnZS1leHBvcnQgcGF0aCBjYW5ub3QgbG9hZCBpbiBhbiBpbmJveCkuXG4gICAgICBlbWFpbCh7IGJyYW5kOiBlbWFpbEJyYW5kRnJvbUNvbnRleHQoY29udGV4dCkgfSksXG4gICAgICAvLyBgYnJhbmROYW1lYCBmYWxscyBiYWNrIHRvIHRoZSByZXNvbHZlZCBjb250ZXh0J3MgZGlzcGxheSBuYW1lLCBzbyB0aGVcbiAgICAgIC8vIGVtYWlsIGNvcHkgbmFtZXMgdGhlIGFwcCByYXRoZXIgdGhhbiBhIGdlbmVyaWMgcGxhY2Vob2xkZXIuIFNwZWxsZWQgYXMgYW5cbiAgICAgIC8vIGV4cGxpY2l0IGA/P2AgcmF0aGVyIHRoYW4gYSBrZXkgYmVmb3JlIGAuLi5jb25maWdgOiBjb21tYW5kZXIgc2V0c1xuICAgICAgLy8gYGJyYW5kTmFtZWAgb24gdGhlIG9wdGlvbnMgb2JqZWN0IHdoZXRoZXIgb3Igbm90IHRoZSBmbGFnIHdhcyBwYXNzZWQsIHNvXG4gICAgICAvLyBzcHJlYWRpbmcgaXQgd291bGQgY2xvYmJlciB0aGUgY29udGV4dCBuYW1lIHdpdGggYHVuZGVmaW5lZGAuXG4gICAgICBhdXRoR2F0ZSh7IC4uLmNvbmZpZywgYnJhbmROYW1lOiBjb25maWcuYnJhbmROYW1lID8/IGNvbnRleHQubmFtZSwgc2VuZENvZGUgfSksXG4gICAgXSxcbiAgfSk7XG5cbiAgLy8gRmFpbCBmYXN0OiB0aGUgZ2F0ZSBuZWVkcyBTTVRQIHRvIGVtYWlsIGNvZGVzLiBgZ2V0RW1haWxSdW50aW1lKClgIHJlc29sdmVzIHRvXG4gIC8vIGBtb2RlOiBcImZpbGVcImAgKG91dGJveCkgb3IgdGhyb3dzIHdoZW4gbm8gU01UUCBjcmVkcyBhcmUgY29uZmlndXJlZCAtIG5laXRoZXJcbiAgLy8gY2FuIGRlbGl2ZXIgdG8gYSByZWFsIGluYm94LCBzbyByZWZ1c2UgdG8gYnJpbmcgdXAgYSBnYXRlIHRoYXQgbGV0cyBub2JvZHkgaW4uXG4gIGlmIChnZXRFbWFpbFJ1bnRpbWUoKS5jb25maWcubW9kZSAhPT0gXCJzbXRwXCIpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBcImVtYWlsIGlzIG5vdCBjb25maWd1cmVkIGZvciBTTVRQIGRlbGl2ZXJ5IC0gdGhlIE9UUCBnYXRlIGNhbm5vdCBzZW5kIGNvZGVzLiBcIiArXG4gICAgICAgIFwiU2V0IFNNVFBfSE9TVC9TTVRQX1VTRVIvU01UUF9QQVNTV09SRCwgb3IgcGFzcyAtLWluc2VjdXJlIC8gVFVOTkVMX0lOU0VDVVJFPXRydWUgdG8gcnVuIHRoZSB0dW5uZWwgb3Blbi5cIixcbiAgICApO1xuICB9XG5cbiAgbG9nZ2VyLmluZm8oXCJnYXRlIGFwcCByZWFkeSAobm8gc2VydmVyOyBwcm94eS1kcml2ZW4pXCIpO1xuICByZXR1cm4gaGFuZGxlLmF1dGhHYXRlIGFzIEF1dGhHYXRlQXBpO1xufVxuIl19
package/package.json CHANGED
@@ -29,11 +29,11 @@
29
29
  "dependencies": {
30
30
  "@clack/prompts": "^1.7.0",
31
31
  "@databricks/appkit": "^0.43.0",
32
- "@dbx-tools/appkit": "0.6.54",
33
- "@dbx-tools/core": "0.6.54",
34
- "@dbx-tools/email": "0.6.54",
35
- "@dbx-tools/shared-core": "0.6.54",
36
- "@dbx-tools/shared-email": "0.6.54",
32
+ "@dbx-tools/appkit": "0.6.55",
33
+ "@dbx-tools/core": "0.6.55",
34
+ "@dbx-tools/email": "0.6.55",
35
+ "@dbx-tools/shared-core": "0.6.55",
36
+ "@dbx-tools/shared-email": "0.6.55",
37
37
  "commander": "^15.0.0",
38
38
  "http-proxy-3": "^1.23.3",
39
39
  "jose": "^6.2.3"
@@ -100,7 +100,7 @@
100
100
  "./package.json": "./package.json"
101
101
  }
102
102
  },
103
- "version": "0.6.54",
103
+ "version": "0.6.55",
104
104
  "types": "./lib/index.d.ts",
105
105
  "type": "module",
106
106
  "exports": {
package/src/app.ts CHANGED
@@ -11,8 +11,10 @@
11
11
  *
12
12
  * It returns the {@link AuthGateApi} the proxy drives. `sendCode` is wired here
13
13
  * (the plugin can't resolve HOW to send on its own) to the email plugin's
14
- * transport, resolving the `From` from the email runtime config (EMAIL_FROM /
15
- * EMAIL_DOMAIN) since an OTP request has no on-behalf-of user.
14
+ * transport. A sign-in code is SYSTEM mail - no on-behalf-of user asked for it
15
+ * and no reply to it reaches anyone - so it sends from the email config's
16
+ * do-not-reply address (`no-reply@EMAIL_DOMAIN` unless EMAIL_SYSTEM_FROM names
17
+ * another).
16
18
  *
17
19
  * FAIL FAST: a gate that can't email a code is useless, so if email does not
18
20
  * resolve to SMTP mode (real delivery), this throws - unless `insecure` is set
@@ -26,7 +28,7 @@ import { createApp as createAppNs } from "@dbx-tools/appkit";
26
28
  import { brand as nodeBrand } from "@dbx-tools/core";
27
29
  import { brand as emailBrand, email, sender, transport } from "@dbx-tools/email";
28
30
  import { log, string } from "@dbx-tools/shared-core";
29
- import { authGate, type AuthGateApi, type AuthGateConfig } from "./plugin.ts";
31
+ import { authGate, type AuthGateApi, type AuthGateConfig, type SendCodeOptions } from "./plugin.ts";
30
32
 
31
33
  const logger = log.logger("tunnel:app");
32
34
 
@@ -43,8 +45,58 @@ export function expiresIn(seconds: number): string {
43
45
  : string.pluralize(seconds, "second");
44
46
  }
45
47
 
48
+ /** The parts of {@link SendCodeOptions} the code email's copy is built from. */
49
+ type CodeCopy = Pick<SendCodeOptions, "message" | "codeTtlSeconds">;
50
+
51
+ /** The reassurance line closing both parts. */
52
+ const IGNORE_LINE = "If you did not request this code, you can ignore this email.";
53
+
54
+ /**
55
+ * The HTML part's source: the full branded template, with the code as a large
56
+ * styled heading (`## ` is what makes it prominent in an inbox).
57
+ */
58
+ export function codeEmailHtmlBody(code: string, opts: CodeCopy): string {
59
+ return [
60
+ opts.message,
61
+ "",
62
+ `## ${code}`,
63
+ "",
64
+ `This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
65
+ "",
66
+ IGNORE_LINE,
67
+ ].join("\n");
68
+ }
69
+
70
+ /**
71
+ * The `text/plain` part, supplied EXPLICITLY rather than rendered from the tree
72
+ * above. Both parts say the same thing; only the line layout differs.
73
+ *
74
+ * The prompt and the code share ONE line ("Your verification code is: 123456").
75
+ * That single-line shape is what iOS, Gmail, Outlook, and Android code detection
76
+ * keys on most reliably - the heuristics look for a code in the same sentence as
77
+ * a recognized prompt, so splitting them across lines makes detection dependent
78
+ * on the client, and any blank line between them defeats it outright.
79
+ *
80
+ * The GENERATED text part cannot hold that shape at all: it is a rendering of the
81
+ * HTML, so it carries the brand header/footer and turns the code heading's CSS
82
+ * margin into blank lines, arriving as `prompt\n\n\ncode`.
83
+ *
84
+ * The code is visible text in BOTH parts, never an image, so a client scraping
85
+ * either one finds it. No trailer line follows the copy - Apple's domain-bound
86
+ * `@domain #code` footer is deliberately NOT emitted, since it constrains the
87
+ * code to one origin and is not what the broadly-compatible shape needs.
88
+ */
89
+ export function codeEmailTextBody(code: string, opts: CodeCopy): string {
90
+ return [
91
+ `${opts.message} ${code}`,
92
+ `This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
93
+ "",
94
+ IGNORE_LINE,
95
+ ].join("\n");
96
+ }
97
+
46
98
  const { createApp } = createAppNs;
47
- const { resolveSenderAddress } = sender;
99
+ const { resolveSystemSenderAddress } = sender;
48
100
  const { getEmailRuntime, sendEmail } = transport;
49
101
  const { emailBrandFromContext } = emailBrand;
50
102
  const { loadBrandContext } = nodeBrand;
@@ -66,32 +118,20 @@ export async function startGateApp(config: AuthGateConfig): Promise<AuthGateApi>
66
118
  // `sendCode` delivers the OTP through the email plugin's SHARED transport, which
67
119
  // the `email()` plugin primes during its `setup()` (awaited by `createApp`
68
120
  // below). Using the module-level `sendEmail` avoids a circular dependency on the
69
- // app handle's inferred type. `From` is the app's configured sender (EMAIL_FROM /
70
- // EMAIL_DOMAIN); the code email has no OBO user, so it resolves with `undefined`.
121
+ // app handle's inferred type. The `From` is the SYSTEM sender: a verification
122
+ // code is machine-generated and unanswerable, so it must not arrive from a
123
+ // person's address inviting a reply.
71
124
  const sendCode: NonNullable<AuthGateConfig["sendCode"]> = async (to, code, opts) => {
72
- const from = resolveSenderAddress(getEmailRuntime().config, undefined);
125
+ const from = resolveSystemSenderAddress(getEmailRuntime().config);
73
126
  await sendEmail(
74
127
  {
75
128
  to: [to],
76
129
  subject: opts.subject,
77
- // Deliberately the conventional one-time-code layout: the prompt line,
78
- // then the bare code ALONE on the next line, then the expiry. iOS, Gmail,
79
- // Outlook, and Android all detect a code from this shape and offer to
80
- // autofill it, and anything more decorative is what breaks that. The code
81
- // stays visible TEXT in both MIME parts (the email plugin renders the
82
- // HTML and plain-text alternatives from one React Email tree), never an
83
- // image, so a client that scrapes the text part still finds it.
84
- body: [
85
- opts.message,
86
- "",
87
- `## ${code}`,
88
- "",
89
- `This code expires in ${expiresIn(opts.codeTtlSeconds)}.`,
90
- "",
91
- "If you did not request this code, you can ignore this email.",
92
- ].join("\n"),
130
+ body: codeEmailHtmlBody(code, opts),
93
131
  },
94
132
  from,
133
+ undefined,
134
+ { text: codeEmailTextBody(code, opts) },
95
135
  );
96
136
  };
97
137