@dbx-tools/cli-tunnel 0.6.45

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 ADDED
@@ -0,0 +1 @@
1
+ # replace this
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `dbx-tools-tunnel` / `dbxt-tunnel` entry: front a Databricks App with a public
4
+ * portr tunnel + email-OTP gate, wrapping the start command after `--`.
5
+ * Delegates to the commander program in `../src/cli`.
6
+ */
7
+ import { CommanderError, runCli } from "../src/cli.ts";
8
+
9
+ runCli(process.argv).catch((err: unknown) => {
10
+ if (err instanceof CommanderError) process.exit(err.exitCode);
11
+ process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
12
+ process.exit(1);
13
+ });
package/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+
5
+ export * as allowlist from "./src/allowlist.ts";
6
+ export * as app from "./src/app.ts";
7
+ export * as cli from "./src/cli.ts";
8
+ export * as otp from "./src/otp.ts";
9
+ export * as plugin from "./src/plugin.ts";
10
+ export * as portr from "./src/portr.ts";
11
+ export * as proxy from "./src/proxy.ts";
12
+ export * as rateLimit from "./src/rate-limit.ts";
13
+ export { CommanderError } from "./src/cli.ts";
14
+ export { CodeStore } from "./src/otp.ts";
15
+ export type { VerifyOutcome } from "./src/otp.ts";
16
+ export { AuthGatePlugin, authGate } from "./src/plugin.ts";
17
+ export type { AuthGateConfig, SendCodeOptions, ResolvedAuthGateConfig, AuthGateApi } from "./src/plugin.ts";
18
+ export type { PortrConfig } from "./src/portr.ts";
19
+ export type { ProxyOptions } from "./src/proxy.ts";
20
+ export { RateLimiter } from "./src/rate-limit.ts";
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `dbx-tools-tunnel` / `dbxt-tunnel` entry: front a Databricks App with a public
4
+ * portr tunnel + email-OTP gate, wrapping the start command after `--`.
5
+ * Delegates to the commander program in `../src/cli`.
6
+ */
7
+ import { CommanderError, runCli } from "../src/cli.js";
8
+ runCli(process.argv).catch((err) => {
9
+ if (err instanceof CommanderError)
10
+ process.exit(err.exitCode);
11
+ process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
12
+ process.exit(1);
13
+ });
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGJ4LXRvb2xzLXR1bm5lbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL2Jpbi9kYngtdG9vbHMtdHVubmVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFDQTs7OztHQUlHO0FBQ0gsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFdkQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFZLEVBQUUsRUFBRTtJQUMxQyxJQUFJLEdBQUcsWUFBWSxjQUFjO1FBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDOUQsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsR0FBRyxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxLQUFLLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzdGLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDbEIsQ0FBQyxDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIjIS91c3IvYmluL2VudiBub2RlXG4vKipcbiAqIGBkYngtdG9vbHMtdHVubmVsYCAvIGBkYnh0LXR1bm5lbGAgZW50cnk6IGZyb250IGEgRGF0YWJyaWNrcyBBcHAgd2l0aCBhIHB1YmxpY1xuICogcG9ydHIgdHVubmVsICsgZW1haWwtT1RQIGdhdGUsIHdyYXBwaW5nIHRoZSBzdGFydCBjb21tYW5kIGFmdGVyIGAtLWAuXG4gKiBEZWxlZ2F0ZXMgdG8gdGhlIGNvbW1hbmRlciBwcm9ncmFtIGluIGAuLi9zcmMvY2xpYC5cbiAqL1xuaW1wb3J0IHsgQ29tbWFuZGVyRXJyb3IsIHJ1bkNsaSB9IGZyb20gXCIuLi9zcmMvY2xpLnRzXCI7XG5cbnJ1bkNsaShwcm9jZXNzLmFyZ3YpLmNhdGNoKChlcnI6IHVua25vd24pID0+IHtcbiAgaWYgKGVyciBpbnN0YW5jZW9mIENvbW1hbmRlckVycm9yKSBwcm9jZXNzLmV4aXQoZXJyLmV4aXRDb2RlKTtcbiAgcHJvY2Vzcy5zdGRlcnIud3JpdGUoYCR7ZXJyIGluc3RhbmNlb2YgRXJyb3IgPyAoZXJyLnN0YWNrID8/IGVyci5tZXNzYWdlKSA6IFN0cmluZyhlcnIpfVxcbmApO1xuICBwcm9jZXNzLmV4aXQoMSk7XG59KTtcbiJdfQ==
package/lib/index.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export * as allowlist from "./src/allowlist.ts";
2
+ export * as app from "./src/app.ts";
3
+ export * as cli from "./src/cli.ts";
4
+ export * as otp from "./src/otp.ts";
5
+ export * as plugin from "./src/plugin.ts";
6
+ export * as portr from "./src/portr.ts";
7
+ export * as proxy from "./src/proxy.ts";
8
+ export * as rateLimit from "./src/rate-limit.ts";
9
+ export { CommanderError } from "./src/cli.ts";
10
+ export { CodeStore } from "./src/otp.ts";
11
+ export type { VerifyOutcome } from "./src/otp.ts";
12
+ export { AuthGatePlugin, authGate } from "./src/plugin.ts";
13
+ export type { AuthGateConfig, SendCodeOptions, ResolvedAuthGateConfig, AuthGateApi } from "./src/plugin.ts";
14
+ export type { PortrConfig } from "./src/portr.ts";
15
+ export type { ProxyOptions } from "./src/proxy.ts";
16
+ export { RateLimiter } from "./src/rate-limit.ts";
package/lib/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // GENERATED by projen watch - DO NOT EDIT.
2
+ // Regenerated from the exporting modules in ./src.
3
+ // Hand edits are overwritten on the next watch; this file is read-only.
4
+ export * as allowlist from "./src/allowlist.js";
5
+ export * as app from "./src/app.js";
6
+ export * as cli from "./src/cli.js";
7
+ export * as otp from "./src/otp.js";
8
+ export * as plugin from "./src/plugin.js";
9
+ export * as portr from "./src/portr.js";
10
+ export * as proxy from "./src/proxy.js";
11
+ export * as rateLimit from "./src/rate-limit.js";
12
+ export { CommanderError } from "./src/cli.js";
13
+ export { CodeStore } from "./src/otp.js";
14
+ export { AuthGatePlugin, authGate } from "./src/plugin.js";
15
+ export { RateLimiter } from "./src/rate-limit.js";
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssU0FBUyxNQUFNLG9CQUFvQixDQUFDO0FBQ2hELE9BQU8sS0FBSyxHQUFHLE1BQU0sY0FBYyxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxHQUFHLE1BQU0sY0FBYyxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxHQUFHLE1BQU0sY0FBYyxDQUFDO0FBQ3BDLE9BQU8sS0FBSyxNQUFNLE1BQU0saUJBQWlCLENBQUM7QUFDMUMsT0FBTyxLQUFLLEtBQUssTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QyxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxTQUFTLE1BQU0scUJBQXFCLENBQUM7QUFDakQsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUM5QyxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBRXpDLE9BQU8sRUFBRSxjQUFjLEVBQUUsUUFBUSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFJM0QsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLHFCQUFxQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gR0VORVJBVEVEIGJ5IHByb2plbiB3YXRjaCAtIERPIE5PVCBFRElULlxuLy8gUmVnZW5lcmF0ZWQgZnJvbSB0aGUgZXhwb3J0aW5nIG1vZHVsZXMgaW4gLi9zcmMuXG4vLyBIYW5kIGVkaXRzIGFyZSBvdmVyd3JpdHRlbiBvbiB0aGUgbmV4dCB3YXRjaDsgdGhpcyBmaWxlIGlzIHJlYWQtb25seS5cblxuZXhwb3J0ICogYXMgYWxsb3dsaXN0IGZyb20gXCIuL3NyYy9hbGxvd2xpc3QudHNcIjtcbmV4cG9ydCAqIGFzIGFwcCBmcm9tIFwiLi9zcmMvYXBwLnRzXCI7XG5leHBvcnQgKiBhcyBjbGkgZnJvbSBcIi4vc3JjL2NsaS50c1wiO1xuZXhwb3J0ICogYXMgb3RwIGZyb20gXCIuL3NyYy9vdHAudHNcIjtcbmV4cG9ydCAqIGFzIHBsdWdpbiBmcm9tIFwiLi9zcmMvcGx1Z2luLnRzXCI7XG5leHBvcnQgKiBhcyBwb3J0ciBmcm9tIFwiLi9zcmMvcG9ydHIudHNcIjtcbmV4cG9ydCAqIGFzIHByb3h5IGZyb20gXCIuL3NyYy9wcm94eS50c1wiO1xuZXhwb3J0ICogYXMgcmF0ZUxpbWl0IGZyb20gXCIuL3NyYy9yYXRlLWxpbWl0LnRzXCI7XG5leHBvcnQgeyBDb21tYW5kZXJFcnJvciB9IGZyb20gXCIuL3NyYy9jbGkudHNcIjtcbmV4cG9ydCB7IENvZGVTdG9yZSB9IGZyb20gXCIuL3NyYy9vdHAudHNcIjtcbmV4cG9ydCB0eXBlIHsgVmVyaWZ5T3V0Y29tZSB9IGZyb20gXCIuL3NyYy9vdHAudHNcIjtcbmV4cG9ydCB7IEF1dGhHYXRlUGx1Z2luLCBhdXRoR2F0ZSB9IGZyb20gXCIuL3NyYy9wbHVnaW4udHNcIjtcbmV4cG9ydCB0eXBlIHsgQXV0aEdhdGVDb25maWcsIFNlbmRDb2RlT3B0aW9ucywgUmVzb2x2ZWRBdXRoR2F0ZUNvbmZpZywgQXV0aEdhdGVBcGkgfSBmcm9tIFwiLi9zcmMvcGx1Z2luLnRzXCI7XG5leHBvcnQgdHlwZSB7IFBvcnRyQ29uZmlnIH0gZnJvbSBcIi4vc3JjL3BvcnRyLnRzXCI7XG5leHBvcnQgdHlwZSB7IFByb3h5T3B0aW9ucyB9IGZyb20gXCIuL3NyYy9wcm94eS50c1wiO1xuZXhwb3J0IHsgUmF0ZUxpbWl0ZXIgfSBmcm9tIFwiLi9zcmMvcmF0ZS1saW1pdC50c1wiO1xuIl19
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Unified access allow-list matching for the email-OTP gate.
3
+ *
4
+ * Each pattern in the configured list is one of three shapes, tried in order:
5
+ *
6
+ * - **domain shortcut** - `databricks.com` or `@databricks.com`: matches any
7
+ * address whose domain equals it (case-insensitive). The leading `@` is
8
+ * optional and stripped.
9
+ * - **glob** - contains `*` or `?`, e.g. `*.databricks.com` or
10
+ * `*@databricks.com`: matched against the WHOLE address with shell-style
11
+ * wildcards (`*` = any run, `?` = one char).
12
+ * - **regex** - wrapped in slashes, `/.../ [flags]`: compiled and tested
13
+ * against the whole address. An invalid regex never matches (it is skipped
14
+ * with a warning rather than throwing).
15
+ *
16
+ * An EMPTY list matches nobody (fail closed): an app that enables the gate but
17
+ * configures no patterns lets no one in, which is the safe default.
18
+ *
19
+ * @module
20
+ */
21
+ /**
22
+ * True when `email` is allowed by ANY pattern in `patterns`. An empty (or
23
+ * missing) list allows nobody - the gate fails closed.
24
+ */
25
+ export declare function matchesAllowlist(email: string, patterns: readonly string[] | undefined): boolean;
26
+ /** Rough shape check so a clearly-invalid address is rejected before any work. */
27
+ export declare function looksLikeEmail(value: string): boolean;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Unified access allow-list matching for the email-OTP gate.
3
+ *
4
+ * Each pattern in the configured list is one of three shapes, tried in order:
5
+ *
6
+ * - **domain shortcut** - `databricks.com` or `@databricks.com`: matches any
7
+ * address whose domain equals it (case-insensitive). The leading `@` is
8
+ * optional and stripped.
9
+ * - **glob** - contains `*` or `?`, e.g. `*.databricks.com` or
10
+ * `*@databricks.com`: matched against the WHOLE address with shell-style
11
+ * wildcards (`*` = any run, `?` = one char).
12
+ * - **regex** - wrapped in slashes, `/.../ [flags]`: compiled and tested
13
+ * against the whole address. An invalid regex never matches (it is skipped
14
+ * with a warning rather than throwing).
15
+ *
16
+ * An EMPTY list matches nobody (fail closed): an app that enables the gate but
17
+ * configures no patterns lets no one in, which is the safe default.
18
+ *
19
+ * @module
20
+ */
21
+ import { log } from "@dbx-tools/shared-core";
22
+ const logger = log.logger("tunnel:allowlist");
23
+ /** Escape a string for literal use inside a `RegExp`. */
24
+ function escapeRegExp(value) {
25
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+ }
27
+ /** Compile a shell-style glob (`*`, `?`) into an anchored, case-insensitive RegExp. */
28
+ function globToRegExp(glob) {
29
+ const body = glob
30
+ .split(/([*?])/)
31
+ .map((part) => (part === "*" ? ".*" : part === "?" ? "." : escapeRegExp(part)))
32
+ .join("");
33
+ return new RegExp(`^${body}$`, "i");
34
+ }
35
+ /** Parse a `/pattern/flags` string into a RegExp, or `undefined` if malformed. */
36
+ function parseRegexLiteral(pattern) {
37
+ const match = /^\/(.+)\/([a-z]*)$/is.exec(pattern);
38
+ if (!match)
39
+ return undefined;
40
+ try {
41
+ const flags = match[2].includes("i") ? match[2] : `${match[2]}i`;
42
+ return new RegExp(match[1], flags);
43
+ }
44
+ catch (error) {
45
+ logger.warn("ignoring invalid regex allow-list pattern", { pattern, error });
46
+ return undefined;
47
+ }
48
+ }
49
+ /** True when `email` matches a single allow-list `pattern`. */
50
+ function matchesPattern(email, pattern) {
51
+ const trimmed = pattern.trim();
52
+ if (!trimmed)
53
+ return false;
54
+ const address = email.trim().toLowerCase();
55
+ // Regex literal: /.../
56
+ if (trimmed.startsWith("/")) {
57
+ const re = parseRegexLiteral(trimmed);
58
+ return re ? re.test(address) : false;
59
+ }
60
+ // Glob: contains a wildcard.
61
+ if (trimmed.includes("*") || trimmed.includes("?")) {
62
+ return globToRegExp(trimmed).test(address);
63
+ }
64
+ // Domain shortcut: `@d.com` or `d.com` -> match the address's domain.
65
+ const domain = trimmed.replace(/^@/, "").toLowerCase();
66
+ const at = address.lastIndexOf("@");
67
+ return at >= 0 && address.slice(at + 1) === domain;
68
+ }
69
+ /**
70
+ * True when `email` is allowed by ANY pattern in `patterns`. An empty (or
71
+ * missing) list allows nobody - the gate fails closed.
72
+ */
73
+ export function matchesAllowlist(email, patterns) {
74
+ if (!email || !patterns || patterns.length === 0)
75
+ return false;
76
+ return patterns.some((pattern) => matchesPattern(email, pattern));
77
+ }
78
+ /** Rough shape check so a clearly-invalid address is rejected before any work. */
79
+ export function looksLikeEmail(value) {
80
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
81
+ }
82
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWxsb3dsaXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FsbG93bGlzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUVILE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUU3QyxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLGtCQUFrQixDQUFDLENBQUM7QUFFOUMseURBQXlEO0FBQ3pELFNBQVMsWUFBWSxDQUFDLEtBQWE7SUFDakMsT0FBTyxLQUFLLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLE1BQU0sQ0FBQyxDQUFDO0FBQ3RELENBQUM7QUFFRCx1RkFBdUY7QUFDdkYsU0FBUyxZQUFZLENBQUMsSUFBWTtJQUNoQyxNQUFNLElBQUksR0FBRyxJQUFJO1NBQ2QsS0FBSyxDQUFDLFFBQVEsQ0FBQztTQUNmLEdBQUcsQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQyxJQUFJLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7U0FDOUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ1osT0FBTyxJQUFJLE1BQU0sQ0FBQyxJQUFJLElBQUksR0FBRyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0FBQ3RDLENBQUM7QUFFRCxrRkFBa0Y7QUFDbEYsU0FBUyxpQkFBaUIsQ0FBQyxPQUFlO0lBQ3hDLE1BQU0sS0FBSyxHQUFHLHNCQUFzQixDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNuRCxJQUFJLENBQUMsS0FBSztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQzdCLElBQUksQ0FBQztRQUNILE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUUsQ0FBQyxDQUFDLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFFLEdBQUcsQ0FBQztRQUNwRSxPQUFPLElBQUksTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUN0QyxDQUFDO0lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztRQUNmLE1BQU0sQ0FBQyxJQUFJLENBQUMsMkNBQTJDLEVBQUUsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUM3RSxPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0FBQ0gsQ0FBQztBQUVELCtEQUErRDtBQUMvRCxTQUFTLGNBQWMsQ0FBQyxLQUFhLEVBQUUsT0FBZTtJQUNwRCxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDL0IsSUFBSSxDQUFDLE9BQU87UUFBRSxPQUFPLEtBQUssQ0FBQztJQUMzQixNQUFNLE9BQU8sR0FBRyxLQUFLLENBQUMsSUFBSSxFQUFFLENBQUMsV0FBVyxFQUFFLENBQUM7SUFFM0MsdUJBQXVCO0lBQ3ZCLElBQUksT0FBTyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQzVCLE1BQU0sRUFBRSxHQUFHLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQ3RDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUM7SUFDdkMsQ0FBQztJQUVELDZCQUE2QjtJQUM3QixJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLElBQUksT0FBTyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ25ELE9BQU8sWUFBWSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUM3QyxDQUFDO0lBRUQsc0VBQXNFO0lBQ3RFLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFDO0lBQ3ZELE1BQU0sRUFBRSxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDcEMsT0FBTyxFQUFFLElBQUksQ0FBQyxJQUFJLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQyxLQUFLLE1BQU0sQ0FBQztBQUNyRCxDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQixDQUFDLEtBQWEsRUFBRSxRQUF1QztJQUNyRixJQUFJLENBQUMsS0FBSyxJQUFJLENBQUMsUUFBUSxJQUFJLFFBQVEsQ0FBQyxNQUFNLEtBQUssQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQy9ELE9BQU8sUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsY0FBYyxDQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO0FBQ3BFLENBQUM7QUFFRCxrRkFBa0Y7QUFDbEYsTUFBTSxVQUFVLGNBQWMsQ0FBQyxLQUFhO0lBQzFDLE9BQU8sNEJBQTRCLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0FBQ3pELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFVuaWZpZWQgYWNjZXNzIGFsbG93LWxpc3QgbWF0Y2hpbmcgZm9yIHRoZSBlbWFpbC1PVFAgZ2F0ZS5cbiAqXG4gKiBFYWNoIHBhdHRlcm4gaW4gdGhlIGNvbmZpZ3VyZWQgbGlzdCBpcyBvbmUgb2YgdGhyZWUgc2hhcGVzLCB0cmllZCBpbiBvcmRlcjpcbiAqXG4gKiAgIC0gKipkb21haW4gc2hvcnRjdXQqKiAtIGBkYXRhYnJpY2tzLmNvbWAgb3IgYEBkYXRhYnJpY2tzLmNvbWA6IG1hdGNoZXMgYW55XG4gKiAgICAgYWRkcmVzcyB3aG9zZSBkb21haW4gZXF1YWxzIGl0IChjYXNlLWluc2Vuc2l0aXZlKS4gVGhlIGxlYWRpbmcgYEBgIGlzXG4gKiAgICAgb3B0aW9uYWwgYW5kIHN0cmlwcGVkLlxuICogICAtICoqZ2xvYioqIC0gY29udGFpbnMgYCpgIG9yIGA/YCwgZS5nLiBgKi5kYXRhYnJpY2tzLmNvbWAgb3JcbiAqICAgICBgKkBkYXRhYnJpY2tzLmNvbWA6IG1hdGNoZWQgYWdhaW5zdCB0aGUgV0hPTEUgYWRkcmVzcyB3aXRoIHNoZWxsLXN0eWxlXG4gKiAgICAgd2lsZGNhcmRzIChgKmAgPSBhbnkgcnVuLCBgP2AgPSBvbmUgY2hhcikuXG4gKiAgIC0gKipyZWdleCoqIC0gd3JhcHBlZCBpbiBzbGFzaGVzLCBgLy4uLi8gW2ZsYWdzXWA6IGNvbXBpbGVkIGFuZCB0ZXN0ZWRcbiAqICAgICBhZ2FpbnN0IHRoZSB3aG9sZSBhZGRyZXNzLiBBbiBpbnZhbGlkIHJlZ2V4IG5ldmVyIG1hdGNoZXMgKGl0IGlzIHNraXBwZWRcbiAqICAgICB3aXRoIGEgd2FybmluZyByYXRoZXIgdGhhbiB0aHJvd2luZykuXG4gKlxuICogQW4gRU1QVFkgbGlzdCBtYXRjaGVzIG5vYm9keSAoZmFpbCBjbG9zZWQpOiBhbiBhcHAgdGhhdCBlbmFibGVzIHRoZSBnYXRlIGJ1dFxuICogY29uZmlndXJlcyBubyBwYXR0ZXJucyBsZXRzIG5vIG9uZSBpbiwgd2hpY2ggaXMgdGhlIHNhZmUgZGVmYXVsdC5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgbG9nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcInR1bm5lbDphbGxvd2xpc3RcIik7XG5cbi8qKiBFc2NhcGUgYSBzdHJpbmcgZm9yIGxpdGVyYWwgdXNlIGluc2lkZSBhIGBSZWdFeHBgLiAqL1xuZnVuY3Rpb24gZXNjYXBlUmVnRXhwKHZhbHVlOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gdmFsdWUucmVwbGFjZSgvWy4qKz9eJHt9KCl8W1xcXVxcXFxdL2csIFwiXFxcXCQmXCIpO1xufVxuXG4vKiogQ29tcGlsZSBhIHNoZWxsLXN0eWxlIGdsb2IgKGAqYCwgYD9gKSBpbnRvIGFuIGFuY2hvcmVkLCBjYXNlLWluc2Vuc2l0aXZlIFJlZ0V4cC4gKi9cbmZ1bmN0aW9uIGdsb2JUb1JlZ0V4cChnbG9iOiBzdHJpbmcpOiBSZWdFeHAge1xuICBjb25zdCBib2R5ID0gZ2xvYlxuICAgIC5zcGxpdCgvKFsqP10pLylcbiAgICAubWFwKChwYXJ0KSA9PiAocGFydCA9PT0gXCIqXCIgPyBcIi4qXCIgOiBwYXJ0ID09PSBcIj9cIiA/IFwiLlwiIDogZXNjYXBlUmVnRXhwKHBhcnQpKSlcbiAgICAuam9pbihcIlwiKTtcbiAgcmV0dXJuIG5ldyBSZWdFeHAoYF4ke2JvZHl9JGAsIFwiaVwiKTtcbn1cblxuLyoqIFBhcnNlIGEgYC9wYXR0ZXJuL2ZsYWdzYCBzdHJpbmcgaW50byBhIFJlZ0V4cCwgb3IgYHVuZGVmaW5lZGAgaWYgbWFsZm9ybWVkLiAqL1xuZnVuY3Rpb24gcGFyc2VSZWdleExpdGVyYWwocGF0dGVybjogc3RyaW5nKTogUmVnRXhwIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgbWF0Y2ggPSAvXlxcLyguKylcXC8oW2Etel0qKSQvaXMuZXhlYyhwYXR0ZXJuKTtcbiAgaWYgKCFtYXRjaCkgcmV0dXJuIHVuZGVmaW5lZDtcbiAgdHJ5IHtcbiAgICBjb25zdCBmbGFncyA9IG1hdGNoWzJdIS5pbmNsdWRlcyhcImlcIikgPyBtYXRjaFsyXSEgOiBgJHttYXRjaFsyXSF9aWA7XG4gICAgcmV0dXJuIG5ldyBSZWdFeHAobWF0Y2hbMV0hLCBmbGFncyk7XG4gIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgbG9nZ2VyLndhcm4oXCJpZ25vcmluZyBpbnZhbGlkIHJlZ2V4IGFsbG93LWxpc3QgcGF0dGVyblwiLCB7IHBhdHRlcm4sIGVycm9yIH0pO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cbn1cblxuLyoqIFRydWUgd2hlbiBgZW1haWxgIG1hdGNoZXMgYSBzaW5nbGUgYWxsb3ctbGlzdCBgcGF0dGVybmAuICovXG5mdW5jdGlvbiBtYXRjaGVzUGF0dGVybihlbWFpbDogc3RyaW5nLCBwYXR0ZXJuOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgY29uc3QgdHJpbW1lZCA9IHBhdHRlcm4udHJpbSgpO1xuICBpZiAoIXRyaW1tZWQpIHJldHVybiBmYWxzZTtcbiAgY29uc3QgYWRkcmVzcyA9IGVtYWlsLnRyaW0oKS50b0xvd2VyQ2FzZSgpO1xuXG4gIC8vIFJlZ2V4IGxpdGVyYWw6IC8uLi4vXG4gIGlmICh0cmltbWVkLnN0YXJ0c1dpdGgoXCIvXCIpKSB7XG4gICAgY29uc3QgcmUgPSBwYXJzZVJlZ2V4TGl0ZXJhbCh0cmltbWVkKTtcbiAgICByZXR1cm4gcmUgPyByZS50ZXN0KGFkZHJlc3MpIDogZmFsc2U7XG4gIH1cblxuICAvLyBHbG9iOiBjb250YWlucyBhIHdpbGRjYXJkLlxuICBpZiAodHJpbW1lZC5pbmNsdWRlcyhcIipcIikgfHwgdHJpbW1lZC5pbmNsdWRlcyhcIj9cIikpIHtcbiAgICByZXR1cm4gZ2xvYlRvUmVnRXhwKHRyaW1tZWQpLnRlc3QoYWRkcmVzcyk7XG4gIH1cblxuICAvLyBEb21haW4gc2hvcnRjdXQ6IGBAZC5jb21gIG9yIGBkLmNvbWAgLT4gbWF0Y2ggdGhlIGFkZHJlc3MncyBkb21haW4uXG4gIGNvbnN0IGRvbWFpbiA9IHRyaW1tZWQucmVwbGFjZSgvXkAvLCBcIlwiKS50b0xvd2VyQ2FzZSgpO1xuICBjb25zdCBhdCA9IGFkZHJlc3MubGFzdEluZGV4T2YoXCJAXCIpO1xuICByZXR1cm4gYXQgPj0gMCAmJiBhZGRyZXNzLnNsaWNlKGF0ICsgMSkgPT09IGRvbWFpbjtcbn1cblxuLyoqXG4gKiBUcnVlIHdoZW4gYGVtYWlsYCBpcyBhbGxvd2VkIGJ5IEFOWSBwYXR0ZXJuIGluIGBwYXR0ZXJuc2AuIEFuIGVtcHR5IChvclxuICogbWlzc2luZykgbGlzdCBhbGxvd3Mgbm9ib2R5IC0gdGhlIGdhdGUgZmFpbHMgY2xvc2VkLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbWF0Y2hlc0FsbG93bGlzdChlbWFpbDogc3RyaW5nLCBwYXR0ZXJuczogcmVhZG9ubHkgc3RyaW5nW10gfCB1bmRlZmluZWQpOiBib29sZWFuIHtcbiAgaWYgKCFlbWFpbCB8fCAhcGF0dGVybnMgfHwgcGF0dGVybnMubGVuZ3RoID09PSAwKSByZXR1cm4gZmFsc2U7XG4gIHJldHVybiBwYXR0ZXJucy5zb21lKChwYXR0ZXJuKSA9PiBtYXRjaGVzUGF0dGVybihlbWFpbCwgcGF0dGVybikpO1xufVxuXG4vKiogUm91Z2ggc2hhcGUgY2hlY2sgc28gYSBjbGVhcmx5LWludmFsaWQgYWRkcmVzcyBpcyByZWplY3RlZCBiZWZvcmUgYW55IHdvcmsuICovXG5leHBvcnQgZnVuY3Rpb24gbG9va3NMaWtlRW1haWwodmFsdWU6IHN0cmluZyk6IGJvb2xlYW4ge1xuICByZXR1cm4gL15bXlxcc0BdK0BbXlxcc0BdK1xcLlteXFxzQF0rJC8udGVzdCh2YWx1ZS50cmltKCkpO1xufVxuIl19
@@ -0,0 +1,31 @@
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` (memory, or Lakebase when the deploy configures persistent
7
+ * cache storage) - which the OTP `CodeStore` uses for storage + TTL eviction;
8
+ * - the `email()` transport (SMTP / outbox), primed from env, so `sendCode` can
9
+ * deliver the one-time code;
10
+ * - dbx-tools auto-configuration + branding via `@dbx-tools/appkit`.
11
+ *
12
+ * It returns the {@link AuthGateApi} the proxy drives. `sendCode` is wired here
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.
16
+ *
17
+ * FAIL FAST: a gate that can't email a code is useless, so if email does not
18
+ * resolve to SMTP mode (real delivery), this throws - unless `insecure` is set
19
+ * (`--insecure` / `TUNNEL_INSECURE=true`), in which case the caller runs the
20
+ * tunnel OPEN with no gate.
21
+ *
22
+ * @module
23
+ */
24
+ import { type AuthGateApi, type AuthGateConfig } from "./plugin.ts";
25
+ /**
26
+ * Boot the gate app and return the API the proxy calls. Throws when email is not
27
+ * in SMTP mode (no way to deliver a code) so a misconfigured gate fails fast at
28
+ * startup rather than silently accepting nobody; the caller may catch this and
29
+ * fall back to insecure/open mode when the operator opted in.
30
+ */
31
+ export declare function startGateApp(config: AuthGateConfig): Promise<AuthGateApi>;
package/lib/src/app.js ADDED
@@ -0,0 +1,71 @@
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` (memory, or Lakebase when the deploy configures persistent
7
+ * cache storage) - which the OTP `CodeStore` uses for storage + TTL eviction;
8
+ * - the `email()` transport (SMTP / outbox), primed from env, so `sendCode` can
9
+ * deliver the one-time code;
10
+ * - dbx-tools auto-configuration + branding via `@dbx-tools/appkit`.
11
+ *
12
+ * It returns the {@link AuthGateApi} the proxy drives. `sendCode` is wired here
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.
16
+ *
17
+ * FAIL FAST: a gate that can't email a code is useless, so if email does not
18
+ * resolve to SMTP mode (real delivery), this throws - unless `insecure` is set
19
+ * (`--insecure` / `TUNNEL_INSECURE=true`), in which case the caller runs the
20
+ * tunnel OPEN with no gate.
21
+ *
22
+ * @module
23
+ */
24
+ import { createApp as createAppNs } from "@dbx-tools/appkit";
25
+ import { email, sender, transport } from "@dbx-tools/email";
26
+ import { log } from "@dbx-tools/shared-core";
27
+ import { authGate } from "./plugin.js";
28
+ const logger = log.logger("tunnel:app");
29
+ const { createApp } = createAppNs;
30
+ const { resolveSenderAddress } = sender;
31
+ const { getEmailRuntime, sendEmail } = transport;
32
+ /**
33
+ * Boot the gate app and return the API the proxy calls. Throws when email is not
34
+ * in SMTP mode (no way to deliver a code) so a misconfigured gate fails fast at
35
+ * startup rather than silently accepting nobody; the caller may catch this and
36
+ * fall back to insecure/open mode when the operator opted in.
37
+ */
38
+ export async function startGateApp(config) {
39
+ // `sendCode` delivers the OTP through the email plugin's SHARED transport, which
40
+ // the `email()` plugin primes during its `setup()` (awaited by `createApp`
41
+ // below). Using the module-level `sendEmail` avoids a circular dependency on the
42
+ // app handle's inferred type. `From` is the app's configured sender (EMAIL_FROM /
43
+ // EMAIL_DOMAIN); the code email has no OBO user, so it resolves with `undefined`.
44
+ const sendCode = async (to, code, opts) => {
45
+ const from = resolveSenderAddress(getEmailRuntime().config, undefined);
46
+ await sendEmail({
47
+ to: [to],
48
+ subject: opts.subject,
49
+ body: [
50
+ opts.message,
51
+ "",
52
+ `## ${code}`,
53
+ "",
54
+ "It expires shortly. If you didn't request this, ignore this email.",
55
+ ].join("\n"),
56
+ }, from);
57
+ };
58
+ // `createApp` namespaces each plugin's exports on the handle by manifest name;
59
+ // `handle.authGate` is the in-process gate API the proxy drives.
60
+ const handle = await createApp({ plugins: [email(), authGate({ ...config, sendCode })] });
61
+ // Fail fast: the gate needs SMTP to email codes. `getEmailRuntime()` resolves to
62
+ // `mode: "file"` (outbox) or throws when no SMTP creds are configured - neither
63
+ // can deliver to a real inbox, so refuse to bring up a gate that lets nobody in.
64
+ if (getEmailRuntime().config.mode !== "smtp") {
65
+ throw new Error("email is not configured for SMTP delivery - the OTP gate cannot send codes. " +
66
+ "Set SMTP_HOST/SMTP_USER/SMTP_PASSWORD, or pass --insecure / TUNNEL_INSECURE=true to run the tunnel open.");
67
+ }
68
+ logger.info("gate app ready (no server; proxy-driven)");
69
+ return handle.authGate;
70
+ }
71
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCRztBQUVILE9BQU8sRUFBRSxTQUFTLElBQUksV0FBVyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDN0QsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFDNUQsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdDLE9BQU8sRUFBRSxRQUFRLEVBQXlDLE1BQU0sYUFBYSxDQUFDO0FBRTlFLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7QUFFeEMsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLFdBQVcsQ0FBQztBQUNsQyxNQUFNLEVBQUUsb0JBQW9CLEVBQUUsR0FBRyxNQUFNLENBQUM7QUFDeEMsTUFBTSxFQUFFLGVBQWUsRUFBRSxTQUFTLEVBQUUsR0FBRyxTQUFTLENBQUM7QUFFakQ7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFlBQVksQ0FBQyxNQUFzQjtJQUN2RCxpRkFBaUY7SUFDakYsMkVBQTJFO0lBQzNFLGlGQUFpRjtJQUNqRixrRkFBa0Y7SUFDbEYsa0ZBQWtGO0lBQ2xGLE1BQU0sUUFBUSxHQUE0QyxLQUFLLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsRUFBRTtRQUNqRixNQUFNLElBQUksR0FBRyxvQkFBb0IsQ0FBQyxlQUFlLEVBQUUsQ0FBQyxNQUFNLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDdkUsTUFBTSxTQUFTLENBQ2I7WUFDRSxFQUFFLEVBQUUsQ0FBQyxFQUFFLENBQUM7WUFDUixPQUFPLEVBQUUsSUFBSSxDQUFDLE9BQU87WUFDckIsSUFBSSxFQUFFO2dCQUNKLElBQUksQ0FBQyxPQUFPO2dCQUNaLEVBQUU7Z0JBQ0YsTUFBTSxJQUFJLEVBQUU7Z0JBQ1osRUFBRTtnQkFDRixvRUFBb0U7YUFDckUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO1NBQ2IsRUFDRCxJQUFJLENBQ0wsQ0FBQztJQUNKLENBQUMsQ0FBQztJQUVGLCtFQUErRTtJQUMvRSxpRUFBaUU7SUFDakUsTUFBTSxNQUFNLEdBQUcsTUFBTSxTQUFTLENBQUMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxLQUFLLEVBQUUsRUFBRSxRQUFRLENBQUMsRUFBRSxHQUFHLE1BQU0sRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBRTFGLGlGQUFpRjtJQUNqRixnRkFBZ0Y7SUFDaEYsaUZBQWlGO0lBQ2pGLElBQUksZUFBZSxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUUsQ0FBQztRQUM3QyxNQUFNLElBQUksS0FBSyxDQUNiLDhFQUE4RTtZQUM1RSwwR0FBMEcsQ0FDN0csQ0FBQztJQUNKLENBQUM7SUFFRCxNQUFNLENBQUMsSUFBSSxDQUFDLDBDQUEwQyxDQUFDLENBQUM7SUFDeEQsT0FBTyxNQUFNLENBQUMsUUFBdUIsQ0FBQztBQUN4QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGUgdHVubmVsIGdhdGUncyB0aW55IEFwcEtpdCBcImFwcFwiIC0gYGNyZWF0ZUFwcGAgV0lUSE9VVCBhIGBzZXJ2ZXIoKWAgcGx1Z2luLlxuICpcbiAqIFRoZXJlIGlzIG5vIEhUVFAgc2VydmVyIGhlcmU6IHRoZSB0dW5uZWwgcHJveHkgaXMgdGhlIHNlcnZlciwgYW5kIGl0IGNhbGxzIHRoZVxuICogZ2F0ZSBoYW5kbGVycyBpbi1wcm9jZXNzLiBgY3JlYXRlQXBwYCBpcyB1c2VkIG9ubHkgZm9yIHdoYXQgaXQgYXV0by13aXJlczpcbiAqICAgLSBgQ2FjaGVNYW5hZ2VyYCAobWVtb3J5LCBvciBMYWtlYmFzZSB3aGVuIHRoZSBkZXBsb3kgY29uZmlndXJlcyBwZXJzaXN0ZW50XG4gKiAgICAgY2FjaGUgc3RvcmFnZSkgLSB3aGljaCB0aGUgT1RQIGBDb2RlU3RvcmVgIHVzZXMgZm9yIHN0b3JhZ2UgKyBUVEwgZXZpY3Rpb247XG4gKiAgIC0gdGhlIGBlbWFpbCgpYCB0cmFuc3BvcnQgKFNNVFAgLyBvdXRib3gpLCBwcmltZWQgZnJvbSBlbnYsIHNvIGBzZW5kQ29kZWAgY2FuXG4gKiAgICAgZGVsaXZlciB0aGUgb25lLXRpbWUgY29kZTtcbiAqICAgLSBkYngtdG9vbHMgYXV0by1jb25maWd1cmF0aW9uICsgYnJhbmRpbmcgdmlhIGBAZGJ4LXRvb2xzL2FwcGtpdGAuXG4gKlxuICogSXQgcmV0dXJucyB0aGUge0BsaW5rIEF1dGhHYXRlQXBpfSB0aGUgcHJveHkgZHJpdmVzLiBgc2VuZENvZGVgIGlzIHdpcmVkIGhlcmVcbiAqICh0aGUgcGx1Z2luIGNhbid0IHJlc29sdmUgSE9XIHRvIHNlbmQgb24gaXRzIG93bikgdG8gdGhlIGVtYWlsIHBsdWdpbidzXG4gKiB0cmFuc3BvcnQsIHJlc29sdmluZyB0aGUgYEZyb21gIGZyb20gdGhlIGVtYWlsIHJ1bnRpbWUgY29uZmlnIChFTUFJTF9GUk9NIC9cbiAqIEVNQUlMX0RPTUFJTikgc2luY2UgYW4gT1RQIHJlcXVlc3QgaGFzIG5vIG9uLWJlaGFsZi1vZiB1c2VyLlxuICpcbiAqIEZBSUwgRkFTVDogYSBnYXRlIHRoYXQgY2FuJ3QgZW1haWwgYSBjb2RlIGlzIHVzZWxlc3MsIHNvIGlmIGVtYWlsIGRvZXMgbm90XG4gKiByZXNvbHZlIHRvIFNNVFAgbW9kZSAocmVhbCBkZWxpdmVyeSksIHRoaXMgdGhyb3dzIC0gdW5sZXNzIGBpbnNlY3VyZWAgaXMgc2V0XG4gKiAoYC0taW5zZWN1cmVgIC8gYFRVTk5FTF9JTlNFQ1VSRT10cnVlYCksIGluIHdoaWNoIGNhc2UgdGhlIGNhbGxlciBydW5zIHRoZVxuICogdHVubmVsIE9QRU4gd2l0aCBubyBnYXRlLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVBcHAgYXMgY3JlYXRlQXBwTnMgfSBmcm9tIFwiQGRieC10b29scy9hcHBraXRcIjtcbmltcG9ydCB7IGVtYWlsLCBzZW5kZXIsIHRyYW5zcG9ydCB9IGZyb20gXCJAZGJ4LXRvb2xzL2VtYWlsXCI7XG5pbXBvcnQgeyBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgYXV0aEdhdGUsIHR5cGUgQXV0aEdhdGVBcGksIHR5cGUgQXV0aEdhdGVDb25maWcgfSBmcm9tIFwiLi9wbHVnaW4udHNcIjtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcInR1bm5lbDphcHBcIik7XG5cbmNvbnN0IHsgY3JlYXRlQXBwIH0gPSBjcmVhdGVBcHBOcztcbmNvbnN0IHsgcmVzb2x2ZVNlbmRlckFkZHJlc3MgfSA9IHNlbmRlcjtcbmNvbnN0IHsgZ2V0RW1haWxSdW50aW1lLCBzZW5kRW1haWwgfSA9IHRyYW5zcG9ydDtcblxuLyoqXG4gKiBCb290IHRoZSBnYXRlIGFwcCBhbmQgcmV0dXJuIHRoZSBBUEkgdGhlIHByb3h5IGNhbGxzLiBUaHJvd3Mgd2hlbiBlbWFpbCBpcyBub3RcbiAqIGluIFNNVFAgbW9kZSAobm8gd2F5IHRvIGRlbGl2ZXIgYSBjb2RlKSBzbyBhIG1pc2NvbmZpZ3VyZWQgZ2F0ZSBmYWlscyBmYXN0IGF0XG4gKiBzdGFydHVwIHJhdGhlciB0aGFuIHNpbGVudGx5IGFjY2VwdGluZyBub2JvZHk7IHRoZSBjYWxsZXIgbWF5IGNhdGNoIHRoaXMgYW5kXG4gKiBmYWxsIGJhY2sgdG8gaW5zZWN1cmUvb3BlbiBtb2RlIHdoZW4gdGhlIG9wZXJhdG9yIG9wdGVkIGluLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc3RhcnRHYXRlQXBwKGNvbmZpZzogQXV0aEdhdGVDb25maWcpOiBQcm9taXNlPEF1dGhHYXRlQXBpPiB7XG4gIC8vIGBzZW5kQ29kZWAgZGVsaXZlcnMgdGhlIE9UUCB0aHJvdWdoIHRoZSBlbWFpbCBwbHVnaW4ncyBTSEFSRUQgdHJhbnNwb3J0LCB3aGljaFxuICAvLyB0aGUgYGVtYWlsKClgIHBsdWdpbiBwcmltZXMgZHVyaW5nIGl0cyBgc2V0dXAoKWAgKGF3YWl0ZWQgYnkgYGNyZWF0ZUFwcGBcbiAgLy8gYmVsb3cpLiBVc2luZyB0aGUgbW9kdWxlLWxldmVsIGBzZW5kRW1haWxgIGF2b2lkcyBhIGNpcmN1bGFyIGRlcGVuZGVuY3kgb24gdGhlXG4gIC8vIGFwcCBoYW5kbGUncyBpbmZlcnJlZCB0eXBlLiBgRnJvbWAgaXMgdGhlIGFwcCdzIGNvbmZpZ3VyZWQgc2VuZGVyIChFTUFJTF9GUk9NIC9cbiAgLy8gRU1BSUxfRE9NQUlOKTsgdGhlIGNvZGUgZW1haWwgaGFzIG5vIE9CTyB1c2VyLCBzbyBpdCByZXNvbHZlcyB3aXRoIGB1bmRlZmluZWRgLlxuICBjb25zdCBzZW5kQ29kZTogTm9uTnVsbGFibGU8QXV0aEdhdGVDb25maWdbXCJzZW5kQ29kZVwiXT4gPSBhc3luYyAodG8sIGNvZGUsIG9wdHMpID0+IHtcbiAgICBjb25zdCBmcm9tID0gcmVzb2x2ZVNlbmRlckFkZHJlc3MoZ2V0RW1haWxSdW50aW1lKCkuY29uZmlnLCB1bmRlZmluZWQpO1xuICAgIGF3YWl0IHNlbmRFbWFpbChcbiAgICAgIHtcbiAgICAgICAgdG86IFt0b10sXG4gICAgICAgIHN1YmplY3Q6IG9wdHMuc3ViamVjdCxcbiAgICAgICAgYm9keTogW1xuICAgICAgICAgIG9wdHMubWVzc2FnZSxcbiAgICAgICAgICBcIlwiLFxuICAgICAgICAgIGAjIyAke2NvZGV9YCxcbiAgICAgICAgICBcIlwiLFxuICAgICAgICAgIFwiSXQgZXhwaXJlcyBzaG9ydGx5LiBJZiB5b3UgZGlkbid0IHJlcXVlc3QgdGhpcywgaWdub3JlIHRoaXMgZW1haWwuXCIsXG4gICAgICAgIF0uam9pbihcIlxcblwiKSxcbiAgICAgIH0sXG4gICAgICBmcm9tLFxuICAgICk7XG4gIH07XG5cbiAgLy8gYGNyZWF0ZUFwcGAgbmFtZXNwYWNlcyBlYWNoIHBsdWdpbidzIGV4cG9ydHMgb24gdGhlIGhhbmRsZSBieSBtYW5pZmVzdCBuYW1lO1xuICAvLyBgaGFuZGxlLmF1dGhHYXRlYCBpcyB0aGUgaW4tcHJvY2VzcyBnYXRlIEFQSSB0aGUgcHJveHkgZHJpdmVzLlxuICBjb25zdCBoYW5kbGUgPSBhd2FpdCBjcmVhdGVBcHAoeyBwbHVnaW5zOiBbZW1haWwoKSwgYXV0aEdhdGUoeyAuLi5jb25maWcsIHNlbmRDb2RlIH0pXSB9KTtcblxuICAvLyBGYWlsIGZhc3Q6IHRoZSBnYXRlIG5lZWRzIFNNVFAgdG8gZW1haWwgY29kZXMuIGBnZXRFbWFpbFJ1bnRpbWUoKWAgcmVzb2x2ZXMgdG9cbiAgLy8gYG1vZGU6IFwiZmlsZVwiYCAob3V0Ym94KSBvciB0aHJvd3Mgd2hlbiBubyBTTVRQIGNyZWRzIGFyZSBjb25maWd1cmVkIC0gbmVpdGhlclxuICAvLyBjYW4gZGVsaXZlciB0byBhIHJlYWwgaW5ib3gsIHNvIHJlZnVzZSB0byBicmluZyB1cCBhIGdhdGUgdGhhdCBsZXRzIG5vYm9keSBpbi5cbiAgaWYgKGdldEVtYWlsUnVudGltZSgpLmNvbmZpZy5tb2RlICE9PSBcInNtdHBcIikge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgIFwiZW1haWwgaXMgbm90IGNvbmZpZ3VyZWQgZm9yIFNNVFAgZGVsaXZlcnkgLSB0aGUgT1RQIGdhdGUgY2Fubm90IHNlbmQgY29kZXMuIFwiICtcbiAgICAgICAgXCJTZXQgU01UUF9IT1NUL1NNVFBfVVNFUi9TTVRQX1BBU1NXT1JELCBvciBwYXNzIC0taW5zZWN1cmUgLyBUVU5ORUxfSU5TRUNVUkU9dHJ1ZSB0byBydW4gdGhlIHR1bm5lbCBvcGVuLlwiLFxuICAgICk7XG4gIH1cblxuICBsb2dnZXIuaW5mbyhcImdhdGUgYXBwIHJlYWR5IChubyBzZXJ2ZXI7IHByb3h5LWRyaXZlbilcIik7XG4gIHJldHVybiBoYW5kbGUuYXV0aEdhdGUgYXMgQXV0aEdhdGVBcGk7XG59XG4iXX0=
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `dbx-tools-tunnel` / `dbxt-tunnel` CLI.
3
+ *
4
+ * Wraps a Databricks App start command with a public portr tunnel and an
5
+ * email-OTP access gate. Everything after `--` is the REAL app start command:
6
+ *
7
+ * dbxt-tunnel --subject "Here's your OTP" --allow databricks.com -- bun src/server.ts
8
+ *
9
+ * Boot sequence:
10
+ * 1. Pick a random PRIVATE port and spawn the app command with
11
+ * `DATABRICKS_APP_PORT` set to it (so the app binds loopback-private).
12
+ * 2. Boot the tiny gate AppKit app (no server): inits `CacheManager` + the
13
+ * email transport, yields the in-process gate API.
14
+ * 3. Start the gate PROXY on the ORIGINAL public port, forwarding to the app.
15
+ * 4. Install + run portr pointed at the public port (when a tunnel is
16
+ * configured; otherwise the proxy still gates nothing and forwards).
17
+ *
18
+ * Supervision: the app child, portr child, and this process are tied together -
19
+ * if ANY exits, everything comes down (concurrently-style `killOthers`).
20
+ *
21
+ * Options come from flags OR env; see the option definitions below.
22
+ *
23
+ * @module
24
+ */
25
+ import { CommanderError } from "commander";
26
+ export { CommanderError };
27
+ /** Parse argv and run the tunnel. */
28
+ export declare function runCli(argv: string[]): Promise<void>;
package/lib/src/cli.js ADDED
@@ -0,0 +1,147 @@
1
+ /**
2
+ * `dbx-tools-tunnel` / `dbxt-tunnel` CLI.
3
+ *
4
+ * Wraps a Databricks App start command with a public portr tunnel and an
5
+ * email-OTP access gate. Everything after `--` is the REAL app start command:
6
+ *
7
+ * dbxt-tunnel --subject "Here's your OTP" --allow databricks.com -- bun src/server.ts
8
+ *
9
+ * Boot sequence:
10
+ * 1. Pick a random PRIVATE port and spawn the app command with
11
+ * `DATABRICKS_APP_PORT` set to it (so the app binds loopback-private).
12
+ * 2. Boot the tiny gate AppKit app (no server): inits `CacheManager` + the
13
+ * email transport, yields the in-process gate API.
14
+ * 3. Start the gate PROXY on the ORIGINAL public port, forwarding to the app.
15
+ * 4. Install + run portr pointed at the public port (when a tunnel is
16
+ * configured; otherwise the proxy still gates nothing and forwards).
17
+ *
18
+ * Supervision: the app child, portr child, and this process are tied together -
19
+ * if ANY exits, everything comes down (concurrently-style `killOthers`).
20
+ *
21
+ * Options come from flags OR env; see the option definitions below.
22
+ *
23
+ * @module
24
+ */
25
+ import { spawn } from "node:child_process";
26
+ import { log } from "@dbx-tools/shared-core";
27
+ import { Command, CommanderError } from "commander";
28
+ import { startGateApp } from "./app.js";
29
+ import { installPortr, resolvePortrConfig, startPortr, writePortrConfig } from "./portr.js";
30
+ import { startProxy } from "./proxy.js";
31
+ export { CommanderError };
32
+ const logger = log.logger("tunnel");
33
+ /** A random ephemeral port for the app to bind (the proxy fronts the public one). */
34
+ function randomPort() {
35
+ return 20000 + Math.floor(Math.random() * 20000);
36
+ }
37
+ /** Build the commander program. `--` separates flags from the app start command. */
38
+ function program() {
39
+ return new Command()
40
+ .name("dbx-tools-tunnel")
41
+ .description("Front a Databricks App with a public portr tunnel + email-OTP gate")
42
+ .option("--subject <text>", "Subject line for the code email (env AUTH_SUBJECT)")
43
+ .option("--allow <patterns>", "Comma/space-separated allow-list: domain / glob / /regex/ (env EMAIL_AUTH_ALLOW)")
44
+ .option("--subdomain <name>", "portr subdomain (else derived from PUBLIC_DOMAIN)")
45
+ .option("--public-domain <host>", "portr <subdomain>.<server> (env PUBLIC_DOMAIN)")
46
+ .option("--brand-name <name>", "Product name in the email + login copy (env AUTH_BRAND_NAME)")
47
+ .option("--message <text>", "Line shown above the code in the email (env AUTH_MESSAGE)")
48
+ .option("--session-ttl <seconds>", "Session lifetime (env AUTH_SESSION_TTL)")
49
+ .option("--code-ttl <seconds>", "One-time-code lifetime (env AUTH_CODE_TTL)")
50
+ .option("--insecure", "Run the tunnel OPEN with no gate (env TUNNEL_INSECURE=true). Otherwise the CLI fails fast when email SMTP is not configured.")
51
+ .allowExcessArguments(true)
52
+ .helpOption("-h, --help", "Show help");
53
+ }
54
+ /** Tie a child's exit to full teardown: any exit brings the whole tunnel down. */
55
+ function superviseExit(children) {
56
+ let shuttingDown = false;
57
+ const shutdown = (code) => {
58
+ if (shuttingDown)
59
+ return;
60
+ shuttingDown = true;
61
+ for (const child of children) {
62
+ if (!child.killed)
63
+ child.kill("SIGTERM");
64
+ }
65
+ // Give children a moment, then exit with the first non-zero code seen.
66
+ setTimeout(() => process.exit(code), 3000).unref();
67
+ };
68
+ for (const child of children) {
69
+ child.on("exit", (code) => {
70
+ logger.warn(`child exited (${code ?? "signal"}); bringing tunnel down`);
71
+ shutdown(code ?? 1);
72
+ });
73
+ }
74
+ for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
75
+ process.on(sig, () => shutdown(0));
76
+ }
77
+ }
78
+ /** Parse argv and run the tunnel. */
79
+ export async function runCli(argv) {
80
+ // Split flags from the wrapped command at the first `--`.
81
+ const sep = argv.indexOf("--");
82
+ const flags = sep >= 0 ? argv.slice(0, sep) : argv;
83
+ const command = sep >= 0 ? argv.slice(sep + 1) : [];
84
+ const prog = program();
85
+ prog.parse(flags);
86
+ const opts = prog.opts();
87
+ if (command.length === 0) {
88
+ throw new CommanderError(1, "tunnel.no-command", "no start command given after `--`");
89
+ }
90
+ const publicPort = Number(process.env.DATABRICKS_APP_PORT ?? 8000);
91
+ const appPort = randomPort();
92
+ const gateConfig = {
93
+ allow: opts.allow,
94
+ subject: opts.subject,
95
+ brandName: opts.brandName,
96
+ message: opts.message,
97
+ sessionTtlSeconds: opts.sessionTtl ? Number(opts.sessionTtl) : undefined,
98
+ codeTtlSeconds: opts.codeTtl ? Number(opts.codeTtl) : undefined,
99
+ };
100
+ // 1. Spawn the wrapped app with the PRIVATE port. It binds loopback; only the
101
+ // proxy reaches it.
102
+ logger.info(`spawning app on private port ${appPort}: ${command.join(" ")}`);
103
+ const [cmd, ...args] = command;
104
+ const app = spawn(cmd, args, {
105
+ env: { ...process.env, DATABRICKS_APP_PORT: String(appPort), HOST: "127.0.0.1" },
106
+ stdio: "inherit",
107
+ });
108
+ // 2. Boot the gate app (cache + email transport + gate API). `startGateApp`
109
+ // FAILS FAST when email can't send codes (no SMTP). Insecure mode
110
+ // (`--insecure` / TUNNEL_INSECURE) skips the gate and runs the tunnel open.
111
+ const insecure = opts.insecure || /^(1|true|yes|on)$/i.test(process.env.TUNNEL_INSECURE ?? "");
112
+ let gate;
113
+ if (insecure) {
114
+ logger.warn("insecure mode - tunnel runs OPEN with no email-OTP gate");
115
+ }
116
+ else {
117
+ try {
118
+ gate = await startGateApp(gateConfig);
119
+ }
120
+ catch (error) {
121
+ // Fail fast: don't silently expose an ungated tunnel. The operator must fix
122
+ // SMTP or explicitly opt into `--insecure`.
123
+ logger.error("cannot start the OTP gate", { error: error.message });
124
+ throw error;
125
+ }
126
+ }
127
+ // 3. Start the gate proxy on the public port (open when `gate` is undefined).
128
+ await startProxy({ publicPort, appPort, gate });
129
+ // 4. Install + run portr when a tunnel is configured.
130
+ const portrConfig = resolvePortrConfig({
131
+ publicDomain: opts.publicDomain,
132
+ subdomain: opts.subdomain,
133
+ port: publicPort,
134
+ });
135
+ const children = [app];
136
+ if (portrConfig) {
137
+ const env = installPortr();
138
+ writePortrConfig(portrConfig, env);
139
+ children.push(startPortr(portrConfig, env));
140
+ }
141
+ else {
142
+ logger.info("no PORTR_TOKEN/PUBLIC_DOMAIN - serving the gate proxy without a public tunnel");
143
+ }
144
+ // Any child exit (or a signal) tears the whole thing down.
145
+ superviseExit(children);
146
+ }
147
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NsaS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0F1Qkc7QUFFSCxPQUFPLEVBQXFCLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzlELE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUM3QyxPQUFPLEVBQUUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNwRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sVUFBVSxDQUFDO0FBRXhDLE9BQU8sRUFBRSxZQUFZLEVBQUUsa0JBQWtCLEVBQUUsVUFBVSxFQUFFLGdCQUFnQixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQzVGLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFFeEMsT0FBTyxFQUFFLGNBQWMsRUFBRSxDQUFDO0FBRTFCLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUM7QUFFcEMscUZBQXFGO0FBQ3JGLFNBQVMsVUFBVTtJQUNqQixPQUFPLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxLQUFLLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBY0Qsb0ZBQW9GO0FBQ3BGLFNBQVMsT0FBTztJQUNkLE9BQU8sSUFBSSxPQUFPLEVBQUU7U0FDakIsSUFBSSxDQUFDLGtCQUFrQixDQUFDO1NBQ3hCLFdBQVcsQ0FBQyxvRUFBb0UsQ0FBQztTQUNqRixNQUFNLENBQUMsa0JBQWtCLEVBQUUsb0RBQW9ELENBQUM7U0FDaEYsTUFBTSxDQUNMLG9CQUFvQixFQUNwQixrRkFBa0YsQ0FDbkY7U0FDQSxNQUFNLENBQUMsb0JBQW9CLEVBQUUsbURBQW1ELENBQUM7U0FDakYsTUFBTSxDQUFDLHdCQUF3QixFQUFFLGdEQUFnRCxD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bnQiOlsiLyoqXG4gKiBgZGJ4LXRvb2xzLXR1bm5lbGAgLyBgZGJ4dC10dW5uZWxgIENMSS5cbiAqXG4gKiBXcmFwcyBhIERhdGFicmlja3MgQXBwIHN0YXJ0IGNvbW1hbmQgd2l0aCBhIHB1YmxpYyBwb3J0ciB0dW5uZWwgYW5kIGFuXG4gKiBlbWFpbC1PVFAgYWNjZXNzIGdhdGUuIEV2ZXJ5dGhpbmcgYWZ0ZXIgYC0tYCBpcyB0aGUgUkVBTCBhcHAgc3RhcnQgY29tbWFuZDpcbiAqXG4gKiAgIGRieHQtdHVubmVsIC0tc3ViamVjdCBcIkhlcmUncyB5b3VyIE9UUFwiIC0tYWxsb3cgZGF0YWJyaWNrcy5jb20gLS0gYnVuIHNyYy9zZXJ2ZXIudHNcbiAqXG4gKiBCb290IHNlcXVlbmNlOlxuICogICAxLiBQaWNrIGEgcmFuZG9tIFBSSVZBVEUgcG9ydCBhbmQgc3Bhd24gdGhlIGFwcCBjb21tYW5kIHdpdGhcbiAqICAgICAgYERBVEFCUklDS1NfQVBQX1BPUlRgIHNldCB0byBpdCAoc28gdGhlIGFwcCBiaW5kcyBsb29wYmFjay1wcml2YXRlKS5cbiAqICAgMi4gQm9vdCB0aGUgdGlueSBnYXRlIEFwcEtpdCBhcHAgKG5vIHNlcnZlcik6IGluaXRzIGBDYWNoZU1hbmFnZXJgICsgdGhlXG4gKiAgICAgIGVtYWlsIHRyYW5zcG9ydCwgeWllbGRzIHRoZSBpbi1wcm9jZXNzIGdhdGUgQVBJLlxuICogICAzLiBTdGFydCB0aGUgZ2F0ZSBQUk9YWSBvbiB0aGUgT1JJR0lOQUwgcHVibGljIHBvcnQsIGZvcndhcmRpbmcgdG8gdGhlIGFwcC5cbiAqICAgNC4gSW5zdGFsbCArIHJ1biBwb3J0ciBwb2ludGVkIGF0IHRoZSBwdWJsaWMgcG9ydCAod2hlbiBhIHR1bm5lbCBpc1xuICogICAgICBjb25maWd1cmVkOyBvdGhlcndpc2UgdGhlIHByb3h5IHN0aWxsIGdhdGVzIG5vdGhpbmcgYW5kIGZvcndhcmRzKS5cbiAqXG4gKiBTdXBlcnZpc2lvbjogdGhlIGFwcCBjaGlsZCwgcG9ydHIgY2hpbGQsIGFuZCB0aGlzIHByb2Nlc3MgYXJlIHRpZWQgdG9nZXRoZXIgLVxuICogaWYgQU5ZIGV4aXRzLCBldmVyeXRoaW5nIGNvbWVzIGRvd24gKGNvbmN1cnJlbnRseS1zdHlsZSBga2lsbE90aGVyc2ApLlxuICpcbiAqIE9wdGlvbnMgY29tZSBmcm9tIGZsYWdzIE9SIGVudjsgc2VlIHRoZSBvcHRpb24gZGVmaW5pdGlvbnMgYmVsb3cuXG4gKlxuICogQG1vZHVsZVxuICovXG5cbmltcG9ydCB7IHR5cGUgQ2hpbGRQcm9jZXNzLCBzcGF3biB9IGZyb20gXCJub2RlOmNoaWxkX3Byb2Nlc3NcIjtcbmltcG9ydCB7IGxvZyB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5pbXBvcnQgeyBDb21tYW5kLCBDb21tYW5kZXJFcnJvciB9IGZyb20gXCJjb21tYW5kZXJcIjtcbmltcG9ydCB7IHN0YXJ0R2F0ZUFwcCB9IGZyb20gXCIuL2FwcC50c1wiO1xuaW1wb3J0IHR5cGUgeyBBdXRoR2F0ZUNvbmZpZyB9IGZyb20gXCIuL3BsdWdpbi50c1wiO1xuaW1wb3J0IHsgaW5zdGFsbFBvcnRyLCByZXNvbHZlUG9ydHJDb25maWcsIHN0YXJ0UG9ydHIsIHdyaXRlUG9ydHJDb25maWcgfSBmcm9tIFwiLi9wb3J0ci50c1wiO1xuaW1wb3J0IHsgc3RhcnRQcm94eSB9IGZyb20gXCIuL3Byb3h5LnRzXCI7XG5cbmV4cG9ydCB7IENvbW1hbmRlckVycm9yIH07XG5cbmNvbnN0IGxvZ2dlciA9IGxvZy5sb2dnZXIoXCJ0dW5uZWxcIik7XG5cbi8qKiBBIHJhbmRvbSBlcGhlbWVyYWwgcG9ydCBmb3IgdGhlIGFwcCB0byBiaW5kICh0aGUgcHJveHkgZnJvbnRzIHRoZSBwdWJsaWMgb25lKS4gKi9cbmZ1bmN0aW9uIHJhbmRvbVBvcnQoKTogbnVtYmVyIHtcbiAgcmV0dXJuIDIwMDAwICsgTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogMjAwMDApO1xufVxuXG5pbnRlcmZhY2UgVHVubmVsT3B0cyB7XG4gIHN1YmplY3Q/OiBzdHJpbmc7XG4gIGFsbG93Pzogc3RyaW5nO1xuICBzdWJkb21haW4/OiBzdHJpbmc7XG4gIHB1YmxpY0RvbWFpbj86IHN0cmluZztcbiAgYnJhbmROYW1lPzogc3RyaW5nO1xuICBtZXNzYWdlPzogc3RyaW5nO1xuICBzZXNzaW9uVHRsPzogc3RyaW5nO1xuICBjb2RlVHRsPzogc3RyaW5nO1xuICBpbnNlY3VyZT86IGJvb2xlYW47XG59XG5cbi8qKiBCdWlsZCB0aGUgY29tbWFuZGVyIHByb2dyYW0uIGAtLWAgc2VwYXJhdGVzIGZsYWdzIGZyb20gdGhlIGFwcCBzdGFydCBjb21tYW5kLiAqL1xuZnVuY3Rpb24gcHJvZ3JhbSgpOiBDb21tYW5kIHtcbiAgcmV0dXJuIG5ldyBDb21tYW5kKClcbiAgICAubmFtZShcImRieC10b29scy10dW5uZWxcIilcbiAgICAuZGVzY3JpcHRpb24oXCJGcm9udCBhIERhdGFicmlja3MgQXBwIHdpdGggYSBwdWJsaWMgcG9ydHIgdHVubmVsICsgZW1haWwtT1RQIGdhdGVcIilcbiAgICAub3B0aW9uKFwiLS1zdWJqZWN0IDx0ZXh0PlwiLCBcIlN1YmplY3QgbGluZSBmb3IgdGhlIGNvZGUgZW1haWwgKGVudiBBVVRIX1NVQkpFQ1QpXCIpXG4gICAgLm9wdGlvbihcbiAgICAgIFwiLS1hbGxvdyA8cGF0dGVybnM+XCIsXG4gICAgICBcIkNvbW1hL3NwYWNlLXNlcGFyYXRlZCBhbGxvdy1saXN0OiBkb21haW4gLyBnbG9iIC8gL3JlZ2V4LyAoZW52IEVNQUlMX0FVVEhfQUxMT1cpXCIsXG4gICAgKVxuICAgIC5vcHRpb24oXCItLXN1YmRvbWFpbiA8bmFtZT5cIiwgXCJwb3J0ciBzdWJkb21haW4gKGVsc2UgZGVyaXZlZCBmcm9tIFBVQkxJQ19ET01BSU4pXCIpXG4gICAgLm9wdGlvbihcIi0tcHVibGljLWRvbWFpbiA8aG9zdD5cIiwgXCJwb3J0ciA8c3ViZG9tYWluPi48c2VydmVyPiAoZW52IFBVQkxJQ19ET01BSU4pXCIpXG4gICAgLm9wdGlvbihcIi0tYnJhbmQtbmFtZSA8bmFtZT5cIiwgXCJQcm9kdWN0IG5hbWUgaW4gdGhlIGVtYWlsICsgbG9naW4gY29weSAoZW52IEFVVEhfQlJBTkRfTkFNRSlcIilcbiAgICAub3B0aW9uKFwiLS1tZXNzYWdlIDx0ZXh0PlwiLCBcIkxpbmUgc2hvd24gYWJvdmUgdGhlIGNvZGUgaW4gdGhlIGVtYWlsIChlbnYgQVVUSF9NRVNTQUdFKVwiKVxuICAgIC5vcHRpb24oXCItLXNlc3Npb24tdHRsIDxzZWNvbmRzPlwiLCBcIlNlc3Npb24gbGlmZXRpbWUgKGVudiBBVVRIX1NFU1NJT05fVFRMKVwiKVxuICAgIC5vcHRpb24oXCItLWNvZGUtdHRsIDxzZWNvbmRzPlwiLCBcIk9uZS10aW1lLWNvZGUgbGlmZXRpbWUgKGVudiBBVVRIX0NPREVfVFRMKVwiKVxuICAgIC5vcHRpb24oXG4gICAgICBcIi0taW5zZWN1cmVcIixcbiAgICAgIFwiUnVuIHRoZSB0dW5uZWwgT1BFTiB3aXRoIG5vIGdhdGUgKGVudiBUVU5ORUxfSU5TRUNVUkU9dHJ1ZSkuIE90aGVyd2lzZSB0aGUgQ0xJIGZhaWxzIGZhc3Qgd2hlbiBlbWFpbCBTTVRQIGlzIG5vdCBjb25maWd1cmVkLlwiLFxuICAgIClcbiAgICAuYWxsb3dFeGNlc3NBcmd1bWVudHModHJ1ZSlcbiAgICAuaGVscE9wdGlvbihcIi1oLCAtLWhlbHBcIiwgXCJTaG93IGhlbHBcIik7XG59XG5cbi8qKiBUaWUgYSBjaGlsZCdzIGV4aXQgdG8gZnVsbCB0ZWFyZG93bjogYW55IGV4aXQgYnJpbmdzIHRoZSB3aG9sZSB0dW5uZWwgZG93bi4gKi9cbmZ1bmN0aW9uIHN1cGVydmlzZUV4aXQoY2hpbGRyZW46IENoaWxkUHJvY2Vzc1tdKTogdm9pZCB7XG4gIGxldCBzaHV0dGluZ0Rvd24gPSBmYWxzZTtcbiAgY29uc3Qgc2h1dGRvd24gPSAoY29kZTogbnVtYmVyKTogdm9pZCA9PiB7XG4gICAgaWYgKHNodXR0aW5nRG93bikgcmV0dXJuO1xuICAgIHNodXR0aW5nRG93biA9IHRydWU7XG4gICAgZm9yIChjb25zdCBjaGlsZCBvZiBjaGlsZHJlbikge1xuICAgICAgaWYgKCFjaGlsZC5raWxsZWQpIGNoaWxkLmtpbGwoXCJTSUdURVJNXCIpO1xuICAgIH1cbiAgICAvLyBHaXZlIGNoaWxkcmVuIGEgbW9tZW50LCB0aGVuIGV4aXQgd2l0aCB0aGUgZmlyc3Qgbm9uLXplcm8gY29kZSBzZWVuLlxuICAgIHNldFRpbWVvdXQoKCkgPT4gcHJvY2Vzcy5leGl0KGNvZGUpLCAzMDAwKS51bnJlZigpO1xuICB9O1xuICBmb3IgKGNvbnN0IGNoaWxkIG9mIGNoaWxkcmVuKSB7XG4gICAgY2hpbGQub24oXCJleGl0XCIsIChjb2RlKSA9PiB7XG4gICAgICBsb2dnZXIud2FybihgY2hpbGQgZXhpdGVkICgke2NvZGUgPz8gXCJzaWduYWxcIn0pOyBicmluZ2luZyB0dW5uZWwgZG93bmApO1xuICAgICAgc2h1dGRvd24oY29kZSA/PyAxKTtcbiAgICB9KTtcbiAgfVxuICBmb3IgKGNvbnN0IHNpZyBvZiBbXCJTSUdURVJNXCIsIFwiU0lHSU5UXCIsIFwiU0lHSFVQXCJdIGFzIGNvbnN0KSB7XG4gICAgcHJvY2Vzcy5vbihzaWcsICgpID0+IHNodXRkb3duKDApKTtcbiAgfVxufVxuXG4vKiogUGFyc2UgYXJndiBhbmQgcnVuIHRoZSB0dW5uZWwuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcnVuQ2xpKGFyZ3Y6IHN0cmluZ1tdKTogUHJvbWlzZTx2b2lkPiB7XG4gIC8vIFNwbGl0IGZsYWdzIGZyb20gdGhlIHdyYXBwZWQgY29tbWFuZCBhdCB0aGUgZmlyc3QgYC0tYC5cbiAgY29uc3Qgc2VwID0gYXJndi5pbmRleE9mKFwiLS1cIik7XG4gIGNvbnN0IGZsYWdzID0gc2VwID49IDAgPyBhcmd2LnNsaWNlKDAsIHNlcCkgOiBhcmd2O1xuICBjb25zdCBjb21tYW5kID0gc2VwID49IDAgPyBhcmd2LnNsaWNlKHNlcCArIDEpIDogW107XG5cbiAgY29uc3QgcHJvZyA9IHByb2dyYW0oKTtcbiAgcHJvZy5wYXJzZShmbGFncyk7XG4gIGNvbnN0IG9wdHMgPSBwcm9nLm9wdHM8VHVubmVsT3B0cz4oKTtcblxuICBpZiAoY29tbWFuZC5sZW5ndGggPT09IDApIHtcbiAgICB0aHJvdyBuZXcgQ29tbWFuZGVyRXJyb3IoMSwgXCJ0dW5uZWwubm8tY29tbWFuZFwiLCBcIm5vIHN0YXJ0IGNvbW1hbmQgZ2l2ZW4gYWZ0ZXIgYC0tYFwiKTtcbiAgfVxuXG4gIGNvbnN0IHB1YmxpY1BvcnQgPSBOdW1iZXIocHJvY2Vzcy5lbnYuREFUQUJSSUNLU19BUFBfUE9SVCA/PyA4MDAwKTtcbiAgY29uc3QgYXBwUG9ydCA9IHJhbmRvbVBvcnQoKTtcblxuICBjb25zdCBnYXRlQ29uZmlnOiBBdXRoR2F0ZUNvbmZpZyA9IHtcbiAgICBhbGxvdzogb3B0cy5hbGxvdyxcbiAgICBzdWJqZWN0OiBvcHRzLnN1YmplY3QsXG4gICAgYnJhbmROYW1lOiBvcHRzLmJyYW5kTmFtZSxcbiAgICBtZXNzYWdlOiBvcHRzLm1lc3NhZ2UsXG4gICAgc2Vzc2lvblR0bFNlY29uZHM6IG9wdHMuc2Vzc2lvblR0bCA/IE51bWJlcihvcHRzLnNlc3Npb25UdGwpIDogdW5kZWZpbmVkLFxuICAgIGNvZGVUdGxTZWNvbmRzOiBvcHRzLmNvZGVUdGwgPyBOdW1iZXIob3B0cy5jb2RlVHRsKSA6IHVuZGVmaW5lZCxcbiAgfTtcblxuICAvLyAxLiBTcGF3biB0aGUgd3JhcHBlZCBhcHAgd2l0aCB0aGUgUFJJVkFURSBwb3J0LiBJdCBiaW5kcyBsb29wYmFjazsgb25seSB0aGVcbiAgLy8gICAgcHJveHkgcmVhY2hlcyBpdC5cbiAgbG9nZ2VyLmluZm8oYHNwYXduaW5nIGFwcCBvbiBwcml2YXRlIHBvcnQgJHthcHBQb3J0fTogJHtjb21tYW5kLmpvaW4oXCIgXCIpfWApO1xuICBjb25zdCBbY21kLCAuLi5hcmdzXSA9IGNvbW1hbmQ7XG4gIGNvbnN0IGFwcCA9IHNwYXduKGNtZCEsIGFyZ3MsIHtcbiAgICBlbnY6IHsgLi4ucHJvY2Vzcy5lbnYsIERBVEFCUklDS1NfQVBQX1BPUlQ6IFN0cmluZyhhcHBQb3J0KSwgSE9TVDogXCIxMjcuMC4wLjFcIiB9LFxuICAgIHN0ZGlvOiBcImluaGVyaXRcIixcbiAgfSk7XG5cbiAgLy8gMi4gQm9vdCB0aGUgZ2F0ZSBhcHAgKGNhY2hlICsgZW1haWwgdHJhbnNwb3J0ICsgZ2F0ZSBBUEkpLiBgc3RhcnRHYXRlQXBwYFxuICAvLyAgICBGQUlMUyBGQVNUIHdoZW4gZW1haWwgY2FuJ3Qgc2VuZCBjb2RlcyAobm8gU01UUCkuIEluc2VjdXJlIG1vZGVcbiAgLy8gICAgKGAtLWluc2VjdXJlYCAvIFRVTk5FTF9JTlNFQ1VSRSkgc2tpcHMgdGhlIGdhdGUgYW5kIHJ1bnMgdGhlIHR1bm5lbCBvcGVuLlxuICBjb25zdCBpbnNlY3VyZSA9IG9wdHMuaW5zZWN1cmUgfHwgL14oMXx0cnVlfHllc3xvbikkL2kudGVzdChwcm9jZXNzLmVudi5UVU5ORUxfSU5TRUNVUkUgPz8gXCJcIik7XG4gIGxldCBnYXRlOiBBd2FpdGVkPFJldHVyblR5cGU8dHlwZW9mIHN0YXJ0R2F0ZUFwcD4+IHwgdW5kZWZpbmVkO1xuICBpZiAoaW5zZWN1cmUpIHtcbiAgICBsb2dnZXIud2FybihcImluc2VjdXJlIG1vZGUgLSB0dW5uZWwgcnVucyBPUEVOIHdpdGggbm8gZW1haWwtT1RQIGdhdGVcIik7XG4gIH0gZWxzZSB7XG4gICAgdHJ5IHtcbiAgICAgIGdhdGUgPSBhd2FpdCBzdGFydEdhdGVBcHAoZ2F0ZUNvbmZpZyk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIC8vIEZhaWwgZmFzdDogZG9uJ3Qgc2lsZW50bHkgZXhwb3NlIGFuIHVuZ2F0ZWQgdHVubmVsLiBUaGUgb3BlcmF0b3IgbXVzdCBmaXhcbiAgICAgIC8vIFNNVFAgb3IgZXhwbGljaXRseSBvcHQgaW50byBgLS1pbnNlY3VyZWAuXG4gICAgICBsb2dnZXIuZXJyb3IoXCJjYW5ub3Qgc3RhcnQgdGhlIE9UUCBnYXRlXCIsIHsgZXJyb3I6IChlcnJvciBhcyBFcnJvcikubWVzc2FnZSB9KTtcbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cbiAgfVxuXG4gIC8vIDMuIFN0YXJ0IHRoZSBnYXRlIHByb3h5IG9uIHRoZSBwdWJsaWMgcG9ydCAob3BlbiB3aGVuIGBnYXRlYCBpcyB1bmRlZmluZWQpLlxuICBhd2FpdCBzdGFydFByb3h5KHsgcHVibGljUG9ydCwgYXBwUG9ydCwgZ2F0ZSB9KTtcblxuICAvLyA0LiBJbnN0YWxsICsgcnVuIHBvcnRyIHdoZW4gYSB0dW5uZWwgaXMgY29uZmlndXJlZC5cbiAgY29uc3QgcG9ydHJDb25maWcgPSByZXNvbHZlUG9ydHJDb25maWcoe1xuICAgIHB1YmxpY0RvbWFpbjogb3B0cy5wdWJsaWNEb21haW4sXG4gICAgc3ViZG9tYWluOiBvcHRzLnN1YmRvbWFpbixcbiAgICBwb3J0OiBwdWJsaWNQb3J0LFxuICB9KTtcbiAgY29uc3QgY2hpbGRyZW46IENoaWxkUHJvY2Vzc1tdID0gW2FwcF07XG4gIGlmIChwb3J0ckNvbmZpZykge1xuICAgIGNvbnN0IGVudiA9IGluc3RhbGxQb3J0cigpO1xuICAgIHdyaXRlUG9ydHJDb25maWcocG9ydHJDb25maWcsIGVudik7XG4gICAgY2hpbGRyZW4ucHVzaChzdGFydFBvcnRyKHBvcnRyQ29uZmlnLCBlbnYpKTtcbiAgfSBlbHNlIHtcbiAgICBsb2dnZXIuaW5mbyhcIm5vIFBPUlRSX1RPS0VOL1BVQkxJQ19ET01BSU4gLSBzZXJ2aW5nIHRoZSBnYXRlIHByb3h5IHdpdGhvdXQgYSBwdWJsaWMgdHVubmVsXCIpO1xuICB9XG5cbiAgLy8gQW55IGNoaWxkIGV4aXQgKG9yIGEgc2lnbmFsKSB0ZWFycyB0aGUgd2hvbGUgdGhpbmcgZG93bi5cbiAgc3VwZXJ2aXNlRXhpdChjaGlsZHJlbik7XG59XG4iXX0=
@@ -0,0 +1,50 @@
1
+ /**
2
+ * One-time-code store + session JWT for the email-OTP tunnel gate.
3
+ *
4
+ * The code store is backed by AppKit's `CacheManager` (auto-configured to memory,
5
+ * or Lakebase when the app wires a persistent `CacheStorage`), so TTL EXPIRY and
6
+ * eviction are the cache's job - no hand-rolled Map or timers. A 6-digit code is
7
+ * generated with `crypto.randomInt` and stored as a SHA-256 hash with an attempt
8
+ * counter (never the plaintext, never in the JWT); `verify` is constant-time on
9
+ * the hash, and the entry is deleted on success or once attempts are exhausted.
10
+ *
11
+ * The session JWT is a short-lived HS256 token (via `jose`) carrying only the
12
+ * email. Its signing key comes from `AUTH_JWT_SECRET`; when unset the gate FAILS
13
+ * OPEN with an ephemeral per-process key (sessions reset on restart) rather than
14
+ * refusing service - a Databricks App is already access-limited, so an unset
15
+ * secret degrades to "sessions don't survive restarts", not "nobody can log in".
16
+ *
17
+ * @module
18
+ */
19
+ /** Result of {@link CodeStore.verify}. */
20
+ export type VerifyOutcome = "ok" | "invalid" | "expired" | "too-many-attempts";
21
+ /**
22
+ * Pending one-time codes, stored in AppKit's cache keyed by lowercased email.
23
+ * Expiry is the cache's TTL (no manual clock); a miss means expired-or-never.
24
+ */
25
+ export declare class CodeStore {
26
+ private readonly ttlSeconds;
27
+ private readonly maxAttempts;
28
+ constructor(ttlSeconds: number, maxAttempts: number);
29
+ private cache;
30
+ private key;
31
+ /**
32
+ * Generate, store (hashed, with the cache TTL), and RETURN a fresh 6-digit
33
+ * code. The caller emails the returned plaintext; only the hash is retained.
34
+ * Replaces any pending code for the address.
35
+ */
36
+ issue(email: string): Promise<string>;
37
+ /**
38
+ * Check `code` for `email`. A cache miss is `expired` (TTL elapsed or never
39
+ * issued). Deletes the entry on success or when attempts are exhausted, so a
40
+ * code is single-use and can't be brute-forced past the cap. An attempt
41
+ * increments the stored counter (re-persisted with a fresh TTL window).
42
+ */
43
+ verify(email: string, code: string): Promise<VerifyOutcome>;
44
+ }
45
+ /** Reset the memoized key (tests, or after changing the env in-process). */
46
+ export declare function resetSigningKey(): void;
47
+ /** Mint a short-lived session JWT for `email`, expiring in `ttlSeconds`. */
48
+ export declare function signSession(email: string, ttlSeconds: number): Promise<string>;
49
+ /** Validate a session JWT, returning the email it was minted for, or `undefined`. */
50
+ export declare function verifySession(token: string | undefined): Promise<string | undefined>;