mirror of
https://github.com/cline/cline.git
synced 2026-09-16 21:01:52 +08:00
only removing custom token on explicit log out action
This commit is contained in:
@@ -990,9 +990,8 @@ 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)
|
||||
// Even on login failure, we preserve any existing tokens
|
||||
// Only clear tokens on explicit logout
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,16 +8,18 @@ import {
|
||||
setPersistence,
|
||||
signInWithCustomToken,
|
||||
signOut,
|
||||
AuthError as FirebaseAuthError
|
||||
AuthError as FirebaseAuthError,
|
||||
} from "firebase/auth"
|
||||
import * as vscode from "vscode"
|
||||
import { ClineProvider } from "../../core/webview/ClineProvider"
|
||||
import { firebaseConfig } from "./config"
|
||||
|
||||
enum AuthErrorType {
|
||||
Network = 'network',
|
||||
InvalidToken = 'invalid_token',
|
||||
Other = 'other'
|
||||
Network = "network",
|
||||
InvalidToken = "invalid_token",
|
||||
ExpiredToken = "expired_token",
|
||||
TokenMismatch = "token_mismatch",
|
||||
Other = "other",
|
||||
}
|
||||
|
||||
interface AuthError {
|
||||
@@ -28,14 +30,14 @@ interface AuthError {
|
||||
|
||||
interface RetryConfig {
|
||||
maxAttempts: number
|
||||
baseDelay: number // in ms
|
||||
maxDelay: number // in ms
|
||||
baseDelay: number // in ms
|
||||
maxDelay: number // in ms
|
||||
}
|
||||
|
||||
const DEFAULT_RETRY_CONFIG: RetryConfig = {
|
||||
maxAttempts: 3,
|
||||
baseDelay: 1000,
|
||||
maxDelay: 10000
|
||||
maxDelay: 10000,
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
@@ -53,7 +55,7 @@ export class FirebaseAuthManager {
|
||||
constructor(provider: ClineProvider) {
|
||||
console.log("Initializing FirebaseAuthManager", { provider })
|
||||
this.providerRef = new WeakRef(provider)
|
||||
|
||||
|
||||
try {
|
||||
const app = initializeApp(firebaseConfig)
|
||||
this.auth = getAuth(app)
|
||||
@@ -83,48 +85,64 @@ export class FirebaseAuthManager {
|
||||
console.error("Failed to set persistence after retries:", authError)
|
||||
// Don't throw - persistence failure shouldn't prevent auth initialization
|
||||
// But we should log it clearly for debugging
|
||||
vscode.window.showErrorMessage("Warning: Failed to set authentication persistence. You may need to log in more frequently.")
|
||||
vscode.window.showErrorMessage(
|
||||
"Warning: Failed to set authentication persistence. You may need to log in more frequently.",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private classifyError(error: any): AuthError {
|
||||
console.log("Classifying auth error:", error)
|
||||
|
||||
|
||||
if (error?.code === "auth/network-request-failed") {
|
||||
return {
|
||||
type: AuthErrorType.Network,
|
||||
message: "Network error during authentication",
|
||||
originalError: error
|
||||
originalError: error,
|
||||
}
|
||||
}
|
||||
|
||||
if (error?.code === "auth/invalid-custom-token" ||
|
||||
error?.code === "auth/custom-token-mismatch" ||
|
||||
error?.code === "auth/argument-error") {
|
||||
|
||||
// Only consider a token invalid if it's explicitly invalid or malformed
|
||||
if (error?.code === "auth/invalid-custom-token" || error?.code === "auth/argument-error") {
|
||||
return {
|
||||
type: AuthErrorType.InvalidToken,
|
||||
message: "Invalid authentication token",
|
||||
originalError: error
|
||||
message: "Invalid authentication token format",
|
||||
originalError: error,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Token mismatch indicates the token might be for a different project/environment
|
||||
if (error?.code === "auth/custom-token-mismatch") {
|
||||
return {
|
||||
type: AuthErrorType.TokenMismatch,
|
||||
message: "Token mismatch - may be for different environment",
|
||||
originalError: error,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle expired tokens separately
|
||||
if (error?.code === "auth/user-token-expired") {
|
||||
return {
|
||||
type: AuthErrorType.ExpiredToken,
|
||||
message: "Authentication token has expired",
|
||||
originalError: error,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: AuthErrorType.Other,
|
||||
message: error?.message || "Unknown authentication error",
|
||||
originalError: error
|
||||
originalError: error,
|
||||
}
|
||||
}
|
||||
|
||||
private async delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
private async retryWithBackoff<T>(
|
||||
operation: () => Promise<T>,
|
||||
config: RetryConfig = DEFAULT_RETRY_CONFIG
|
||||
): Promise<T> {
|
||||
private async retryWithBackoff<T>(operation: () => Promise<T>, config: RetryConfig = DEFAULT_RETRY_CONFIG): Promise<T> {
|
||||
let lastError: any
|
||||
|
||||
|
||||
for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
|
||||
try {
|
||||
console.log(`Attempting operation (attempt ${attempt}/${config.maxAttempts})`)
|
||||
@@ -132,32 +150,55 @@ export class FirebaseAuthManager {
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
const authError = this.classifyError(error)
|
||||
|
||||
// Don't retry for invalid token errors
|
||||
if (authError.type === AuthErrorType.InvalidToken) {
|
||||
console.log("Invalid token error - not retrying:", authError)
|
||||
|
||||
// Don't retry for token-related errors
|
||||
if (
|
||||
authError.type === AuthErrorType.InvalidToken ||
|
||||
authError.type === AuthErrorType.TokenMismatch ||
|
||||
authError.type === AuthErrorType.ExpiredToken
|
||||
) {
|
||||
console.log("Token-related error - not retrying:", authError)
|
||||
throw error
|
||||
}
|
||||
|
||||
|
||||
if (attempt === config.maxAttempts) {
|
||||
console.error(`All ${config.maxAttempts} attempts failed:`, authError)
|
||||
throw error
|
||||
}
|
||||
|
||||
|
||||
// Calculate delay with exponential backoff
|
||||
const delay = Math.min(
|
||||
config.baseDelay * Math.pow(2, attempt - 1),
|
||||
config.maxDelay
|
||||
)
|
||||
|
||||
const delay = Math.min(config.baseDelay * Math.pow(2, attempt - 1), config.maxDelay)
|
||||
|
||||
console.log(`Attempt ${attempt} failed, retrying in ${delay}ms:`, authError)
|
||||
await this.delay(delay)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw lastError
|
||||
}
|
||||
|
||||
private async cleanupFailedAuth(provider: ClineProvider, error: AuthError) {
|
||||
console.log("Cleaning up failed authentication state", { errorType: error.type })
|
||||
try {
|
||||
// Clear user info since it's no longer valid
|
||||
await provider.setUserInfo(undefined)
|
||||
console.log("User info cleared")
|
||||
|
||||
// We no longer clear the auth token here - it will only be cleared on explicit user logout
|
||||
// Instead, we just sign out of Firebase if needed
|
||||
if (
|
||||
error.type === AuthErrorType.InvalidToken ||
|
||||
error.type === AuthErrorType.TokenMismatch ||
|
||||
error.type === AuthErrorType.ExpiredToken
|
||||
) {
|
||||
console.log("Auth error detected - signing out but preserving token")
|
||||
await this.signOut()
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.error("Error during auth state cleanup:", cleanupError)
|
||||
}
|
||||
}
|
||||
|
||||
private async restoreSession() {
|
||||
console.log("Attempting to restore session")
|
||||
const provider = this.providerRef.deref()
|
||||
@@ -178,19 +219,14 @@ export class FirebaseAuthManager {
|
||||
} catch (error) {
|
||||
const authError = this.classifyError(error)
|
||||
console.error("Failed to restore session with custom token:", authError)
|
||||
|
||||
if (authError.type === AuthErrorType.InvalidToken) {
|
||||
// Only clean up state for invalid token errors
|
||||
console.log("Invalid token detected - cleaning up auth state")
|
||||
await this.cleanupFailedAuth(provider)
|
||||
} else if (authError.type === AuthErrorType.Network) {
|
||||
// For network errors, preserve the token and throw to allow retry
|
||||
|
||||
// Clean up Firebase auth state but preserve the token
|
||||
await this.cleanupFailedAuth(provider, authError)
|
||||
|
||||
if (authError.type === AuthErrorType.Network) {
|
||||
// For network errors, throw to allow retry
|
||||
console.log("Network error during session restore - will retry later")
|
||||
throw error
|
||||
} else {
|
||||
// For other errors, log but preserve the token
|
||||
console.log("Non-critical error during session restore - preserving token for retry")
|
||||
console.error("Error details:", authError)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -198,46 +234,22 @@ export class FirebaseAuthManager {
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanupFailedAuth(provider: ClineProvider) {
|
||||
console.log("Cleaning up failed authentication state")
|
||||
try {
|
||||
// First clear user info since it's less critical
|
||||
await provider.setUserInfo(undefined)
|
||||
console.log("User info cleared")
|
||||
|
||||
// Then clear auth token and sign out
|
||||
await provider.setAuthToken(undefined)
|
||||
console.log("Auth token cleared")
|
||||
|
||||
await this.signOut()
|
||||
console.log("Authentication state cleaned up successfully")
|
||||
} catch (error) {
|
||||
console.error("Error during auth state cleanup:", error)
|
||||
// Even if cleanup fails, we want to ensure the token is cleared for security
|
||||
try {
|
||||
await provider.setAuthToken(undefined)
|
||||
console.log("Auth token cleared after cleanup error")
|
||||
} catch (tokenError) {
|
||||
console.error("Critical: Failed to clear auth token:", tokenError)
|
||||
// At this point, we've tried our best to clean up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCurrentUser(): User | null {
|
||||
return this.auth.currentUser
|
||||
}
|
||||
|
||||
private async handleAuthStateChange(user: User | null) {
|
||||
console.log("Auth state changed", {
|
||||
user: user ? {
|
||||
uid: user.uid,
|
||||
email: user.email,
|
||||
emailVerified: user.emailVerified,
|
||||
isAnonymous: user.isAnonymous,
|
||||
metadata: user.metadata
|
||||
} : null,
|
||||
isInitialState: this.isInitialAuthState
|
||||
console.log("Auth state changed", {
|
||||
user: user
|
||||
? {
|
||||
uid: user.uid,
|
||||
email: user.email,
|
||||
emailVerified: user.emailVerified,
|
||||
isAnonymous: user.isAnonymous,
|
||||
metadata: user.metadata,
|
||||
}
|
||||
: null,
|
||||
isInitialState: this.isInitialAuthState,
|
||||
})
|
||||
|
||||
const provider = this.providerRef.deref()
|
||||
@@ -251,7 +263,7 @@ export class FirebaseAuthManager {
|
||||
console.log("User signed in", {
|
||||
userId: user.uid,
|
||||
lastLoginAt: user.metadata.lastSignInTime,
|
||||
createdAt: user.metadata.creationTime
|
||||
createdAt: user.metadata.creationTime,
|
||||
})
|
||||
|
||||
// Store public user info in state
|
||||
@@ -262,14 +274,10 @@ export class FirebaseAuthManager {
|
||||
}
|
||||
await provider.setUserInfo(userInfo)
|
||||
console.log("User info set in provider", { userInfo })
|
||||
|
||||
} else if (!this.isInitialAuthState) {
|
||||
// Only clear auth state if this isn't the initial null state
|
||||
// Only clear user info if this isn't the initial null state
|
||||
console.log("User signed out (not initial state)")
|
||||
await this.retryWithBackoff(async () => {
|
||||
await provider.setAuthToken(undefined)
|
||||
await provider.setUserInfo(undefined)
|
||||
})
|
||||
await provider.setUserInfo(undefined)
|
||||
} else {
|
||||
console.log("Initial auth state is null, attempting session restore")
|
||||
this.isInitialAuthState = false
|
||||
@@ -292,11 +300,8 @@ export class FirebaseAuthManager {
|
||||
} catch (error) {
|
||||
console.error("Error handling auth state change:", error)
|
||||
// Attempt to clean up state if something went wrong
|
||||
try {
|
||||
await this.cleanupFailedAuth(provider)
|
||||
} catch (cleanupError) {
|
||||
console.error("Failed to cleanup after auth state change error:", cleanupError)
|
||||
}
|
||||
const authError = this.classifyError(error)
|
||||
await this.cleanupFailedAuth(provider, authError)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user