From f1c28ba300614c3b7af5d10f77c6e8277a88df14 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 18 Jul 2025 18:45:03 +0000 Subject: [PATCH] fix: improve authentication reliability in Windsurf - Add enhanced error handling and logging for credential storage/retrieval - Implement periodic credential checking as fallback for Windsurf compatibility - Force credential reload after authentication callback to ensure state transition - Add manual credential check with timeout after successful authentication - Improve secrets API error handling for different VSCode-compatible editors Fixes #5914 --- packages/cloud/src/auth/WebAuthService.ts | 87 +++++++++++++++++++---- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/packages/cloud/src/auth/WebAuthService.ts b/packages/cloud/src/auth/WebAuthService.ts index d14cbe67d8..43afdf82b3 100644 --- a/packages/cloud/src/auth/WebAuthService.ts +++ b/packages/cloud/src/auth/WebAuthService.ts @@ -115,18 +115,25 @@ export class WebAuthService extends EventEmitter implements A private async handleCredentialsChange(): Promise { try { + this.log("[auth] Handling credentials change...") const credentials = await this.loadCredentials() if (credentials) { + this.log("[auth] Credentials found, checking if they changed") if ( this.credentials === null || this.credentials.clientToken !== credentials.clientToken || this.credentials.sessionId !== credentials.sessionId ) { + this.log("[auth] Credentials changed, transitioning to attempting session") this.transitionToAttemptingSession(credentials) + } else { + this.log("[auth] Credentials unchanged") } } else { + this.log("[auth] No credentials found") if (this.state !== "logged-out") { + this.log("[auth] Transitioning to logged-out state") this.transitionToLoggedOut() } } @@ -135,6 +142,15 @@ export class WebAuthService extends EventEmitter implements A } } + /** + * Manually check for credential changes - used as fallback for Windsurf + * where secrets.onDidChange might not work reliably + */ + private async checkCredentialsManually(): Promise { + this.log("[auth] Manually checking credentials...") + await this.handleCredentialsChange() + } + private transitionToLoggedOut(): void { this.timer.stop() @@ -191,26 +207,64 @@ export class WebAuthService extends EventEmitter implements A return } + this.log("[auth] Initializing WebAuthService...") + await this.handleCredentialsChange() - this.context.subscriptions.push( - this.context.secrets.onDidChange((e) => { - if (e.key === this.authCredentialsKey) { - this.handleCredentialsChange() - } - }), - ) + // Set up secrets change listener with error handling for Windsurf compatibility + try { + this.context.subscriptions.push( + this.context.secrets.onDidChange((e) => { + if (e.key === this.authCredentialsKey) { + this.log("[auth] Secrets changed, handling credentials change") + this.handleCredentialsChange() + } + }), + ) + this.log("[auth] Secrets change listener registered successfully") + } catch (error) { + this.log("[auth] Warning: Failed to register secrets change listener:", error) + // Continue without the listener - we'll rely on manual credential checks + } + + // Set up periodic credential check as fallback for Windsurf + const credentialCheckInterval = setInterval(async () => { + if (this.state === "initializing") { + return // Don't check during initialization + } + await this.checkCredentialsManually() + }, 5000) // Check every 5 seconds + + // Clean up interval when context is disposed + this.context.subscriptions.push({ + dispose: () => { + clearInterval(credentialCheckInterval) + this.log("[auth] Credential check interval cleared") + } + }) + + this.log("[auth] WebAuthService initialization complete") } private async storeCredentials(credentials: AuthCredentials): Promise { - await this.context.secrets.store(this.authCredentialsKey, JSON.stringify(credentials)) + try { + await this.context.secrets.store(this.authCredentialsKey, JSON.stringify(credentials)) + this.log("[auth] Credentials stored successfully") + } catch (error) { + this.log("[auth] Error storing credentials:", error) + throw new Error(`Failed to store authentication credentials: ${error}`) + } } private async loadCredentials(): Promise { - const credentialsJson = await this.context.secrets.get(this.authCredentialsKey) - if (!credentialsJson) return null - try { + const credentialsJson = await this.context.secrets.get(this.authCredentialsKey) + if (!credentialsJson) { + this.log("[auth] No credentials found in storage") + return null + } + + this.log("[auth] Loading credentials from storage") const parsedJson = JSON.parse(credentialsJson) const credentials = authCredentialsSchema.parse(parsedJson) @@ -221,12 +275,13 @@ export class WebAuthService extends EventEmitter implements A this.log("[auth] Migrated credentials with organizationId") } + this.log("[auth] Credentials loaded successfully") return credentials } catch (error) { if (error instanceof z.ZodError) { this.log("[auth] Invalid credentials format:", error.errors) } else { - this.log("[auth] Failed to parse stored credentials:", error) + this.log("[auth] Failed to load or parse stored credentials:", error) } return null } @@ -298,6 +353,14 @@ export class WebAuthService extends EventEmitter implements A await this.storeCredentials(credentials) + // Force credential reload and state transition for Windsurf compatibility + await this.handleCredentialsChange() + + // Additional manual check after a short delay to ensure state transition + setTimeout(async () => { + await this.checkCredentialsManually() + }, 1000) + vscode.window.showInformationMessage("Successfully authenticated with Roo Code Cloud") this.log("[auth] Successfully authenticated with Roo Code Cloud") } catch (error) {