feat(auth): per-tab account — different account per browser tab
Two tabs in one browser can now run two different signed-in accounts at once. - The signed session cookie stays the browser's account WALLET (account_ids). Which account a given tab acts as is a per-tab choice held in sessionStorage and sent per-request via the X-Siftlode-Account header; current_user honours it only for an account already in the wallet, without mutating the cookie's default account. Switching accounts sets the header + reloads THIS tab only, instead of the old cookie-wide switch that changed every tab. - WebSocket can't send headers, so the per-tab account rides in the ?account= query param (validated against the wallet). - Logout is per-tab aware: it signs the requesting tab's active account out of the wallet (promoting a new default only if the removed one was the default), and the tab drops its override. A stale per-tab header account 401s just that tab instead of clearing the session. - Serve index.html with Cache-Control: no-cache so a deploy's new hashed bundle is picked up immediately instead of the browser running a heuristically-cached stale index.html.
This commit is contained in:
parent
c0487101fe
commit
5cd807ec51
6 changed files with 146 additions and 36 deletions
|
|
@ -363,7 +363,19 @@ async def messages_ws(ws: WebSocket) -> None:
|
|||
"""Live push channel: authenticated by the session cookie, registers the connection so
|
||||
sent messages reach this user's open tabs instantly. We don't consume client→server frames
|
||||
(sending goes through POST /api/messages); the receive loop only detects disconnect."""
|
||||
uid = ws.session.get("user_id") if "session" in ws.scope else None
|
||||
# A browser WebSocket can't send custom headers, so the per-tab account (see
|
||||
# X-Siftlode-Account on HTTP) rides in the ?account= query param instead — honoured only for
|
||||
# an account already in this browser's wallet. Falls back to the session default.
|
||||
sess = ws.session if "session" in ws.scope else {}
|
||||
uid = sess.get("user_id")
|
||||
q = ws.query_params.get("account")
|
||||
if q:
|
||||
try:
|
||||
qid = int(q)
|
||||
except ValueError:
|
||||
qid = None
|
||||
if qid is not None and qid in (sess.get("account_ids") or []):
|
||||
uid = qid
|
||||
if not uid:
|
||||
await ws.close(code=1008)
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue