Skip to content

Commit 7284fd0

Browse files
committed
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
1 parent e8deedd commit 7284fd0

File tree

6 files changed

+152
-4
lines changed

6 files changed

+152
-4
lines changed

packages/cloud/src/CloudService.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,20 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
216216
return this.authService!.handleCallback(code, state, organizationId)
217217
}
218218

219+
public async handleManualToken(token: string): Promise<void> {
220+
this.ensureInitialized()
221+
if (!this.authService) {
222+
throw new Error("Auth service not available")
223+
}
224+
// For WebAuthService, we need to add a method to handle manual tokens
225+
// Type guard to check if the auth service is WebAuthService
226+
if (this.authService instanceof WebAuthService) {
227+
return this.authService.handleManualToken(token)
228+
} else {
229+
throw new Error("Manual token authentication not supported with current auth service")
230+
}
231+
}
232+
219233
// SettingsService
220234

221235
public getAllowList(): OrganizationAllowList {

packages/cloud/src/WebAuthService.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,42 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
330330
}
331331
}
332332

333+
/**
334+
* Handle manual token input
335+
*
336+
* This method allows users to manually paste a token from the authentication page
337+
* when automatic redirect doesn't work (e.g., in Firebase Studio)
338+
*
339+
* @param token The authentication token from the URL
340+
*/
341+
public async handleManualToken(token: string): Promise<void> {
342+
if (!token || !token.trim()) {
343+
throw new Error("Invalid token provided")
344+
}
345+
346+
try {
347+
// The token is the ticket that we need to exchange for credentials
348+
const credentials = await this.clerkSignIn(token.trim())
349+
350+
// For manual token, we don't have organization context from the URL
351+
// Set it to null (personal account) by default
352+
credentials.organizationId = null
353+
354+
await this.storeCredentials(credentials)
355+
356+
const vscode = await importVscode()
357+
358+
if (vscode) {
359+
vscode.window.showInformationMessage("Successfully authenticated with Roo Code Cloud via manual token")
360+
}
361+
362+
this.log("[auth] Successfully authenticated with Roo Code Cloud via manual token")
363+
} catch (error) {
364+
this.log(`[auth] Error handling manual token: ${error}`)
365+
throw new Error(`Failed to authenticate with manual token: ${error}`)
366+
}
367+
}
368+
333369
/**
334370
* Log out
335371
*

src/core/webview/webviewMessageHandler.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2251,6 +2251,35 @@ export const webviewMessageHandler = async (
22512251

22522252
break
22532253
}
2254+
case "rooCloudManualToken": {
2255+
if (message.token) {
2256+
try {
2257+
// Extract the actual token from the URL if a full URL is pasted
2258+
let token = message.token.trim()
2259+
const tokenMatch = token.match(/[?&]token=([^&]+)/)
2260+
if (tokenMatch) {
2261+
token = tokenMatch[1]
2262+
}
2263+
2264+
// Call the manual token authentication method
2265+
await CloudService.instance.handleManualToken(token)
2266+
await provider.postStateToWebview()
2267+
provider.postMessageToWebview({
2268+
type: "authenticatedUser",
2269+
userInfo: CloudService.instance.getUserInfo(),
2270+
})
2271+
} catch (error) {
2272+
provider.log(
2273+
`Manual token authentication failed: ${error instanceof Error ? error.message : String(error)}`,
2274+
)
2275+
vscode.window.showErrorMessage(
2276+
t("common:errors.manual_token_auth_failed") ||
2277+
"Manual token authentication failed. Please check the token and try again.",
2278+
)
2279+
}
2280+
}
2281+
break
2282+
}
22542283

22552284
case "saveCodeIndexSettingsAtomic": {
22562285
if (!message.codeIndexSettings) {

src/shared/WebviewMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export interface WebviewMessage {
180180
| "cloudButtonClicked"
181181
| "rooCloudSignIn"
182182
| "rooCloudSignOut"
183+
| "rooCloudManualToken"
183184
| "condenseTaskContextRequest"
184185
| "requestIndexingStatus"
185186
| "startIndexing"
@@ -222,6 +223,7 @@ export interface WebviewMessage {
222223
| "removeQueuedMessage"
223224
| "editQueuedMessage"
224225
text?: string
226+
token?: string
225227
editedMessageContent?: string
226228
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
227229
disabled?: boolean

webview-ui/src/components/cloud/CloudView.tsx

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { useEffect, useRef } from "react"
2-
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
1+
import { useEffect, useRef, useState } from "react"
2+
import { VSCodeButton, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
33

44
import { type CloudUserInfo, TelemetryEventName } from "@roo-code/types"
55

@@ -9,7 +9,7 @@ import { vscode } from "@src/utils/vscode"
99
import { telemetryClient } from "@src/utils/TelemetryClient"
1010
import { ToggleSwitch } from "@/components/ui/toggle-switch"
1111

12-
import { History, PiggyBank, SquareArrowOutUpRightIcon } from "lucide-react"
12+
import { History, PiggyBank, SquareArrowOutUpRightIcon, Key } from "lucide-react"
1313

1414
// Define the production URL constant locally to avoid importing from cloud package in tests
1515
const PRODUCTION_ROO_CODE_API_URL = "https://app.roocode.com"
@@ -25,6 +25,9 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
2525
const { t } = useAppTranslation()
2626
const { remoteControlEnabled, setRemoteControlEnabled } = useExtensionState()
2727
const wasAuthenticatedRef = useRef(false)
28+
const [showManualToken, setShowManualToken] = useState(false)
29+
const [manualToken, setManualToken] = useState("")
30+
const [tokenError, setTokenError] = useState("")
2831

2932
const rooLogoUri = (window as any).IMAGES_BASE_URI + "/roo-logo.svg"
3033

@@ -75,6 +78,26 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
7578
vscode.postMessage({ type: "remoteControlEnabled", bool: newValue })
7679
}
7780

81+
const handleManualTokenSubmit = () => {
82+
if (!manualToken.trim()) {
83+
setTokenError(t("cloud:manualTokenError") || "Please enter a valid token")
84+
return
85+
}
86+
setTokenError("")
87+
// Extract the token from the URL if a full URL is pasted
88+
let token = manualToken.trim()
89+
const tokenMatch = token.match(/[?&]token=([^&]+)/)
90+
if (tokenMatch) {
91+
token = tokenMatch[1]
92+
}
93+
94+
// Send the manual token to the backend
95+
vscode.postMessage({ type: "rooCloudManualToken", token })
96+
// Clear the token field for security
97+
setManualToken("")
98+
setShowManualToken(false)
99+
}
100+
78101
return (
79102
<div className="flex flex-col h-full">
80103
<div className="flex justify-between items-center mb-6">
@@ -192,6 +215,45 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
192215
<VSCodeButton appearance="primary" onClick={handleConnectClick} className="w-1/2">
193216
{t("cloud:connect")}
194217
</VSCodeButton>
218+
219+
{/* Manual token input section */}
220+
<div className="w-full mt-4">
221+
<button
222+
onClick={() => setShowManualToken(!showManualToken)}
223+
className="text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0 text-sm flex items-center gap-1">
224+
<Key size="14" />
225+
{t("cloud:manualTokenLink") || "Having trouble? Enter token manually"}
226+
</button>
227+
228+
{showManualToken && (
229+
<div className="mt-4 p-4 border border-vscode-widget-border rounded">
230+
<p className="text-sm text-vscode-descriptionForeground mb-3">
231+
{t("cloud:manualTokenDescription") ||
232+
"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."}
233+
</p>
234+
<div className="flex gap-2">
235+
<VSCodeTextField
236+
value={manualToken}
237+
onInput={(e: any) => {
238+
setManualToken(e.target.value)
239+
setTokenError("")
240+
}}
241+
placeholder={t("cloud:manualTokenPlaceholder") || "Paste token or URL here"}
242+
className="flex-1"
243+
/>
244+
<VSCodeButton
245+
appearance="secondary"
246+
onClick={handleManualTokenSubmit}
247+
disabled={!manualToken.trim()}>
248+
{t("cloud:submitToken") || "Submit"}
249+
</VSCodeButton>
250+
</div>
251+
{tokenError && (
252+
<p className="text-sm text-vscode-errorForeground mt-2">{tokenError}</p>
253+
)}
254+
</div>
255+
)}
256+
</div>
195257
</div>
196258
</>
197259
)}

webview-ui/src/i18n/locales/en/cloud.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,10 @@
1313
"visitCloudWebsite": "Visit Roo Code Cloud",
1414
"remoteControl": "Roomote Control",
1515
"remoteControlDescription": "Enable following and interacting with tasks in this workspace with Roo Code Cloud",
16-
"cloudUrlPillLabel": "Roo Code Cloud URL"
16+
"cloudUrlPillLabel": "Roo Code Cloud URL",
17+
"manualTokenLink": "Having trouble? Enter token manually",
18+
"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.",
19+
"manualTokenPlaceholder": "Paste token or URL here",
20+
"manualTokenError": "Please enter a valid token",
21+
"submitToken": "Submit"
1722
}

0 commit comments

Comments
 (0)