fix: uniform decrypt error and clear stored credentials on logout (#11632)

* fix: uniform decrypt error and clear stored credentials on logout

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kv72vdCDggCZEjJSmNCwnX

* fix: also clear mcp-oauth-callback on logout, cover the new keys in the logout test

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kv72vdCDggCZEjJSmNCwnX

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alexander Petric
2026-10-08 22:32:55 +02:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 5d90f96863
commit 987c321ebc
3 changed files with 60 additions and 14 deletions
+16
View File
@@ -12,9 +12,25 @@ import { clearUser } from './logout'
describe('clearUser', () => {
beforeEach(() => {
sessionStorage.clear()
localStorage.clear()
vi.clearAllMocks()
})
it('drops credential-bearing localStorage entries and keeps preferences', async () => {
const credentialKeys = ['oauth-callback', 'mcp-oauth-callback', 'test_dev_token']
for (const key of credentialKeys) {
localStorage.setItem(key, 'a-token')
}
localStorage.setItem('dark-mode', 'true')
await clearUser()
for (const key of credentialKeys) {
expect(localStorage.getItem(key)).toBeNull()
}
expect(localStorage.getItem('dark-mode')).toBe('true')
})
// Logging out while impersonating must not leave the impersonator's own token behind.
it('drops the impersonation token backup', async () => {
sessionStorage.setItem('pre_impersonation_token', 'a-token')
+10 -6
View File
@@ -6,15 +6,19 @@ import { clearStores } from './storeUtils'
// Note: logout and logoutWithRedirect have been moved to logoutKit.ts
// as they depend on SvelteKit navigation
// The impersonation backup holds the impersonator's own auth token, so it must go on
// logout too, not only when impersonation is ended from the banner — otherwise a usable
// token stays readable for the lifetime of the tab.
function clearImpersonationFromStorage(): void {
// Every browser-storage entry that can hold a credential must go on logout. The impersonation
// backup is the impersonator's own auth token; `oauth-callback` and `mcp-oauth-callback` carry a
// connected account's token response when the OAuth popup had no opener to post it to;
// `test_dev_token` is the session token of the /test_dev pages.
function clearCredentialsFromStorage(): void {
try {
sessionStorage.removeItem('pre_impersonation_token')
sessionStorage.removeItem('pre_impersonation_email')
localStorage.removeItem('oauth-callback')
localStorage.removeItem('mcp-oauth-callback')
localStorage.removeItem('test_dev_token')
} catch (e) {
console.error('error interacting with session storage', e)
console.error('error interacting with browser storage', e)
}
}
@@ -23,7 +27,7 @@ export async function clearUser() {
noteSessionEmail(undefined)
accountSetup.reset()
clearStores()
clearImpersonationFromStorage()
clearCredentialsFromStorage()
await UserService.logout()
} catch (error) {}
}