Accounts, credentials, per-account settings and signatures lived in the
same libremail.db that SQLCipher encrypts under the auth-bound passphrase
when app-lock + encrypted-cache are on. A genuine key invalidation
(biometric re-enrollment or lock removal/re-add) made that file
undecryptable, and the "clear + re-sync" recovery wiped the accounts and
stored credentials along with the mail cache, dropping the user into
onboarding (issue #111).
Move those four tables into a new plaintext AccountDatabase
(libremail-accounts.db) that is never bound to the auth key. Credentials
stay AES-GCM sealed at the column level by the surviving non-auth
KeystoreCrypto master key, so the only secret never touches disk in the
clear. A cache-key invalidation now wipes only libremail.db; the user
stays signed in.
- AccountDatabase (v1) + AccountDatabaseModule; the cache DB drops to v15
via MIGRATION_14_15. DAOs are unchanged and re-provided from the new DB,
so no injection site changes.
- AccountDataMigrator performs the one-time cross-DB copy at startup,
before Room opens either database. It attaches the cache (with its
resolved passphrase, so an encrypted source is handled) and copies with
INSERT OR IGNORE. It is crash-safe and idempotent: the source is dropped
only by MIGRATION_14_15 after the copy, a re-run never duplicates or
overwrites, and it runs after the clear-pending wipe so an unrecoverable
cache degrades to "nothing to move" instead of blocking.
- Exported schemas for both databases; MigrationTest asserts the account
rows/backfills survive to v14 then are dropped at v15, plus a dedicated
14->15 test. AccountDataMigratorTest covers the plaintext + encrypted
copy, idempotency, a DDL-vs-Room drift guard, and end-to-end survival of
a simulated cache wipe.
Closes#111
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>