From 2c40bc67091f1c0ef141244e875c57de4999fc2c Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 31 Oct 2026 20:36:40 +0100 Subject: [PATCH] fix: forget an account's saved sender identities when it is signed out --- src/lib/__tests__/identity-cache.test.ts | 10 +++++++++- src/lib/identity-cache.ts | 7 +++++++ src/stores/auth-store.ts | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/lib/__tests__/identity-cache.test.ts b/src/lib/__tests__/identity-cache.test.ts index 9bd5010f..6da1040e 100644 --- a/src/lib/__tests__/identity-cache.test.ts +++ b/src/lib/__tests__/identity-cache.test.ts @@ -5,7 +5,7 @@ import fs from 'node:fs'; import path from 'node:path'; import AsyncStorage from '@react-native-async-storage/async-storage'; import type { Identity } from '../../api/types'; -import { loadComposerIdentities, readCachedIdentities } from '../identity-cache'; +import { forgetCachedIdentities, loadComposerIdentities, readCachedIdentities } from '../identity-cache'; const me = { id: 'i1', name: 'User A', email: 'usera@example.org', mayDelete: false } as Identity; const offline = () => Promise.reject(new Error('Network request failed')); @@ -15,6 +15,14 @@ beforeEach(async () => { }); describe('composer identities', () => { + it('drops the kept identities when the login leaves the device', async () => { + await loadComposerIdentities('usera@host', async () => [me]); + await forgetCachedIdentities('usera@host'); + expect(await readCachedIdentities('usera@host')).toEqual([]); + const auth = fs.readFileSync(path.resolve(__dirname, '../../stores/auth-store.ts'), 'utf8'); + expect(auth).toMatch(/async function forgetRemovedAccount\(accountId: string\): Promise \{\s*void forgetCachedIdentities\(accountId\);/); + }); + it('keeps what the server said for the login', async () => { const result = await loadComposerIdentities('usera@host', async () => [me]); expect(result).toEqual({ identities: [me], fresh: true, error: null }); diff --git a/src/lib/identity-cache.ts b/src/lib/identity-cache.ts index 597c1c55..8d2791e8 100644 --- a/src/lib/identity-cache.ts +++ b/src/lib/identity-cache.ts @@ -27,6 +27,13 @@ export async function readCachedIdentities(localAccountId: string | null | undef } } +/** Drop the identities kept for a login that left this device. */ +export async function forgetCachedIdentities(localAccountId: string): Promise { + try { + await AsyncStorage.removeItem(keyFor(localAccountId)); + } catch { /* best effort */ } +} + /** Keep what the server just said, for the next composer opened offline. */ export async function writeCachedIdentities(localAccountId: string | null | undefined, identities: Identity[]): Promise { if (!localAccountId) return; diff --git a/src/stores/auth-store.ts b/src/stores/auth-store.ts index cc31ab97..07380e49 100644 --- a/src/stores/auth-store.ts +++ b/src/stores/auth-store.ts @@ -1,3 +1,4 @@ +import { forgetCachedIdentities } from '../lib/identity-cache'; import { create } from 'zustand'; import { jmapClient, @@ -176,6 +177,7 @@ function clearDeviceWideAccountData(): void { // until nobody is signed in. When it was the last account, the device-wide // data goes too. async function forgetRemovedAccount(accountId: string): Promise { + void forgetCachedIdentities(accountId); try { const feeds = useCalendarSubscriptionsStore; // On a cold start the stored list may not have been read yet; written