The multi-account refresh-token cookie slot wiring was half-implemented:
every account's refresh token ended up on slot 0, so "+ Add Account"
silently clobbered the previous account's `jmap_rt` cookie. On page
refresh, only the most-recently-added account had a working refresh
token; the others bounced to login.
Three coordinated changes:
1. `app/[locale]/login/page.tsx` (handleOAuthLogin): write the next-free
cookie slot to `sessionStorage['oauth_cookie_slot']` before redirecting
to the IdP. `loginWithOAuth` already reads this key but it was never
written, so it always defaulted to 0.
2. `stores/auth-store.ts` (loginWithOAuth): distinguish "no value set"
(`rawSlot === null`) from "value is 0". Previously
`parseInt(getItem(...) || '0')` collapsed both cases, making the
`getNextCookieSlot()` fallback unreachable.
3. `stores/auth-store.ts` (loginWithServerSso) +
`app/api/auth/sso/complete/route.ts`: pass the slot through the body of
the POST and use it for `refreshTokenCookieName(slot)`. Same pattern as
the existing `/api/auth/token POST` that already accepts a slot. The
server defaults to 0 for back-compat with any caller that omits it.
After the fix, signing in with multiple accounts produces distinct
`jmap_rt`, `jmap_rt_1`, `jmap_rt_2`, ... cookies (matching the cookieSlot
field in account-store) and all accounts survive a page refresh.
Repro before the fix:
- Sign in with one account, refresh — works.
- Click "+ Add Account", sign in with a second account, refresh — second
account vanishes from the dropdown; switching to the first account in
the dropdown still shows the second account's identity in the From box.
- Add AccountSwitcher component for managing user accounts with UI for switching, adding, and logging out.
- Create account state manager to handle snapshots of account-specific states for efficient switching.
- Introduce utility functions for account management, including ID generation and avatar color assignment.
- Implement Zustand store for account management, supporting addition, removal, and state retrieval of accounts.
Add opt-in SSO authentication alongside Basic Auth. OAuth endpoints are
auto-discovered via .well-known, with support for external IdPs
(Keycloak, Authentik) via configurable OAUTH_ISSUER_URL. Sessions
persist through httpOnly refresh token cookies with automatic renewal.