diff --git a/package.json b/package.json index 03702f04fa..367f6944a6 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,11 @@ "title": "Settings", "icon": "$(settings-gear)" }, + { + "command": "cline.accountLoginClicked", + "title": "Account", + "icon": "$(account)" + }, { "command": "cline.openInNewTab", "title": "Open In New Tab", @@ -122,6 +127,11 @@ "command": "cline.settingsButtonClicked", "group": "navigation@5", "when": "view == claude-dev.SidebarProvider" + }, + { + "command": "cline.accountLoginClicked", + "group": "navigation@6", + "when": "view == claude-dev.SidebarProvider" } ] }, diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index d3210c7b4b..d6c5d87d26 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -947,25 +947,24 @@ export class ClineProvider implements vscode.WebviewViewProvider { return true } - async handleAuthCallback(token: string, apiKey: string) { + async handleAuthCallback(customToken: string, apiKey: string) { try { - // First sign in with Firebase to trigger auth state change - await this.authManager.signInWithCustomToken(token) - - // Then store the token securely - await this.storeSecret("authToken", token) + // Store the custom token for future re-authentication + await this.storeSecret("authToken", customToken) await this.storeSecret("clineApiKey", apiKey) + // Sign in with Firebase using the custom token + await this.authManager.signInWithCustomToken(customToken) + const clineProvider: ApiProvider = "cline" await this.updateGlobalState("apiProvider", clineProvider) - // Update API configuration with the new provider and auth token + // Update API configuration with the new provider and API key const { apiConfiguration } = await this.getState() const updatedConfig = { ...apiConfiguration, apiProvider: clineProvider, clineApiKey: apiKey, - authToken: token, } if (this.cline) { @@ -977,6 +976,9 @@ export class ClineProvider implements vscode.WebviewViewProvider { } catch (error) { console.error("Failed to handle auth callback:", error) vscode.window.showErrorMessage("Failed to log in to Cline") + // Clean up stored tokens on failure + await this.storeSecret("authToken", undefined) + await this.storeSecret("clineApiKey", undefined) } } diff --git a/src/services/auth/FirebaseAuthManager.ts b/src/services/auth/FirebaseAuthManager.ts index 835a84c0c0..5ce2a62d55 100644 --- a/src/services/auth/FirebaseAuthManager.ts +++ b/src/services/auth/FirebaseAuthManager.ts @@ -1,5 +1,14 @@ import { initializeApp } from "firebase/app" -import { Auth, User, getAuth, onAuthStateChanged, signInWithCustomToken, signOut } from "firebase/auth" +import { + Auth, + User, + browserLocalPersistence, + getAuth, + onAuthStateChanged, + setPersistence, + signInWithCustomToken, + signOut, +} from "firebase/auth" import * as vscode from "vscode" import { ClineProvider } from "../../core/webview/ClineProvider" import { firebaseConfig } from "./config" @@ -22,6 +31,15 @@ export class FirebaseAuthManager { this.auth = getAuth(app) console.log("Firebase app initialized", { appConfig: firebaseConfig }) + // Set persistence to LOCAL to maintain auth state across sessions + setPersistence(this.auth, browserLocalPersistence) + .then(() => { + console.log("Firebase persistence set to LOCAL") + }) + .catch((error) => { + console.error("Error setting persistence:", error) + }) + // Auth state listener onAuthStateChanged(this.auth, this.handleAuthStateChange.bind(this)) console.log("Auth state change listener added") @@ -38,22 +56,46 @@ export class FirebaseAuthManager { return } + // Check if we already have an active user session from Firebase's persistence + const currentUser = this.auth.currentUser + if (currentUser) { + console.log("Found existing Firebase session") + await provider.setUserInfo({ + displayName: currentUser.displayName, + email: currentUser.email, + photoURL: currentUser.photoURL, + }) + console.log("Existing session restored") + return + } + + // If no active session, try to sign in with stored custom token const storedToken = await provider.getSecret("authToken") if (storedToken) { - console.log("Found stored auth token, attempting to restore session") + console.log("Found stored custom token, attempting to restore session") try { await this.signInWithCustomToken(storedToken) - console.log("Session restored successfully") + console.log("Session restored successfully with custom token") } catch (error) { - console.error("Failed to restore session, clearing token:", error) + console.error("Failed to restore session with custom token:", error) await provider.setAuthToken(undefined) await provider.setUserInfo(undefined) + // Attempt to sign out to ensure clean state + try { + await this.signOut() + } catch (signOutError) { + console.error("Error during cleanup after failed session restore:", signOutError) + } } } else { - console.log("No stored auth token found") + console.log("No stored custom token found") } } + getCurrentUser(): User | null { + return this.auth.currentUser + } + private async handleAuthStateChange(user: User | null) { console.log("Auth state changed", { user }) const provider = this.providerRef.deref() @@ -64,8 +106,6 @@ export class FirebaseAuthManager { if (user) { console.log("User signed in", { userId: user.uid }) - const idToken = await user.getIdToken() - await provider.setAuthToken(idToken) // Store public user info in state await provider.setUserInfo({ displayName: user.displayName, diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index ad1e141fa5..74ee47a433 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -5,7 +5,7 @@ import { validateApiConfiguration, validateModelId } from "../../utils/validate" import { vscode } from "../../utils/vscode" import ApiOptions from "./ApiOptions" import SettingsButton from "../common/SettingsButton" -const IS_DEV = false // FIXME: use flags when packaging +const IS_DEV = true // FIXME: use flags when packaging type SettingsViewProps = { onDone: () => void