Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/cloud/src/CloudService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,20 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
return this.authService!.handleCallback(code, state, organizationId)
}

public async handleManualToken(token: string): Promise<void> {
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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instanceof check here works, but have you considered adding handleManualToken to the AuthService interface instead? That way we could avoid runtime type checking and make this more maintainable. Just a thought - what if we need to support manual tokens in other auth service implementations later?

return this.authService.handleManualToken(token)
} else {
throw new Error("Manual token authentication not supported with current auth service")
}
}

// SettingsService

public getAllowList(): OrganizationAllowList {
Expand Down
36 changes: 36 additions & 0 deletions packages/cloud/src/WebAuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,42 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> 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<void> {
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice we're hardcoding organizationId to null here. Would it make sense to allow users to specify their organization if they belong to one? Maybe we could parse it from the URL or add an optional field in the UI?


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
*
Expand Down
29 changes: 29 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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=([^&]+)/)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The token extraction regex is duplicated here and in CloudView.tsx. Could we extract this to a shared utility function to ensure consistency?

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() || undefined,
})
} 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") ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using an inline fallback string in the error message for manual token authentication failure. Instead, rely solely on the translation function (t) with correctly defined translation keys.

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

"Manual token authentication failed. Please check the token and try again.",
)
}
}
break
}

case "saveCodeIndexSettingsAtomic": {
if (!message.codeIndexSettings) {
Expand Down
2 changes: 2 additions & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export interface WebviewMessage {
| "cloudButtonClicked"
| "rooCloudSignIn"
| "rooCloudSignOut"
| "rooCloudManualToken"
| "condenseTaskContextRequest"
| "requestIndexingStatus"
| "startIndexing"
Expand Down Expand Up @@ -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
Expand Down
68 changes: 65 additions & 3 deletions webview-ui/src/components/cloud/CloudView.tsx
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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"
Expand All @@ -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"

Expand Down Expand Up @@ -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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using inline fallback strings with t(). The translation keys already have defaults in the locale files. Remove the '|| "Please enter a valid token"' fallback to ensure all user‐facing text is sourced from the translation files.

Suggested change
setTokenError(t("cloud:manualTokenError") || "Please enter a valid token")
setTokenError(t("cloud:manualTokenError"))

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

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("")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good that we're clearing the token after submission! Consider also clearing it if the user collapses the manual token section without submitting - just for extra security. Also, might be nice to add a loading state while the token is being processed.

setShowManualToken(false)
}

return (
<div className="flex flex-col h-full">
<div className="flex justify-between items-center mb-6">
Expand Down Expand Up @@ -192,6 +215,45 @@ export const CloudView = ({ userInfo, isAuthenticated, cloudApiUrl, onDone }: Cl
<VSCodeButton appearance="primary" onClick={handleConnectClick} className="w-1/2">
{t("cloud:connect")}
</VSCodeButton>

{/* Manual token input section */}
<div className="w-full mt-4">
<button
onClick={() => setShowManualToken(!showManualToken)}
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">
<Key size="14" />
{t("cloud:manualTokenLink") || "Having trouble? Enter token manually"}
</button>

{showManualToken && (
<div className="mt-4 p-4 border border-vscode-widget-border rounded">
<p className="text-sm text-vscode-descriptionForeground mb-3">
{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."}
</p>
<div className="flex gap-2">
<VSCodeTextField
value={manualToken}
onInput={(e: any) => {
setManualToken(e.target.value)
setTokenError("")
}}
placeholder={t("cloud:manualTokenPlaceholder") || "Paste token or URL here"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove inline fallback string from the placeholder prop in VSCodeTextField. The call t("cloud:manualTokenPlaceholder") already returns the correct translated string from the locale file.

Suggested change
placeholder={t("cloud:manualTokenPlaceholder") || "Paste token or URL here"}
placeholder={t("cloud:manualTokenPlaceholder")}

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

className="flex-1"
/>
<VSCodeButton
appearance="secondary"
onClick={handleManualTokenSubmit}
disabled={!manualToken.trim()}>
{t("cloud:submitToken") || "Submit"}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove inline fallback string from the submit button text. Use solely the translation via t("cloud:submitToken") so that all user-facing text comes from the locale files.

Suggested change
{t("cloud:submitToken") || "Submit"}
{t("cloud:submitToken")}

This comment was generated because it violated a code review rule: irule_C0ez7Rji6ANcGkkX.

</VSCodeButton>
</div>
{tokenError && (
<p className="text-sm text-vscode-errorForeground mt-2">{tokenError}</p>
)}
</div>
)}
</div>
</div>
</>
)}
Expand Down
7 changes: 6 additions & 1 deletion webview-ui/src/i18n/locales/en/cloud.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Loading