Repository navigation
fix(auth): clear the previous user's state on sign-out - #583
Merged
Merged
Conversation
An expired session signs out without reloading the page: App.jsx answers a 401 with setUser(null), and setUser reset only the unread counts and sender favicons when the user changed. Whoever signed in next in that tab was shown the previous user's open compose, with its recipients, subject and quoted text, and their message list, search and message windows until their own data loaded. Settings held only in memory stayed the previous user's until loadPreferences replaced them, and the ones it sets only when the new user has saved them (remote images, the image whitelist, hidden folders, shortcuts, the auto-lock timer) carried over for the whole session. After a lockout, setLocked(false) also restored the message that was open when the previous user locked the screen. When a signed-in user leaves, setUser now resets the mailbox state that locking clears, the compose and message windows, the reply-draft lookups and the settings held only in memory, and drops the message remembered for the lock screen. A cold load goes from no user to one and resets nothing, so the reading preferences the store seeds from localStorage still apply before loadPreferences answers. Signing out also clears the localStorage keys of the auto-open reply draft and after-remove preferences, which the next sign-in otherwise started from. Two tests that had a composer survive an identity change now assert that it is closed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
maathimself
approved these changes
Oct 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An expired session signs out without reloading the page.
request()dispatchesmailflow:session_expiredfor any 401 outside/auth/(api.js#L27-L29), five wrong PINs on the lock screen end the session the same way (api.js#L158-L164), and App.jsx answers withsetUser(null)andsetLocked(false)(App.jsx#L27). When the user changes,setUserresets only the unread counts and the sender favicons (store/index.js#L110-L133). So whoever signs in next in that tab is shown the previous user's open compose, with its recipients, subject and quoted text (MailApp.jsx#L920), and their message list, search and message windows until their own data loads. Settings held only in memory stay the previous user's untilloadPreferencesreplaces them (store/index.js#L1143), and the ones it sets only when the new user has saved them (#L1211-L1234) carry over for the whole session: remote images load if the previous user allowed them, their image whitelist, hidden folders and shortcuts apply, and their auto-lock timer can lock a user who has no PIN to unlock with. After a lockout,setLocked(false)also restores the message that was open when the previous user locked the screen (store/index.js#L181-L184).Sign out from the sidebar and from the lock screen reload the page through the shared
signOuthelper (#523), so this is about the paths that do not reload. One gap on the reload path too: the two newer reading preferences, auto-opening reply drafts and what opens after a removal, are seeded from localStorage when the store is created (store/index.js#L472,#L733-L734) but are not onSIGN_OUT_CLEARED_KEYS(signOut.js#L8-L14), so the next sign-in starts from the previous user's choice.Changes
store/index.js: when a signed-in user leaves, that issetUserwith another id or with null, the store also resets what locking clears (messages, search, the selection, threads, accounts, folders, notifications, backfill progress, GTD sections and category counts), the compose state, message windows and reply-draft lookups, and the settings held only in memory (enabledPlugins,aiActions,blockRemoteImages,imageWhitelist,hiddenFolders,shortcuts,autoLockMinutes,categorizationEnabled,gtdPetSlug,autoOpenReplyDrafts,afterRemove). It also removesmailflow_locked_message, so the lock clear that follows a lockout has nothing to restore. A cold load goes from no user to one and resets nothing, so the two preferences seeded from localStorage still apply beforeloadPreferencesanswers. An update to the same user, as Settings does after a PIN or 2FA change (AdminPanel.jsx#L7771), resets nothing.replyDraftRevisionis left alone: lookups and handoffs in flight already stop on the user id.utils/signOut.js:mailflow_auto_open_reply_draftsandmailflow_after_removejoinSIGN_OUT_CLEARED_KEYS. Appearance settings are still kept (theme matching login screen #208).store/signOutState.test.jsdrives the store the way App.jsx handles a 401, then signs in a second user: the compose left open is gone; every mailbox field and every in-memory setting in the reset is seeded with the first user's data and must equal the store's initial value fromuseStore.getInitialState(), so a field dropped from the reset, or a value that drifts from the default, fails by name; the message open at the lock is cleared on the lockout path; a same-user update keeps the compose and the accounts; a cold load keeps the two seeded preferences.utils/signOut.test.jsgains a case for the two keys. Two existing tests asserted that a composer survives an identity change (InboxReplyDraftObserver.test.js, the ownership cases, andComposeModal.replyDraft.test.js, "a mounted draft save response cannot update a different signed-in owner"); they now assert that it is closed, which is the point of the change.Behaviour changes:
loadPreferencesfinishes, the same as on a cold load.Not part of this PR:
loadPreferencesstays applied after an expired-session sign-out until the page reloads.Testing
main, five of the seven new tests fail:composingis still true, the first user's account is still listed, the selected message is stillm1,enabledPluginsis still['gtd'], andmailflow_after_removesurvives the sign-out. The same-user and cold-load tests pass before and after; they guard against resetting too eagerly. Without themailflow_locked_messageremoval, the lockout test fails becausesetLocked(false)restoresm1. With the reset on every identity change rather than on a leaving user, the cold-load test fails. The two adapted tests fail in their original form against the fix, three cases.npm test2809/2809,npm run lintclean,npm run buildsucceeds.Contributor License Agreement
By submitting this pull request I confirm that:
third-party material and confirmed it is compatible with the CLA).
🤖 Generated with Claude Code