Skip to content

fix(auth): guard account switch against slot→token desync#506

Open
shukiv wants to merge 2 commits into
bulwarkmail:mainfrom
shukiv:fix/account-switch-identity-guard
Open

fix(auth): guard account switch against slot→token desync#506
shukiv wants to merge 2 commits into
bulwarkmail:mainfrom
shukiv:fix/account-switch-identity-guard

Conversation

@shukiv

@shukiv shukiv commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Problem

On account switch, the target client connects with the token at the account's stored cookieSlot. If that slot→token mapping is ever wrong — corrupted client state persisted by an older build, or any future slot desync — the connection succeeds as a different account and the UI silently shows the wrong mailbox. Observed in the wild: switching repeatedly kept showing another account's mail; only a full site-data wipe + re-login cleared it.

Fix

Post-connect identity guard in switchAccount: derive the connected session's accountId (primary-identity email for OAuth, else the JMAP session username) and compare to the account being switched to. On mismatch: drop the poisoned slot cookies, force a clean re-auth — never bind a session whose identity doesn't match.

Belt-and-suspenders on top of 8b164c5 (per-account cookie slot through OAuth flows): that prevents new corruption at allocation time; this catches any residual/leftover mapping at switch time and converts "silently wrong mailbox" into "re-login".

Notes

  • Guard returns null (non-blocking) when identity can't be determined — never wedges a switch on a transient identities-fetch failure.
  • Canonicalisation matters: comparing the raw JMAP username would false-positive on OAuth (preferred_username ≠ email). Unit test added for exactly that.

Test

stores/__tests__/auth-store-switch-guard.test.ts — 3 cases (email-derived id, JMAP-username fallback, null when undeterminable). Typecheck clean.

When switching accounts, the target client connects with the token at
the account's stored cookieSlot. If that slot→token mapping is ever
wrong — e.g. corrupted client state persisted by an older build, or any
future slot desync — the connection succeeds as a *different* account
and the UI silently shows the wrong mailbox.

Add a post-connect identity guard: derive the connected session's
accountId (primary-identity email for OAuth, else the JMAP session
username) and compare it to the account being switched to. On mismatch,
drop the poisoned slot cookies and force a clean re-auth instead of
binding the wrong session.

This is belt-and-suspenders on top of 8b164c5, which fixed the slot
allocation that caused such a desync: that prevents new corruption,
this catches any residual/leftover mapping at switch time.

Adds a unit test for the canonicalisation (email vs JMAP username), the
make-or-break detail that avoids OAuth false-positives.
Accounts whose primary sending identity differs from their login (basic
auth registers accountId from the typed login; OAuth from the identity
email) were force-re-authed on switch because the guard derived the
connected id only from the primary-identity email. Collect every
server-confirmed identifier (JMAP Session.username + primary-identity
email) and only re-auth when the target matches none. Excludes the
constructor username so a real desync still trips. Adds
JMAPClient.getSessionUsername().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant