@dbx-tools/cli-tunnel 0.6.59 → 0.6.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -364
- package/index.ts +2 -19
- package/lib/index.d.ts +2 -19
- package/lib/index.js +2 -15
- package/lib/src/app.d.ts +12 -123
- package/lib/src/app.js +22 -250
- package/lib/src/cli.d.ts +19 -22
- package/lib/src/cli.js +143 -128
- package/lib/src/options.d.ts +46 -0
- package/lib/src/options.js +51 -0
- package/lib/src/proxy.d.ts +27 -37
- package/lib/src/proxy.js +115 -220
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -81
- package/src/app.ts +20 -282
- package/src/cli.ts +152 -162
- package/src/options.ts +85 -0
- package/src/proxy.ts +139 -261
- package/bin/dbx-tools-tunnel.ts +0 -13
- package/lib/bin/dbx-tools-tunnel.d.ts +0 -2
- package/lib/bin/dbx-tools-tunnel.js +0 -14
- package/lib/src/allowlist.d.ts +0 -32
- package/lib/src/allowlist.js +0 -57
- package/lib/src/env.d.ts +0 -57
- package/lib/src/env.js +0 -60
- package/lib/src/headers.d.ts +0 -108
- package/lib/src/headers.js +0 -140
- package/lib/src/otp.d.ts +0 -49
- package/lib/src/otp.js +0 -124
- package/lib/src/plugin.d.ts +0 -147
- package/lib/src/plugin.js +0 -138
- package/lib/src/portr.d.ts +0 -40
- package/lib/src/portr.js +0 -93
- package/lib/src/rate-limit.d.ts +0 -35
- package/lib/src/rate-limit.js +0 -53
- package/lib/src/signing-key.d.ts +0 -86
- package/lib/src/signing-key.js +0 -170
- package/src/allowlist.ts +0 -60
- package/src/env.ts +0 -72
- package/src/headers.ts +0 -155
- package/src/otp.ts +0 -137
- package/src/plugin.ts +0 -269
- package/src/portr.ts +0 -113
- package/src/rate-limit.ts +0 -59
- package/src/signing-key.ts +0 -201
package/src/cli.ts
CHANGED
|
@@ -1,201 +1,191 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `dbx
|
|
2
|
+
* `dbx tunnel` - front a command with a public portr tunnel and an email-OTP gate.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* This is the WRAPPER path, and it exists for one case: a project that does not
|
|
5
|
+
* use `@dbx-tools/appkit`'s `createApp`, and therefore cannot register
|
|
6
|
+
* `tunnelInterceptor()` + the `authGate` plugin in-process. An AppKit app should
|
|
7
|
+
* still take the plugin path - one process, no proxy hop, no duplicated header
|
|
8
|
+
* handling.
|
|
6
9
|
*
|
|
7
|
-
*
|
|
10
|
+
* The wrapper claims the PUBLIC port (`DATABRICKS_APP_PORT`, the port the
|
|
11
|
+
* platform and portr route to), moves the wrapped app to a private one, and
|
|
12
|
+
* reverse-proxies between them so the gate sits in front of traffic it would
|
|
13
|
+
* otherwise have no way to intercept. Everything else is delegated: the gate
|
|
14
|
+
* config comes from `plugin.resolveAuthGateConfig`, the portr lifecycle from
|
|
15
|
+
* `portr.*` - both the same functions the in-process path uses.
|
|
8
16
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* platform-neutral and honours whatever port it finds there.
|
|
14
|
-
* 2. Boot the tiny gate AppKit app (no server): inits `CacheManager` + the
|
|
15
|
-
* email transport, yields the in-process gate API.
|
|
16
|
-
* 3. Start the gate PROXY on the ORIGINAL public port, forwarding to the app.
|
|
17
|
-
* 4. Install + run portr pointed at the public port (when a tunnel is
|
|
18
|
-
* configured; otherwise the proxy still gates nothing and forwards).
|
|
19
|
-
*
|
|
20
|
-
* Supervision: the app child, portr child, and this process are tied together -
|
|
21
|
-
* if ANY exits, everything comes down (concurrently-style `killOthers`).
|
|
22
|
-
*
|
|
23
|
-
* Options come from flags OR env; see the option definitions below.
|
|
17
|
+
* Ships no bin. `@dbx-tools/cli` mounts `buildProgram()` as `dbx tunnel` lazily,
|
|
18
|
+
* so `dbx dev` pays for none of this, and `--insecure` / `status` / `install`
|
|
19
|
+
* never load AppKit or the SMTP stack either (the gate app is behind a dynamic
|
|
20
|
+
* import).
|
|
24
21
|
*
|
|
25
22
|
* @module
|
|
26
23
|
*/
|
|
27
24
|
|
|
28
25
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
29
|
-
import {
|
|
26
|
+
import { createServer } from "node:net";
|
|
27
|
+
import { log } from "@dbx-tools/shared-core";
|
|
28
|
+
import { portr } from "@dbx-tools/tunnel";
|
|
30
29
|
import { Command, CommanderError } from "commander";
|
|
31
|
-
import {
|
|
32
|
-
import { FORWARD_HEADERS_ENV, INSECURE_ENV } from "./env.ts";
|
|
33
|
-
import type { AuthGateConfig } from "./plugin.ts";
|
|
34
|
-
import { installPortr, resolvePortrConfig, startPortr, writePortrConfig } from "./portr.ts";
|
|
30
|
+
import { resolveTunnelOptions, type TunnelOptions } from "./options.ts";
|
|
35
31
|
import { startProxy } from "./proxy.ts";
|
|
36
32
|
|
|
37
33
|
export { CommanderError };
|
|
38
34
|
|
|
39
35
|
const logger = log.logger("tunnel");
|
|
40
36
|
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
return 20000 + Math.floor(Math.random() * 20000);
|
|
44
|
-
}
|
|
37
|
+
/** How long a child gets to exit on SIGTERM before the wrapper leaves anyway. */
|
|
38
|
+
const SHUTDOWN_GRACE_MS = 3_000;
|
|
45
39
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
40
|
+
/**
|
|
41
|
+
* A free loopback port, from the OS rather than a random guess: binding `0` and
|
|
42
|
+
* reading back what was assigned is the only way to know the port is actually
|
|
43
|
+
* available, so two tunnels can run side by side without colliding.
|
|
44
|
+
*/
|
|
45
|
+
function freePort(): Promise<number> {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const probe = createServer();
|
|
48
|
+
probe.on("error", reject);
|
|
49
|
+
probe.listen(0, "127.0.0.1", () => {
|
|
50
|
+
const address = probe.address();
|
|
51
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
52
|
+
probe.close(() => (port > 0 ? resolve(port) : reject(new Error("no free port"))));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
-
.option("--
|
|
71
|
-
.option("--
|
|
72
|
-
.option(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
.option("--
|
|
77
|
-
.option("--session-
|
|
78
|
-
.option("--
|
|
79
|
-
.option(
|
|
80
|
-
"--session-cutoff <when>",
|
|
81
|
-
"Invalidate sessions issued before this point, signing everyone out: a date, ISO instant, epoch seconds, or relative duration like -30d (env TUNNEL_AUTH_SESSION_CUTOFF)",
|
|
82
|
-
)
|
|
83
|
-
.option(
|
|
84
|
-
"--forward-headers <patterns>",
|
|
85
|
-
"Extra x- request headers tunnel traffic may forward: literal / glob / /regex/ (env TUNNEL_FORWARD_HEADERS)",
|
|
86
|
-
)
|
|
87
|
-
.option(
|
|
88
|
-
"--insecure",
|
|
89
|
-
"Run the tunnel OPEN with no gate (env TUNNEL_INSECURE=true). Otherwise the CLI fails fast when email SMTP is not configured.",
|
|
90
|
-
)
|
|
91
|
-
.allowExcessArguments(true)
|
|
92
|
-
.helpOption("-h, --help", "Show help");
|
|
57
|
+
/**
|
|
58
|
+
* Every gate/portr flag. Declared on the root command AND on `run`/`status` so
|
|
59
|
+
* `dbx tunnel --allow x -- cmd` and `dbx tunnel run --allow x -- cmd` behave
|
|
60
|
+
* identically - commander does not inherit options downward.
|
|
61
|
+
*/
|
|
62
|
+
function addOptions(command: Command): Command {
|
|
63
|
+
return command
|
|
64
|
+
.option("--public-domain <host>", "portr public domain (<subdomain>.<server>)")
|
|
65
|
+
.option("--subdomain <name>", "portr subdomain (else derived from the public domain)")
|
|
66
|
+
.option("--port <port>", "public port the wrapper listens on")
|
|
67
|
+
.option("--app-port <port>", "private port the wrapped app is told to bind")
|
|
68
|
+
.option("--allow <patterns...>", "email allow-list (domain / glob / /regex/)")
|
|
69
|
+
.option("--subject <text>", "verification email subject")
|
|
70
|
+
.option("--brand-name <name>", "verification email brand name")
|
|
71
|
+
.option("--message <text>", "verification email message")
|
|
72
|
+
.option("--session-ttl <seconds>", "session lifetime")
|
|
73
|
+
.option("--code-ttl <seconds>", "one-time-code lifetime")
|
|
74
|
+
.option("--session-cutoff <when>", "invalidate every session issued before this")
|
|
75
|
+
.option("--forward-headers <patterns...>", "extra x- headers tunnel traffic may forward")
|
|
76
|
+
.option("--insecure", "run open, with no gate");
|
|
93
77
|
}
|
|
94
78
|
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Tie the wrapper's lifetime to its children's, in both directions: a child that
|
|
81
|
+
* exits takes the wrapper down with its code, and a signal to the wrapper is
|
|
82
|
+
* forwarded before it leaves. Without this a crashed app leaves a portr tunnel
|
|
83
|
+
* serving a dead port, which looks like a hang rather than a failure.
|
|
84
|
+
*/
|
|
85
|
+
function supervise(children: readonly ChildProcess[]): void {
|
|
86
|
+
let stopping = false;
|
|
87
|
+
const stop = (code: number): void => {
|
|
88
|
+
if (stopping) return;
|
|
89
|
+
stopping = true;
|
|
90
|
+
for (const child of children) if (!child.killed) child.kill("SIGTERM");
|
|
91
|
+
setTimeout(() => process.exit(code), SHUTDOWN_GRACE_MS).unref();
|
|
106
92
|
};
|
|
107
|
-
for (const child of children)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
shutdown(code ?? 1);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"] as const) {
|
|
114
|
-
process.on(sig, () => shutdown(0));
|
|
93
|
+
for (const child of children) child.on("exit", (code) => stop(code ?? 1));
|
|
94
|
+
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"] as const) {
|
|
95
|
+
process.on(signal, () => stop(0));
|
|
115
96
|
}
|
|
116
97
|
}
|
|
117
98
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const flags = sep >= 0 ? argv.slice(0, sep) : argv;
|
|
123
|
-
const command = sep >= 0 ? argv.slice(sep + 1) : [];
|
|
124
|
-
|
|
125
|
-
const prog = program();
|
|
126
|
-
prog.parse(flags);
|
|
127
|
-
const opts = prog.opts<TunnelOpts>();
|
|
128
|
-
|
|
129
|
-
if (command.length === 0) {
|
|
130
|
-
throw new CommanderError(1, "tunnel.no-command", "no start command given after `--`");
|
|
99
|
+
async function run(raw: TunnelOptions, command: readonly string[]): Promise<void> {
|
|
100
|
+
const [executable, ...args] = command;
|
|
101
|
+
if (!executable) {
|
|
102
|
+
throw new CommanderError(1, "tunnel.no-command", "no command given - pass it after `--`");
|
|
131
103
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// 1. Spawn the wrapped app with the PRIVATE port. It binds loopback; only the
|
|
147
|
-
// proxy reaches it.
|
|
148
|
-
logger.info(`spawning app on private port ${appPort}: ${command.join(" ")}`);
|
|
149
|
-
const [cmd, ...args] = command;
|
|
150
|
-
const app = spawn(cmd!, args, {
|
|
151
|
-
env: { ...process.env, DATABRICKS_APP_PORT: String(appPort), HOST: "127.0.0.1" },
|
|
104
|
+
const resolved = resolveTunnelOptions(raw);
|
|
105
|
+
|
|
106
|
+
// The wrapped app must not be reachable from outside: it binds loopback on a
|
|
107
|
+
// private port, and the wrapper is the only thing that talks to it. An
|
|
108
|
+
// unresolved `--app-port` becomes a free ephemeral port rather than a fixed
|
|
109
|
+
// guess, so two tunnels can run side by side.
|
|
110
|
+
const appPort = resolved.appPort ?? (await freePort());
|
|
111
|
+
const app = spawn(executable, args, {
|
|
112
|
+
env: {
|
|
113
|
+
...process.env,
|
|
114
|
+
DATABRICKS_APP_PORT: String(appPort),
|
|
115
|
+
PORT: String(appPort),
|
|
116
|
+
HOST: "127.0.0.1",
|
|
117
|
+
},
|
|
152
118
|
stdio: "inherit",
|
|
153
119
|
});
|
|
120
|
+
const children: ChildProcess[] = [app];
|
|
154
121
|
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (
|
|
161
|
-
logger.warn("insecure mode - tunnel runs OPEN with no email-OTP gate");
|
|
162
|
-
} else {
|
|
163
|
-
try {
|
|
164
|
-
gate = await startGateApp(gateConfig);
|
|
165
|
-
} catch (error) {
|
|
166
|
-
// Fail fast: don't silently expose an ungated tunnel. The operator must fix
|
|
167
|
-
// SMTP or explicitly opt into `--insecure`.
|
|
168
|
-
logger.error("cannot start the OTP gate", { error: (error as Error).message });
|
|
169
|
-
throw error;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
122
|
+
// Dynamic import: the gate is the only thing here that needs AppKit + SMTP, so
|
|
123
|
+
// an `--insecure` run never loads either.
|
|
124
|
+
const gate = resolved.gate.insecure
|
|
125
|
+
? undefined
|
|
126
|
+
: await (await import("./app.ts")).startGateApp(resolved.gateConfig);
|
|
127
|
+
if (!gate) logger.warn("running OPEN - no gate is in front of this tunnel");
|
|
172
128
|
|
|
173
|
-
// 3. Start the gate proxy on the public port (open when `gate` is undefined).
|
|
174
|
-
// `forwardHeaders` only ADDS to the built-in allow-list; see `./headers.ts`.
|
|
175
129
|
await startProxy({
|
|
176
|
-
publicPort,
|
|
130
|
+
publicPort: resolved.publicPort,
|
|
177
131
|
appPort,
|
|
178
132
|
gate,
|
|
179
|
-
forwardHeaders:
|
|
133
|
+
forwardHeaders: resolved.gate.forwardHeaders,
|
|
180
134
|
});
|
|
181
135
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
port: publicPort,
|
|
187
|
-
});
|
|
188
|
-
const children: ChildProcess[] = [app];
|
|
189
|
-
if (portrConfig) {
|
|
190
|
-
const portrEnv = installPortr();
|
|
191
|
-
writePortrConfig(portrConfig, portrEnv);
|
|
192
|
-
children.push(startPortr(portrConfig, portrEnv));
|
|
136
|
+
if (resolved.portr) {
|
|
137
|
+
const portrEnv = await portr.installPortr();
|
|
138
|
+
await portr.writePortrConfig(resolved.portr, portrEnv);
|
|
139
|
+
children.push(await portr.startPortr(resolved.portr, portrEnv));
|
|
193
140
|
} else {
|
|
194
|
-
logger.info(
|
|
195
|
-
|
|
196
|
-
);
|
|
141
|
+
logger.info("no PORTR_TOKEN / TUNNEL_PUBLIC_DOMAIN - serving locally only", {
|
|
142
|
+
publicPort: resolved.publicPort,
|
|
143
|
+
});
|
|
197
144
|
}
|
|
145
|
+
supervise(children);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The `dbx tunnel` program. No side effects until parsed. */
|
|
149
|
+
export function buildProgram(name = "dbx tunnel"): Command {
|
|
150
|
+
const program = addOptions(
|
|
151
|
+
new Command()
|
|
152
|
+
.name(name)
|
|
153
|
+
.description("Front a command with a portr tunnel and an email-OTP gate"),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// `run` is the DEFAULT action as well as a named subcommand, preserving the old
|
|
157
|
+
// wrapper's ergonomics (`dbx tunnel --allow x -- bun src/server.ts`) while
|
|
158
|
+
// leaving somewhere for `status` and `install` to live.
|
|
159
|
+
program
|
|
160
|
+
.argument("[command...]", "the command to wrap, after `--`")
|
|
161
|
+
.action(async (command: string[], _options: TunnelOptions, cmd: Command) => {
|
|
162
|
+
await run(cmd.opts<TunnelOptions>(), command);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
addOptions(
|
|
166
|
+
program.command("run").description("Wrap a command (the default action)"),
|
|
167
|
+
)
|
|
168
|
+
.argument("<command...>", "the command to wrap, after `--`")
|
|
169
|
+
.action(async (command: string[], _options: TunnelOptions, cmd: Command) => {
|
|
170
|
+
await run(cmd.opts<TunnelOptions>(), command);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Worth its own command: the most common failure is a tunnel that silently
|
|
174
|
+
// does nothing because no token or domain resolved, and this prints exactly
|
|
175
|
+
// what would happen without starting anything.
|
|
176
|
+
addOptions(
|
|
177
|
+
program.command("status").description("Resolve the configuration and print it"),
|
|
178
|
+
).action((_options: TunnelOptions, cmd: Command) => {
|
|
179
|
+
const resolved = resolveTunnelOptions(cmd.opts<TunnelOptions>());
|
|
180
|
+
process.stdout.write(`${JSON.stringify(resolved, null, 2)}\n`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
program
|
|
184
|
+
.command("install")
|
|
185
|
+
.description("Install the portr binary and exit")
|
|
186
|
+
.action(async () => {
|
|
187
|
+
await portr.installPortr();
|
|
188
|
+
});
|
|
198
189
|
|
|
199
|
-
|
|
200
|
-
superviseExit(children);
|
|
190
|
+
return program;
|
|
201
191
|
}
|
package/src/options.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flag -> config -> default resolution for `dbx tunnel`.
|
|
3
|
+
*
|
|
4
|
+
* The point of this module is that it does almost NOTHING itself: every gate
|
|
5
|
+
* setting is handed straight to `@dbx-tools/tunnel`'s own
|
|
6
|
+
* `plugin.resolveAuthGateConfig`, which is the same function the in-process
|
|
7
|
+
* plugin path calls. The CLI therefore cannot drift from the plugin on a default,
|
|
8
|
+
* an env name, or a coercion rule - a flag is just a value passed where the
|
|
9
|
+
* plugin's `config` object would go, and `config.*` fills the rest in from the
|
|
10
|
+
* environment, a `.env` file, or `databricks.yml`.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { config } from "@dbx-tools/core";
|
|
16
|
+
import { object } from "@dbx-tools/shared-core";
|
|
17
|
+
import { type AuthGateConfig, plugin, portr } from "@dbx-tools/tunnel";
|
|
18
|
+
|
|
19
|
+
/** Raw commander flag values (every numeric flag arrives as a string). */
|
|
20
|
+
export interface TunnelOptions {
|
|
21
|
+
publicDomain?: string;
|
|
22
|
+
subdomain?: string;
|
|
23
|
+
port?: string | number;
|
|
24
|
+
appPort?: string | number;
|
|
25
|
+
allow?: string[];
|
|
26
|
+
subject?: string;
|
|
27
|
+
brandName?: string;
|
|
28
|
+
message?: string;
|
|
29
|
+
sessionTtl?: string | number;
|
|
30
|
+
codeTtl?: string | number;
|
|
31
|
+
sessionCutoff?: string;
|
|
32
|
+
forwardHeaders?: string[];
|
|
33
|
+
insecure?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ResolvedTunnelOptions {
|
|
37
|
+
/** The port the wrapper itself listens on - what portr forwards to. */
|
|
38
|
+
publicPort: number;
|
|
39
|
+
/** The private port the wrapped app is told to bind. Unset means "pick one". */
|
|
40
|
+
appPort?: number;
|
|
41
|
+
/**
|
|
42
|
+
* The gate config as the `authGate` PLUGIN takes it - flags only, nothing
|
|
43
|
+
* resolved. Passed straight to the plugin so it applies its own fallbacks
|
|
44
|
+
* exactly once, in the one place that owns them.
|
|
45
|
+
*/
|
|
46
|
+
gateConfig: AuthGateConfig;
|
|
47
|
+
/** The same config after the plugin's resolution, for `status` and for routing. */
|
|
48
|
+
gate: plugin.ResolvedAuthGateConfig;
|
|
49
|
+
portr: ReturnType<typeof portr.resolvePortrConfig>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveTunnelOptions(options: TunnelOptions): ResolvedTunnelOptions {
|
|
53
|
+
// The Databricks Apps runtime contract: the platform routes to
|
|
54
|
+
// DATABRICKS_APP_PORT, so the WRAPPER claims it and the wrapped app is moved
|
|
55
|
+
// to a private one.
|
|
56
|
+
const publicPort = config.positiveInt(options.port, "DATABRICKS_APP_PORT", 8000);
|
|
57
|
+
const appPort = config.positiveInt(options.appPort, "APP_PORT", 0, { prefix: "TUNNEL" });
|
|
58
|
+
const gateConfig: AuthGateConfig = {
|
|
59
|
+
allow: options.allow,
|
|
60
|
+
subject: options.subject,
|
|
61
|
+
brandName: options.brandName,
|
|
62
|
+
message: options.message,
|
|
63
|
+
// Coerced, not resolved: the plugin owns the env name and the default for
|
|
64
|
+
// these, so the flag is passed through as the `config` value it expects and
|
|
65
|
+
// only needs a string -> number nudge on the way.
|
|
66
|
+
sessionTtlSeconds: object.toNumber(options.sessionTtl),
|
|
67
|
+
codeTtlSeconds: object.toNumber(options.codeTtl),
|
|
68
|
+
sessionCutoff: options.sessionCutoff,
|
|
69
|
+
forwardHeaders: options.forwardHeaders,
|
|
70
|
+
insecure: options.insecure,
|
|
71
|
+
publicDomain: options.publicDomain,
|
|
72
|
+
};
|
|
73
|
+
const gate = plugin.resolveAuthGateConfig(gateConfig);
|
|
74
|
+
return {
|
|
75
|
+
publicPort,
|
|
76
|
+
...(appPort > 0 ? { appPort } : {}),
|
|
77
|
+
gateConfig,
|
|
78
|
+
gate,
|
|
79
|
+
portr: portr.resolvePortrConfig({
|
|
80
|
+
publicDomain: gate.publicDomain,
|
|
81
|
+
subdomain: options.subdomain,
|
|
82
|
+
port: publicPort,
|
|
83
|
+
}),
|
|
84
|
+
};
|
|
85
|
+
}
|