From 81f192619d7cb389faa9163d4bc89021340fa94a Mon Sep 17 00:00:00 2001 From: Johannes Herold Date: Fri, 1 May 2026 20:36:48 +0200 Subject: [PATCH 1/2] fix(cli): surface auth URL so headless logins can complete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `superset auth login` opens the authorization URL via xdg-open (Linux), open (macOS), or start (Windows). On headless Linux hosts — the typical target of the CLI release — xdg-open is often missing, and exec() silently swallows the failure. The user sees only "Waiting for browser authorization..." with no URL anywhere; the login hangs forever. Make the URL discoverable via a new `onAuthUrl` callback on `login()`. The callback fires just before the browser launch, letting the command layer render through clack — printing a fresh `p.log.info` with the URL which works on TTY (interleaved with the spinner) and headless alike. Browser auto-launch behaviour is unchanged for GUI users. Verified: an SSH user can now `ssh -L 51789:localhost:51789 `, read the printed URL, paste it into a local browser, and complete the OAuth loopback callback through the SSH tunnel. --- .../cli/src/commands/auth/login/command.ts | 9 ++++++++- packages/cli/src/lib/auth.ts | 19 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/auth/login/command.ts b/packages/cli/src/commands/auth/login/command.ts index 6619ae09e18..4cbfb6bbf31 100644 --- a/packages/cli/src/commands/auth/login/command.ts +++ b/packages/cli/src/commands/auth/login/command.ts @@ -22,7 +22,14 @@ export default command({ spinner?.start("Waiting for browser authorization..."); if (!spinner) p.log.info("Waiting for browser authorization…"); - const result = await login(opts.signal); + const result = await login({ + signal: opts.signal, + onAuthUrl: (url) => { + p.log.info( + `If your browser doesn't open automatically, paste this URL:\n${url}`, + ); + }, + }); config.auth = { accessToken: result.accessToken, diff --git a/packages/cli/src/lib/auth.ts b/packages/cli/src/lib/auth.ts index 5e111592aae..d53efa10178 100644 --- a/packages/cli/src/lib/auth.ts +++ b/packages/cli/src/lib/auth.ts @@ -131,7 +131,18 @@ export function getWebUrl(): string { return env.SUPERSET_API_URL.replace("api.superset.sh", "app.superset.sh"); } -export async function login(signal: AbortSignal): Promise { +export interface LoginOptions { + signal: AbortSignal; + /** + * Called once with the authorization URL just before the browser is + * launched. Useful on headless hosts where `xdg-open` isn't available — + * the caller can surface the URL so the user can paste it into a + * browser on a different machine. + */ + onAuthUrl?: (url: string) => void; +} + +export async function login(options: LoginOptions): Promise { const apiUrl = env.SUPERSET_API_URL; const webUrl = getWebUrl(); @@ -143,13 +154,15 @@ export async function login(signal: AbortSignal): Promise { authorizeUrl.searchParams.set("redirect_uri", redirectUri); authorizeUrl.searchParams.set("state", state); - await openBrowser(authorizeUrl.toString()); + const url = authorizeUrl.toString(); + options.onAuthUrl?.(url); + await openBrowser(url); const code = await waitForCallback({ server, port, expectedState: state, - signal, + signal: options.signal, timeoutMs: 5 * 60 * 1000, }); From e798930028a4c9f172574c862c3c5db27024392f Mon Sep 17 00:00:00 2001 From: Johannes Herold Date: Sat, 2 May 2026 07:50:04 +0200 Subject: [PATCH 2/2] fix(cli): close loopback server if onAuthUrl callback throws MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a caller-supplied `onAuthUrl` callback throws, the loopback HTTP server bound earlier by `bindLoopbackServer` would never be closed — leaking the port and starving subsequent login attempts (loopback candidates are 51789 + 51790; both being held would surface as "All loopback ports in use"). Wrap the callback in try/catch and close the server before re-throwing. The error still propagates so the caller knows their callback failed. Reported by @cubic-dev-ai (P2) on #3950. --- packages/cli/src/lib/auth.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/lib/auth.ts b/packages/cli/src/lib/auth.ts index d53efa10178..18df944a048 100644 --- a/packages/cli/src/lib/auth.ts +++ b/packages/cli/src/lib/auth.ts @@ -155,7 +155,13 @@ export async function login(options: LoginOptions): Promise { authorizeUrl.searchParams.set("state", state); const url = authorizeUrl.toString(); - options.onAuthUrl?.(url); + try { + options.onAuthUrl?.(url); + } catch (err) { + // Don't leak the bound loopback server if the callback throws. + server.close(); + throw err; + } await openBrowser(url); const code = await waitForCallback({