-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat: add manual token authentication for Firebase Studio support #7724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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=([^&]+)/) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") || | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
|
||
| 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" | ||||||
|
|
||||||
|
|
@@ -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") | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
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("") | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"> | ||||||
|
|
@@ -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"} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
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"} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
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> | ||||||
| </> | ||||||
| )} | ||||||
|
|
||||||
There was a problem hiding this comment.
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?