@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 +1 -0
- package/bin/dbx-tools-tunnel.ts +13 -0
- package/index.ts +20 -0
- package/lib/bin/dbx-tools-tunnel.d.ts +2 -0
- package/lib/bin/dbx-tools-tunnel.js +14 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +16 -0
- package/lib/src/allowlist.d.ts +27 -0
- package/lib/src/allowlist.js +82 -0
- package/lib/src/app.d.ts +31 -0
- package/lib/src/app.js +71 -0
- package/lib/src/cli.d.ts +28 -0
- package/lib/src/cli.js +147 -0
- package/lib/src/otp.d.ts +50 -0
- package/lib/src/otp.js +134 -0
- package/lib/src/plugin.d.ts +99 -0
- package/lib/src/plugin.js +123 -0
- package/lib/src/portr.d.ts +39 -0
- package/lib/src/portr.js +90 -0
- package/lib/src/proxy.d.ts +40 -0
- package/lib/src/proxy.js +204 -0
- package/lib/src/rate-limit.d.ts +35 -0
- package/lib/src/rate-limit.js +53 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +117 -0
- package/src/allowlist.ts +88 -0
- package/src/app.ts +82 -0
- package/src/cli.ts +176 -0
- package/src/otp.ts +150 -0
- package/src/plugin.ts +200 -0
- package/src/portr.ts +110 -0
- package/src/proxy.ts +243 -0
- package/src/rate-limit.ts +59 -0
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,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
|
package/lib/src/app.d.ts
ADDED
|
@@ -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=
|
package/lib/src/cli.d.ts
ADDED
|
@@ -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=
|
package/lib/src/otp.d.ts
ADDED
|
@@ -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>;
|