fix(auth): a tab acts only as the account it was built for, and the review's other findings
A tab names the account it was claimed for on every request, beacon and upload (X-Siftlode-Expect-Account); current_user and /auth/logout refuse a mismatch with 401 account_changed and keep the session, so a tab left showing the account signed in before reloads instead of writing to the new one. /auth/login clears a connect marker left by an abandoned connect, and a link completes only on a session that still holds. The prefs merge reads the row under a lock. A tab's sessionStorage records its own owner. A page the account loses is given up, address bar and remembered page included. The panel layouts are preferences; the storage contract also refuses a key handed to the Storage API or to usePref outside its kind. A refused sign-in as someone else and a failed sign-out say so. The chromium-only e2e specs are in the gate register, the member walk waits for its requests, and devices.spec checks the save itself.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
"""PUT /api/me/preferences merges the keys it is sent into the stored ones, DB-lane.
|
||||
|
||||
A client saves its preferences a key at a time, and several at once on the boot that moves this
|
||||
browser's copies to the account. Each merge must start from the row as stored when it runs, not as
|
||||
the request first read it — two merges over the same earlier read keep only the last one's keys.
|
||||
"""
|
||||
|
||||
from fastapi import Depends, Request
|
||||
from sqlalchemy import update
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.auth import current_user
|
||||
from app.db import SessionLocal, get_db
|
||||
from app.models import User
|
||||
|
||||
_PW = "correct horse battery"
|
||||
|
||||
|
||||
def test_a_save_keeps_a_key_another_save_stored_after_this_one_read_the_account(
|
||||
client, db, make_user, override_dep
|
||||
):
|
||||
user = make_user(email="prefs@example.com", password=_PW)
|
||||
assert (
|
||||
client.post("/auth/password-login", json={"email": "prefs@example.com", "password": _PW})
|
||||
).status_code == 200
|
||||
|
||||
def read_then_another_save(request: Request, session: Session = Depends(get_db)) -> User:
|
||||
# The account as this request reads it, then a second save committing in the meantime.
|
||||
caller = current_user(request, session)
|
||||
with SessionLocal() as other:
|
||||
other.execute(
|
||||
update(User)
|
||||
.where(User.id == caller.id)
|
||||
.values(preferences={"seenVersion": "1.2.3"})
|
||||
)
|
||||
other.commit()
|
||||
return caller
|
||||
|
||||
override_dep(current_user, read_then_another_save)
|
||||
r = client.put("/api/me/preferences", json={"startOnHome": False})
|
||||
|
||||
assert r.status_code == 200
|
||||
db.expire_all()
|
||||
assert db.get(User, user.id).preferences == {"seenVersion": "1.2.3", "startOnHome": False}
|
||||
Reference in New Issue
Block a user