fix(auth): address SA4 review findings (WS epoch check, commit ordering, verify guard)

Adversarial re-review of the session-epoch work surfaced:
- WS auth (messages_ws) skipped the epoch check, so a revoked-but-unexpired cookie
  could still open the live push channel after a reset/logout-others. Now mirrors
  current_user: loads the user once, rejects a stale-epoch cookie before connecting.
- set_password + logout_others re-stamped the cookie BEFORE db.commit(); a failed
  commit would strand the current session at a newer epoch than the DB and wrongly
  401 it. Commit first, then re-stamp.
- Welcome verify effect could double-POST the single-use token (StrictMode/remount)
  and flip the banner to a false 'invalid'. Fire-once useRef guard.

Left as-is (low value, documented): the Plex image proxy authenticates without a DB
load / epoch check (poster/art fetches only); adding one would cost a DB hit per image.
This commit is contained in:
npeter83 2026-07-12 03:07:13 +02:00
parent 95d1549570
commit 07dba4da9e
3 changed files with 20 additions and 7 deletions

View file

@ -809,9 +809,11 @@ def logout_others(
in somewhere" / a suspected session compromise. The demo account (shared) is excluded."""
if user.is_demo:
raise HTTPException(status_code=403, detail="Not available in the demo account.")
# Commit the bump BEFORE re-stamping this cookie, so a failed commit can't strand the current
# session at a newer epoch than the DB (see set_password).
bump_session_epoch(user)
_remember_epoch(request, user)
db.commit()
_remember_epoch(request, user)
log.info("Logout-others: uid=%s (epoch=%s)", user.id, user.session_epoch)
return {"ok": True}
@ -892,10 +894,11 @@ def set_password(
raise HTTPException(status_code=403, detail="Current password is incorrect.")
user.password_hash = hash_password(new_password)
# SA4: a password change logs out every OTHER session; re-stamp this cookie at the new epoch so
# the person making the change stays signed in here.
# the person making the change stays signed in here. Commit BEFORE re-stamping so a failed commit
# can't leave the cookie at a newer epoch than the DB (which would wrongly 401 THIS session).
bump_session_epoch(user)
_remember_epoch(request, user)
db.commit()
_remember_epoch(request, user)
log.info("Password set/changed: uid=%s", user.id)
return {"ok": True}