fix(auth): wipe account-bound data on logout (closes #18) #30

Merged
starsetbyte merged 3 commits from fix/logout-clears-account-data into main 2026-06-21 14:18:30 +00:00
Owner

Problem

Logout cleared auth tokens (and, since a prior fix, the OAuth store) but left all account-bound caches in place. A second account signing in on the same device saw the first account's timeline, custom feeds, drafts, and notifications (issue #18).

Fix

New AccountDataCleaner, called from AuthRepository.logout(), wipes every account-bound store:

  • Room (clearAllTables): posts, feed cursors, drafts, notifications
  • Draft images on disk (ComposerRepository.deleteAllDraftImages) — these are files keyed by draft id, not Room rows
  • Cached feed preferences (FeedPreferencesRepository.clearLocalData) — including the in-memory cachedRawPreferences/cachedSavedItems snapshot, since the repo is a @Singleton that survives logout
  • Recent searches (SearchRepository.clearLocalData)

App-level settings (theme, dynamic color, OAuth-migration dismissal) live in SettingsRepository and are intentionally preserved — they belong to the device, not the account. The wipe is local-only, so it runs whether or not the server deleteSession succeeds.

Tests

  • AccountDataCleanerTest — verifies all four stores are wiped
  • LocalDataClearTest — both clearLocalData methods remove their keys and leave theme_mode intact
  • AuthRepositoryTest — logout invokes the cleaner
  • Extracted the in-memory FakePreferencesDataStore into a shared test helper

Full unit suite green except the pre-existing, unrelated NotificationsRepositoryTest failure (fixed separately in PR #29).

Known follow-ups (out of scope)

  • Per-DID scoping for fast multi-account switching — the issue's "better" option; this PR is the immediate safety fix.
  • Session-expiry path: TokenAuthenticator's forced logout calls SessionManager.clearSession() directly and does not route through logout(), so an expired session followed by a different account login isn't covered yet. Best addressed together with per-DID scoping (clear-on-account-change at the login boundary), which also cleanly covers the OAuth login path (it bypasses AuthRepository via OAuthLoginViewModel).

🤖 Generated with Claude Code

https://claude.ai/code/session_0151pbAku7AeA9fgzWKVQLPE

## Problem Logout cleared auth tokens (and, since a prior fix, the OAuth store) but left **all account-bound caches** in place. A second account signing in on the same device saw the first account's timeline, custom feeds, drafts, and notifications (issue #18). ## Fix New `AccountDataCleaner`, called from `AuthRepository.logout()`, wipes every account-bound store: - **Room** (`clearAllTables`): posts, feed cursors, drafts, notifications - **Draft images on disk** (`ComposerRepository.deleteAllDraftImages`) — these are files keyed by draft id, not Room rows - **Cached feed preferences** (`FeedPreferencesRepository.clearLocalData`) — including the in-memory `cachedRawPreferences`/`cachedSavedItems` snapshot, since the repo is a `@Singleton` that survives logout - **Recent searches** (`SearchRepository.clearLocalData`) App-level settings (theme, dynamic color, OAuth-migration dismissal) live in `SettingsRepository` and are **intentionally preserved** — they belong to the device, not the account. The wipe is local-only, so it runs whether or not the server `deleteSession` succeeds. ## Tests - `AccountDataCleanerTest` — verifies all four stores are wiped - `LocalDataClearTest` — both `clearLocalData` methods remove their keys **and leave `theme_mode` intact** - `AuthRepositoryTest` — logout invokes the cleaner - Extracted the in-memory `FakePreferencesDataStore` into a shared test helper Full unit suite green except the pre-existing, unrelated `NotificationsRepositoryTest` failure (fixed separately in PR #29). ## Known follow-ups (out of scope) - **Per-DID scoping** for fast multi-account switching — the issue's "better" option; this PR is the immediate safety fix. - **Session-expiry path:** `TokenAuthenticator`'s forced logout calls `SessionManager.clearSession()` directly and does not route through `logout()`, so an expired session followed by a *different* account login isn't covered yet. Best addressed together with per-DID scoping (clear-on-account-change at the login boundary), which also cleanly covers the OAuth login path (it bypasses AuthRepository via OAuthLoginViewModel). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0151pbAku7AeA9fgzWKVQLPE
Logout cleared auth tokens but left all account-bound caches intact, so a
second account signing in on the same device saw the first account's
timeline, custom feeds, drafts, and notifications.

Add `AccountDataCleaner`, invoked from `AuthRepository.logout()`, which wipes:
- Room (`clearAllTables`): posts, feed cursors, drafts, notifications
- draft image files on disk (`ComposerRepository.deleteAllDraftImages`)
- cached feed preferences (`FeedPreferencesRepository.clearLocalData`,
  including the in-memory snapshot, since the repo is a @Singleton)
- recent searches (`SearchRepository.clearLocalData`)

App-level settings (theme, dynamic color, OAuth-migration dismissal) live in
SettingsRepository and are intentionally preserved — they belong to the
device, not the account. The wipe is local-only, so it runs whether or not
the server `deleteSession` succeeds.

Tests: AccountDataCleaner orchestration, both clearLocalData methods
(verifying theme survives the wipe), and logout calling the cleaner.
Extracts the in-memory FakePreferencesDataStore into a shared test helper.

Known follow-ups (out of scope): per-DID scoping for fast multi-account
switching (the issue's "better" option), and clearing on session expiry
(TokenAuthenticator's forced logout doesn't route through logout()).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151pbAku7AeA9fgzWKVQLPE
Merge branch 'main' into fix/logout-clears-account-data
Some checks failed
peregrine-ci / assembleDebug Build failed (7s)
8c35303817
fix(feeds): align clearLocalData with snapshot-based FeedPreferencesRepository
All checks were successful
peregrine-ci / assembleDebug Build succeeded (25s)
9035633ad1
Resolves a semantic merge conflict with main: #18 was branched before the
FeedPreferencesRepository rework that replaced the in-memory
cachedRawPreferences/cachedSavedItems fields with per-call server snapshots.
clearLocalData still referenced the removed fields, breaking the build when
CI merged main into the PR branch.

Drop the dead field resets — clearing the persisted pinned/saved feed keys is
sufficient now that preferences are re-fetched on the next refresh. Also remove
the duplicate FakePreferencesDataStore that main's FeedPreferencesRepositoryTest
carried, reusing the shared test helper this PR introduced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0151pbAku7AeA9fgzWKVQLPE
Sign in to join this conversation.
No description provided.