From 7284fd0b62dc889781433dc2e88da1a10731d169 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sat, 6 Sep 2025 01:27:28 +0000 Subject: [PATCH] feat: add manual token authentication for Firebase Studio support - Add manual token input UI to CloudView component with expandable section - Add new WebviewMessage type 'rooCloudManualToken' with token field - Implement handleManualToken method in WebAuthService to process manual tokens - Add CloudService.handleManualToken method to route manual token auth - Update webviewMessageHandler to handle manual token submission - Add translation strings for manual token UI elements This allows users in Firebase Studio and other IDEs that don't support automatic authentication redirects to manually paste the token from the authentication page. Fixes #7723 --- packages/cloud/src/CloudService.ts | 14 ++++ packages/cloud/src/WebAuthService.ts | 36 ++++++++++ src/core/webview/webviewMessageHandler.ts | 29 ++++++++ src/shared/WebviewMessage.ts | 2 + webview-ui/src/components/cloud/CloudView.tsx | 68 ++++++++++++++++++- webview-ui/src/i18n/locales/en/cloud.json | 7 +- 6 files changed, 152 insertions(+), 4 deletions(-) diff --git a/packages/cloud/src/CloudService.ts b/packages/cloud/src/CloudService.ts index 8c8320cba7..1ff433c140 100644 --- a/packages/cloud/src/CloudService.ts +++ b/packages/cloud/src/CloudService.ts @@ -216,6 +216,20 @@ export class CloudService extends EventEmitter implements Di return this.authService!.handleCallback(code, state, organizationId) } + public async handleManualToken(token: string): Promise { + this.ensureInitialized() + if (!this.authService) { + throw new Error("Auth service not available") + } + // For WebAuthService, we need to add a method to handle manual tokens + // Type guard to check if the auth service is WebAuthService + if (this.authService instanceof WebAuthService) { + return this.authService.handleManualToken(token) + } else { + throw new Error("Manual token authentication not supported with current auth service") + } + } + // SettingsService public getAllowList(): OrganizationAllowList { diff --git a/packages/cloud/src/WebAuthService.ts b/packages/cloud/src/WebAuthService.ts index 934ca90b71..cede4bd68a 100644 --- a/packages/cloud/src/WebAuthService.ts +++ b/packages/cloud/src/WebAuthService.ts @@ -330,6 +330,42 @@ export class WebAuthService extends EventEmitter implements A } } + /** + * Handle manual token input + * + * This method allows users to manually paste a token from the authentication page + * when automatic redirect doesn't work (e.g., in Firebase Studio) + * + * @param token The authentication token from the URL + */ + public async handleManualToken(token: string): Promise { + if (!token || !token.trim()) { + throw new Error("Invalid token provided") + } + + try { + // The token is the ticket that we need to exchange for credentials + const credentials = await this.clerkSignIn(token.trim()) + + // For manual token, we don't have organization context from the URL + // Set it to null (personal account) by default + credentials.organizationId = null + + await this.storeCredentials(credentials) + + const vscode = await importVscode() + + if (vscode) { + vscode.window.showInformationMessage("Successfully authenticated with Roo Code Cloud via manual token") + } + + this.log("[auth] Successfully authenticated with Roo Code Cloud via manual token") + } catch (error) { + this.log(`[auth] Error handling manual token: ${error}`) + throw new Error(`Failed to authenticate with manual token: ${error}`) + } + } + /** * Log out * diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 080fbbcd94..1b3fbeb009 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2251,6 +2251,35 @@ export const webviewMessageHandler = async ( break } + case "rooCloudManualToken": { + if (message.token) { + try { + // Extract the actual token from the URL if a full URL is pasted + let token = message.token.trim() + const tokenMatch = token.match(/[?&]token=([^&]+)/) + if (tokenMatch) { + token = tokenMatch[1] + } + + // Call the manual token authentication method + await CloudService.instance.handleManualToken(token) + await provider.postStateToWebview() + provider.postMessageToWebview({ + type: "authenticatedUser", + userInfo: CloudService.instance.getUserInfo(), + }) + } catch (error) { + provider.log( + `Manual token authentication failed: ${error instanceof Error ? error.message : String(error)}`, + ) + vscode.window.showErrorMessage( + t("common:errors.manual_token_auth_failed") || + "Manual token authentication failed. Please check the token and try again.", + ) + } + } + break + } case "saveCodeIndexSettingsAtomic": { if (!message.codeIndexSettings) { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 565712bfbf..259b4a2340 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -180,6 +180,7 @@ export interface WebviewMessage { | "cloudButtonClicked" | "rooCloudSignIn" | "rooCloudSignOut" + | "rooCloudManualToken" | "condenseTaskContextRequest" | "requestIndexingStatus" | "startIndexing" @@ -222,6 +223,7 @@ export interface WebviewMessage { | "removeQueuedMessage" | "editQueuedMessage" text?: string + token?: string editedMessageContent?: string tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud" disabled?: boolean diff --git a/webview-ui/src/components/cloud/CloudView.tsx b/webview-ui/src/components/cloud/CloudView.tsx index 63733ef7d2..72f86ebc2c 100644 --- a/webview-ui/src/components/cloud/CloudView.tsx +++ b/webview-ui/src/components/cloud/CloudView.tsx @@ -1,5 +1,5 @@ -import { useEffect, useRef } from "react" -import { VSCodeButton } from "@vscode/webview-ui-toolkit/react" +import { useEffect, useRef, useState } from "react" +import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { type CloudUserInfo, TelemetryEventName } from "@roo-code/types" @@ -9,7 +9,7 @@ import { vscode } from "@src/utils/vscode" import { telemetryClient } from "@src/utils/TelemetryClient" import { ToggleSwitch } from "@/components/ui/toggle-switch" -import { History, PiggyBank, SquareArrowOutUpRightIcon } from "lucide-react" +import { History, PiggyBank, SquareArrowOutUpRightIcon, Key } from "lucide-react" // Define the production URL constant locally to avoid importing from cloud package in tests const PRODUCTION_ROO_CODE_API_URL = "https://app.roocode.com" @@ -25,6 +25,9 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl const { t } = useAppTranslation() const { remoteControlEnabled, setRemoteControlEnabled } = useExtensionState() const wasAuthenticatedRef = useRef(false) + const [showManualToken, setShowManualToken] = useState(false) + const [manualToken, setManualToken] = useState("") + const [tokenError, setTokenError] = useState("") const rooLogoUri = (window as any).IMAGES_BASE_URI + "/roo-logo.svg" @@ -75,6 +78,26 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl vscode.postMessage({ type: "remoteControlEnabled", bool: newValue }) } + const handleManualTokenSubmit = () => { + if (!manualToken.trim()) { + setTokenError(t("cloud:manualTokenError") || "Please enter a valid token") + return + } + setTokenError("") + // Extract the token from the URL if a full URL is pasted + let token = manualToken.trim() + const tokenMatch = token.match(/[?&]token=([^&]+)/) + if (tokenMatch) { + token = tokenMatch[1] + } + + // Send the manual token to the backend + vscode.postMessage({ type: "rooCloudManualToken", token }) + // Clear the token field for security + setManualToken("") + setShowManualToken(false) + } + return (
@@ -192,6 +215,45 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl {t("cloud:connect")} + + {/* Manual token input section */} +
+ + + {showManualToken && ( +
+

+ {t("cloud:manualTokenDescription") || + "If you're using Firebase Studio or another IDE that doesn't support automatic authentication, you can paste the token from the authentication page here."} +

+
+ { + setManualToken(e.target.value) + setTokenError("") + }} + placeholder={t("cloud:manualTokenPlaceholder") || "Paste token or URL here"} + className="flex-1" + /> + + {t("cloud:submitToken") || "Submit"} + +
+ {tokenError && ( +

{tokenError}

+ )} +
+ )} +
)} diff --git a/webview-ui/src/i18n/locales/en/cloud.json b/webview-ui/src/i18n/locales/en/cloud.json index 88948c9153..f0686ec010 100644 --- a/webview-ui/src/i18n/locales/en/cloud.json +++ b/webview-ui/src/i18n/locales/en/cloud.json @@ -13,5 +13,10 @@ "visitCloudWebsite": "Visit Roo Code Cloud", "remoteControl": "Roomote Control", "remoteControlDescription": "Enable following and interacting with tasks in this workspace with Roo Code Cloud", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "manualTokenLink": "Having trouble? Enter token manually", + "manualTokenDescription": "If you're using Firebase Studio or another IDE that doesn't support automatic authentication, you can paste the token from the authentication page here.", + "manualTokenPlaceholder": "Paste token or URL here", + "manualTokenError": "Please enter a valid token", + "submitToken": "Submit" }