diff --git a/stores/__tests__/auth-store-archive-restore.test.ts b/stores/__tests__/auth-store-archive-restore.test.ts index 50d429a84..47f51b689 100644 --- a/stores/__tests__/auth-store-archive-restore.test.ts +++ b/stores/__tests__/auth-store-archive-restore.test.ts @@ -95,3 +95,27 @@ it('stops waiting once the account is signed in', () => { expect(alice).toMatchObject({ label: 'Alice', avatarColor: '#16a34a', isConnected: true }); expect(alice?.awaitingSignIn).toBeUndefined(); }); + +it('signs out to an account that is signed in, not to one awaiting sign-in', async () => { + restoreAccounts(archived); + useAccountStore.setState((s) => ({ + accounts: [...s.accounts, { ...signedIn, id: 'carol@x.test@mail.x.test', username: 'carol@x.test', email: 'carol@x.test', label: 'Carol', cookieSlot: 2, isDefault: false }] as never, + })); + await useAuthStore.getState().checkAuth(); + // Wait for the background restore of the other remembered account. + await vi.waitFor(() => expect(useAccountStore.getState().getAccountById('carol@x.test@mail.x.test')?.isConnected).toBe(true)); + + await useAuthStore.getState().logout(); + expect(useAuthStore.getState().activeAccountId).toBe('carol@x.test@mail.x.test'); + expect(useAuthStore.getState().isAuthenticated).toBe(true); + expect(useAccountStore.getState().accounts.map(a => a.id)).toEqual(['alice@x.test@mail.x.test', 'carol@x.test@mail.x.test']); +}); + +it('treats a browser left with only accounts awaiting sign-in as signed out, not as a connection failure', async () => { + useAccountStore.setState({ accounts: [{ ...signedIn, rememberMe: false }] as never }); + restoreAccounts(archived.slice(1)); + await useAuthStore.getState().checkAuth(); + expect(useAuthStore.getState().isAuthenticated).toBe(false); + expect(useAuthStore.getState().error).not.toBe('connection_failed'); + expect(useAccountStore.getState().accounts.map(a => a.id)).toEqual(['alice@x.test@mail.x.test']); +}); diff --git a/stores/auth-store.ts b/stores/auth-store.ts index eb8076bfe..5a79f6ba4 100644 --- a/stores/auth-store.ts +++ b/stores/auth-store.ts @@ -2112,7 +2112,13 @@ export const useAuthStore = create()( // Check if there are remaining accounts to switch to. Read the store // afresh: `accountStore` is the state from before removeAccount(). - const nextAccount = wasDemoMode ? undefined : useAccountStore.getState().accounts[0]; + // Prefer an account that is signed in here: one restored from the + // account list backup (or otherwise not connected) has no client, and + // picking it signs out every account left. + const remainingAccounts = useAccountStore.getState().accounts; + const nextAccount = wasDemoMode + ? undefined + : remainingAccounts.find((a) => clients.has(a.id)) ?? remainingAccounts[0]; const droppedAccounts: AccountEntry[] = []; if (nextAccount) { @@ -2762,8 +2768,13 @@ export const useAuthStore = create()( } } - // No accounts could be restored - if (accounts.some((account) => accountStore.getAccountById(account.id))) { + // No accounts could be restored. Accounts restored from the account + // list backup were never signed in here, so they say nothing about + // the connection. + if (accounts.some((account) => { + const entry = accountStore.getAccountById(account.id); + return entry && !entry.awaitingSignIn; + })) { set({ isAuthenticated: false, isLoading: false,