diff --git a/packages/cli/src/digitalocean/digitalocean.ts b/packages/cli/src/digitalocean/digitalocean.ts index 2c2ddd32..c23f3e3e 100644 --- a/packages/cli/src/digitalocean/digitalocean.ts +++ b/packages/cli/src/digitalocean/digitalocean.ts @@ -330,6 +330,7 @@ async function tryDoOAuth(): Promise { const csrfState = generateCsrfState(); let oauthCode: string | null = null; + let oauthDenied = false; let server: ReturnType | null = null; // Try ports in range @@ -347,6 +348,7 @@ async function tryDoOAuth(): Promise { if (error) { const desc = url.searchParams.get("error_description") || error; logError(`DigitalOcean authorization denied: ${desc}`); + oauthDenied = true; return new Response(OAUTH_ERROR_HTML, { status: 403, headers: { @@ -434,12 +436,19 @@ async function tryDoOAuth(): Promise { // Wait up to 120 seconds logStep("Waiting for authorization in browser (timeout: 120s)..."); const deadline = Date.now() + 120_000; - while (!oauthCode && Date.now() < deadline) { + while (!oauthCode && !oauthDenied && Date.now() < deadline) { await sleep(500); } server.stop(true); + if (oauthDenied) { + logError("OAuth authorization was denied by the user"); + logError("Alternative: Use a manual API token instead"); + logError(" export DO_API_TOKEN=dop_v1_..."); + return null; + } + if (!oauthCode) { logError("OAuth authentication timed out after 120 seconds"); logError("Alternative: Use a manual API token instead"); diff --git a/packages/cli/src/shared/oauth.ts b/packages/cli/src/shared/oauth.ts index 0a157cf0..410b975e 100644 --- a/packages/cli/src/shared/oauth.ts +++ b/packages/cli/src/shared/oauth.ts @@ -56,6 +56,8 @@ const SUCCESS_HTML = `

Authentication Failed

Invalid or missing state parameter (CSRF protection). Please try again.

`; +const DENIAL_HTML = `

Authorization Denied

You denied access to OpenRouter. You can close this tab and return to your terminal.

`; + async function tryOauthFlow(callbackPort = 5180, agentSlug?: string, cloudSlug?: string): Promise { logStep("Attempting OAuth authentication..."); @@ -72,6 +74,7 @@ async function tryOauthFlow(callbackPort = 5180, agentSlug?: string, cloudSlug?: const csrfState = generateCsrfState(); let oauthCode: string | null = null; + let oauthDenied = false; let server: ReturnType | null = null; // Try ports in range @@ -83,6 +86,22 @@ async function tryOauthFlow(callbackPort = 5180, agentSlug?: string, cloudSlug?: hostname: "127.0.0.1", fetch(req) { const url = new URL(req.url); + if (url.pathname === "/callback") { + // Check for OAuth denial / error + const error = url.searchParams.get("error"); + if (error) { + const desc = url.searchParams.get("error_description") || error; + logError(`OpenRouter authorization denied: ${desc}`); + oauthDenied = true; + return new Response(DENIAL_HTML, { + status: 403, + headers: { + "Content-Type": "text/html", + Connection: "close", + }, + }); + } + } const code = url.searchParams.get("code"); if (url.pathname === "/callback" && code) { // CSRF check @@ -145,12 +164,19 @@ async function tryOauthFlow(callbackPort = 5180, agentSlug?: string, cloudSlug?: // Wait up to 120 seconds logStep("Waiting for authentication in browser (timeout: 120s)..."); const deadline = Date.now() + 120_000; - while (!oauthCode && Date.now() < deadline) { + while (!oauthCode && !oauthDenied && Date.now() < deadline) { await new Promise((r) => setTimeout(r, 500)); } server.stop(true); + if (oauthDenied) { + logError("OAuth authorization was denied by the user"); + logError("Alternative: Use a manual API key instead"); + logError(" export OPENROUTER_API_KEY=sk-or-v1-..."); + return null; + } + if (!oauthCode) { logError("OAuth authentication timed out after 120 seconds"); logError("Alternative: Use a manual API key instead");