fix(cli): surface tunnel bootstrap connect URL for --try-cf-tunnel (#561)

The CLI tunnel startup generated a bootstrap token but discarded it,
then built a URL with ?token=<uiPassword> which the tunnel auth system
ignores. Remote users always saw 'Tunnel access required' with no way
to authenticate.

Capture the bootstrap token, build the /connect?t=... URL, and pass it
through onTunnelReady so the CLI prints the correct one-time connect
link (and QR code).
This commit is contained in:
plfavreau
2026-03-04 14:54:29 +02:00
committed by GitHub
parent 2377263a8e
commit 0b3d0378d4
2 changed files with 14 additions and 8 deletions
+5 -3
View File
@@ -636,10 +636,12 @@ const commands = {
exitOnShutdown: true, exitOnShutdown: true,
uiPassword: typeof effectiveUiPassword === 'string' ? effectiveUiPassword : null, uiPassword: typeof effectiveUiPassword === 'string' ? effectiveUiPassword : null,
tryCfTunnel: options.tryCfTunnel, tryCfTunnel: options.tryCfTunnel,
onTunnelReady: async (url) => { onTunnelReady: async (url, connectUrl) => {
const displayUrl = buildTunnelUrl(url, effectiveUiPassword, options.tunnelPasswordUrl); const displayUrl = connectUrl || buildTunnelUrl(url, effectiveUiPassword, options.tunnelPasswordUrl);
console.log(`\n🌐 Tunnel URL: \x1b[36m${displayUrl}\x1b[0m\n`); console.log(`\n🌐 Tunnel URL: \x1b[36m${displayUrl}\x1b[0m\n`);
if (options.tunnelPasswordUrl && effectiveUiPassword) { if (connectUrl) {
console.log('🔑 One-time connect link (expires after first use)\n');
} else if (options.tunnelPasswordUrl && effectiveUiPassword) {
console.log('🔑 Password is embedded in URL for auto-login\n'); console.log('🔑 Password is embedded in URL for auto-login\n');
} }
if (options.tunnelQr) { if (options.tunnelQr) {
+9 -5
View File
@@ -13371,12 +13371,16 @@ async function main(options = {}) {
const bootstrapTtlMs = settings?.tunnelBootstrapTtlMs === null const bootstrapTtlMs = settings?.tunnelBootstrapTtlMs === null
? null ? null
: normalizeTunnelBootstrapTtlMs(settings?.tunnelBootstrapTtlMs); : normalizeTunnelBootstrapTtlMs(settings?.tunnelBootstrapTtlMs);
tunnelAuthController.issueBootstrapToken({ ttlMs: bootstrapTtlMs }); const bootstrapToken = tunnelAuthController.issueBootstrapToken({ ttlMs: bootstrapTtlMs });
} const connectUrl = `${tunnelUrl.replace(/\/$/, '')}/connect?t=${encodeURIComponent(bootstrapToken.token)}`;
if (onTunnelReady) { if (onTunnelReady) {
if (tunnelUrl) { onTunnelReady(tunnelUrl, connectUrl);
onTunnelReady(tunnelUrl); } else {
console.log(`\n🌐 Tunnel URL: ${connectUrl}`);
console.log('🔑 One-time connect link (expires after first use)\n');
} }
} else if (onTunnelReady) {
onTunnelReady(tunnelUrl, null);
} }
} catch (error) { } catch (error) {
console.error(`Failed to start Cloudflare tunnel: ${error.message}`); console.error(`Failed to start Cloudflare tunnel: ${error.message}`);