Skip to content

Commit 290fe57

Browse files
committed
feat: immediately restart auth flow on invalid client errors
Instead of throwing an error and requiring manual restart, now: - Immediately kick off fresh auth flow after clearing invalid client data - Open browser for new registration and authentication automatically - Complete connection seamlessly without user intervention - Add protection against infinite loops if fresh registration also fails This ensures users don't need to manually restart their client when server-side client registration is deleted - the recovery is automatic.
1 parent 81572df commit 290fe57

File tree

2 files changed

+70
-14
lines changed

2 files changed

+70
-14
lines changed

DEFENSIVE_AUTH_CHANGES.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ When the scenario occurs again:
6262
2. Classifies this as `invalid_client` error
6363
3. Logs clear message about client registration being deleted
6464
4. Clears ALL stored auth data (tokens, client_info.json, code_verifier.txt)
65-
5. Provides helpful error message suggesting to reconnect
66-
6. Next connection attempt will start fresh with new client registration
65+
5. **Immediately starts fresh auth flow** (no manual restart required)
66+
6. Opens browser for new client registration and authentication
67+
7. Completes with working connection automatically
6768

6869
## Testing
6970

@@ -74,6 +75,7 @@ When the scenario occurs again:
7475
## Benefits
7576

7677
- **No More Infinite Loops**: Client won't keep retrying with invalid credentials
77-
- **Automatic Recovery**: Forces clean slate for successful reconnection
78-
- **Better User Experience**: Clear error messages about what happened
78+
- **Immediate Recovery**: Automatically starts fresh auth flow without manual restart
79+
- **Seamless User Experience**: Single browser prompt resolves the entire issue
7980
- **Robust Defense**: Handles edge cases where server state diverges from client state
81+
- **Fail-Safe Protection**: Prevents infinite loops even if fresh registration fails

src/lib/utils.ts

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ export async function connectToRemoteServer(
420420

421421
// Handle invalid_client errors by clearing all auth data
422422
if (errorType === 'invalid_client') {
423-
log('Client registration is invalid. Clearing all stored auth data and restarting registration...')
423+
log('Client registration is invalid. Clearing all stored auth data and starting fresh auth flow...')
424424
if (DEBUG) debugLog('Invalid client registration detected, clearing all auth data')
425425

426426
// Clear all auth data to force complete re-registration
@@ -500,8 +500,8 @@ export async function connectToRemoteServer(
500500
const authErrorType = classifyAuthError(authError)
501501
if (authErrorType === 'invalid_client') {
502502
log('Token exchange failed due to invalid client. This suggests the client registration was deleted on the server.')
503-
log('Clearing all stored auth data to force complete re-registration on next attempt...')
504-
if (DEBUG) debugLog('Token exchange failed with invalid_client, clearing all auth data')
503+
log('Clearing all stored auth data and immediately starting fresh auth flow...')
504+
if (DEBUG) debugLog('Token exchange failed with invalid_client, clearing all auth data and restarting auth')
505505

506506
// Clear all auth data to force complete re-registration
507507
const extendedProvider = authProvider as ExtendedOAuthClientProvider
@@ -512,13 +512,67 @@ export async function connectToRemoteServer(
512512
// Reset retry state since we're starting fresh
513513
resetAuthRetryState(serverUrlHash)
514514

515-
// Create a more descriptive error message
516-
const descriptiveError = new Error(
517-
'Client registration appears to be invalid or deleted on the server. ' +
518-
'Cleared all local auth data. Please try connecting again to re-register.',
519-
)
520-
descriptiveError.stack = authError.stack
521-
throw descriptiveError
515+
// Immediately restart the auth flow with fresh registration
516+
log('Starting fresh authentication flow...')
517+
if (DEBUG) debugLog('Restarting auth flow after clearing invalid client data')
518+
519+
const { waitForAuthCode, skipBrowserAuth } = await authInitializer()
520+
521+
if (skipBrowserAuth) {
522+
log('Authentication required but skipping browser auth - using shared auth')
523+
if (DEBUG) debugLog('Authentication required but skipping browser auth - using shared auth')
524+
} else {
525+
log('Authentication required. Waiting for authorization...')
526+
if (DEBUG) debugLog('Authentication required. Waiting for authorization...')
527+
}
528+
529+
// Wait for the authorization code from the callback
530+
if (DEBUG) debugLog('Waiting for auth code from callback server')
531+
const newCode = await waitForAuthCode()
532+
if (DEBUG) debugLog('Received new auth code from callback server')
533+
534+
// Try token exchange again with the new code
535+
log('Completing fresh authorization...')
536+
if (DEBUG) debugLog('Completing fresh authorization with new code')
537+
try {
538+
await transport.finishAuth(newCode)
539+
if (DEBUG) debugLog('Fresh authorization completed successfully')
540+
} catch (freshAuthError: any) {
541+
// If the fresh auth also fails with invalid_client, we have a deeper problem
542+
const freshErrorType = classifyAuthError(freshAuthError)
543+
if (freshErrorType === 'invalid_client') {
544+
log('Fresh authentication also failed with invalid client. This suggests a persistent server-side issue.')
545+
if (DEBUG) debugLog('Fresh auth also failed with invalid_client, giving up')
546+
throw new Error(
547+
'Fresh client registration also failed with invalid_client error. ' +
548+
'This suggests a persistent server-side issue. Please check server configuration.',
549+
)
550+
}
551+
// For other errors, just re-throw
552+
throw freshAuthError
553+
}
554+
555+
// Reset retry state on successful auth
556+
resetAuthRetryState(serverUrlHash)
557+
558+
// Continue with recursive reconnection
559+
if (recursionReasons.has(REASON_AUTH_NEEDED)) {
560+
const errorMessage = `Already attempted reconnection for reason: ${REASON_AUTH_NEEDED}. Giving up.`
561+
log(errorMessage)
562+
if (DEBUG)
563+
debugLog('Already attempted auth reconnection, giving up', {
564+
recursionReasons: Array.from(recursionReasons),
565+
})
566+
throw new Error(errorMessage)
567+
}
568+
569+
// Track this reason for recursion
570+
recursionReasons.add(REASON_AUTH_NEEDED)
571+
log(`Recursively reconnecting for reason: ${REASON_AUTH_NEEDED}`)
572+
if (DEBUG) debugLog('Recursively reconnecting after fresh auth', { recursionReasons: Array.from(recursionReasons) })
573+
574+
// Recursively call connectToRemoteServer with the updated recursion tracking
575+
return connectToRemoteServer(client, serverUrl, authProvider, headers, authInitializer, transportStrategy, recursionReasons)
522576
}
523577

524578
throw authError

0 commit comments

Comments
 (0)