Multi-Account Support — Design Proposal #16
saknarajapakshe
started this conversation in
Ideas
Replies: 2 comments 2 replies
|
@saknarajapakshe, What do you mean by
In the non-goal section? Can you elaborate a bit? |
2 replies
@saknarajapakshe, please take this feedback and open issues, and you can start implementing this. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
TL;DR
Quicksilver currently supports exactly one email account per device. This proposal adds support for linking multiple accounts and switching between them, in a way that doesn't block a later move to OAuth.
LinkedAccount) from account session (AccountSession) as separate types — they have different lifecycles.AuthContext(auth mechanics, password today, OAuth-ready later) +AccountContext(linked accounts, sessions, switching).window.opentarget — no custom cross-tab protocol.Jump to Section 11 – Decisions for the settled calls, or Feedback wanted for open questions.
Contents
Note
The links above use explicit anchors rather than GitHub auto-generated heading slugs, since auto-slug anchors aren’t always reliably clickable inside Discussion posts.
1. Goal
Today Quicksilver supports exactly one email account per device/browser.
A user registers with one email + app password (or IMAP/SMTP app-specific credential), and the app hard-blocks login with a different address on the same device (
AuthContext.tsx,login()throws"wrong_email").We want to:
This proposal covers UI + client-side account management only.
It documents backend implications for future OAuth cohesion, but backend OAuth implementation itself is out of scope for now.
Non-goals
2. Current State (what exists today)
A large part of the groundwork already exists.
currentUser, single JWT ref, singleMailProfileinlocalStorage["quicksilver_profile"]src/nonview/core/AuthContext.tsxAuthContext.tsx(login(), ~line 186)session_id); store ismap[sessionID]*Session, not user-keyedserver/internal/session/store.go,server/internal/auth/jwt.goAPIClientinAuthProvider, token viagetTokenclosuresrc/nonview/api/client.tsaccounton rows; includesclearAccount(account)src/nonview/cache/db.tsThread/ThreadMessagehave noaccountId; scoping happens at cache layersrc/nonview/core/DataContext.tsx/profile; no dropdown/menu pattern exists yetsrc/view/moles/Sidebar.tsx,src/view/moles/Header.tsxImportant
Cache is already multi-account-friendly. The real gap is:
AuthContext(single-account assumptions),3. Design Principles
AuthContext.APIClientat the selected account’s token.authMethodnow so OAuth later is additive.4. Data Model Changes
4.1 Replace single
MailProfilewith account list; split Account vs SessionLinkedAccount(identity + server endpoints) andAccountSession(auth session token) are separate because they have different lifecycles.TypeScript shapes
Stored as:
localStorage["quicksilver_accounts"]→LinkedAccount[]localStorage["quicksilver_sessions"]→AccountSession[]Passwords are never stored.
Warning
quicksilver_sessionsincreases XSS blast radius (multiple account tokens in one storage boundary).This is an amplification of existing localStorage trust assumptions, not a new trust model.
Mitigation remains strict CSP + sanitized HTML paths (e.g., DOMPurify).
4.2 Migration for existing users
Migration steps
On first load after update, if old keys exist and new account list doesn’t:
quicksilver_profileto one-itemLinkedAccount[].quicksilver_jwtto one-itemAccountSession[](required).sessionStorage["quicksilver_active_account"]with migrated account id.quicksilver_profile,quicksilver_jwt).4.3 Domain data (
Thread, cache)No domain type changes needed. Cache already scopes by account key.
5. Two Contexts:
AuthContext+AccountContextAuthContext: how credentials become valid sessions.AccountContext: which accounts are linked/active and how switching works.5.1
AuthContext— authentication mechanicsInterface + client handling
Notes:
apiClientfromAuthContext.login/registerlogout/reauthenticate5.2
AccountContext— linked accounts, sessions, switchingInterface + behavior
Behavior highlights:
addAccount/reauthenticatecallAuthContextthen persist session.switchAccountupdates active pointer; data hydrates from IndexedDB + background refresh.removeAccountlogs out session, clears cache, removes account/session entries.5.3 Account lifecycle
Linked/Authenticated/Expired/RemovedState diagrams
Persisted lifecycle:
Per-tab overlay:
6. UI Plan
6.1 Account switcher component
Create
src/view/moles/AccountSwitcher.tsxwith MUIMenu/MenuItem.Anchor to existing profile row in
Sidebar.tsx(desktop) and profile avatar inHeader.tsx(mobile).Menu includes:
Multi-tab model + switch sequence
Use named window target:
Same target name reuses/focuses existing tab/window for that account.
Storage split:
localStorage["quicksilver_accounts"]→ shared profileslocalStorage["quicksilver_sessions"]→ shared sessions/tokenssessionStorage["quicksilver_active_account"](or?account=id) → tab-local active accountswitchAccount(id)sequence:6.2 Reuse Login/Registration for “Add account”
Use
?mode=addso flow callsaddAccount()instead of replacing current account session context.6.3 Per-account visual identity (optional)
Nice-to-have only (distinct color/avatar per account).
7. Realtime Sync (SSE / IMAP IDLE) Across Multiple Accounts
Decision: every authenticated linked account stays live, even when not currently displayed.
At ~2.5 accounts/user and 5,000 concurrent users:
Tradeoff: ~2.5× backend steady-state load for simpler switching + live unread badges across all accounts.
Design impact
switchAccount()requires no reconnect/teardown choreography.Revisit if account/user average or provider connection caps become problematic.
8. Backend / API — now vs later
Needed now: none.
Current backend session architecture already supports concurrent sessions.
Frontend only needs multi-session storage + active routing.
Note
Session store is in-memory; restart invalidates all sessions (already true today).
Impact is larger per user once multiple accounts are linked.
9. Future-Proofing for OAuth (not implementing yet)
LinkedAccount.authMethodis the extension seam.Future backend work for OAuth
/api/v1/auth/oauth/*endpoints10. Phased Implementation Plan
Phase 1 — Data & context layer
LinkedAccount[]+AccountSession[]storage and migration.AuthContextandAccountContext.Phase 2 — Switcher UI + always-on per-account realtime
AccountSwitcher, wire into Sidebar/Header.Phase 3 — Lifecycle polish
Phase 4 — Optional niceties
Phase 5 — OAuth (separate initiative)
11. Decisions
AuthContextandAccountContextseparate; keepLinkedAccountandAccountSessionseparate.Feedback wanted
LinkedAccount,AccountSession,AccountContext?All reactions