fix(e1): address /code-review high findings on the E1 stack
Review round on the whole E1 stack (main..HEAD). 6 findings, all low/medium, none blocking; fixed for a clean gate-green handoff. - plex/sync (correctness): the un-enriched cast guard compared the stored cast against THIS listing's length, so a trimmed Plex cast on a not-yet-enriched row was never applied. Compare against the listing CAP (_LISTING_CAST_CAP=20) instead: >cap = enriched (keep, survives a watermark reset), <=cap = listing-sourced (track edits in both directions). +2 regression tests. - main (security): add X-Frame-Options: DENY as the legacy-browser fallback for the CSP frame-ancestors clickjacking guard. - downloads/service (altitude): finalize_if_running now uses a SQLAlchemy 2.0 update() construct instead of a hand-built text() UPDATE string — same atomic WHERE status='running', idiomatic, no identifier interpolation, no manual StrEnum coercion. Drops the now-unused text import. - auth/admin (reuse): extract the SEC-10 "drop untrusted preset password + bump epoch" invariant into one helper (revoke_preset_password_if_unverified) so the Google-callback and admin-activation paths can't drift. - tests/migrations (robustness): guarantee the shared schema is restored to head in a finally, so a downgrade/re-upgrade failure can't cascade into later tests. - worker (simplification): align _finish_from_cache's display_name fill with the _download path's guard (no behavior change). Gate green: ruff clean, 334 pytest (0 skip), frontend unchanged.
This commit is contained in:
@@ -286,14 +286,9 @@ async def callback(
|
||||
return RedirectResponse(url="/?access=denied")
|
||||
user = existing
|
||||
user.google_sub = sub
|
||||
# SEC-10: a password set on this account BEFORE its email was ever verified is untrusted —
|
||||
# whoever registered the address (to pre-empt the real owner, who is only now arriving via
|
||||
# Google) could have chosen it. Drop it now that Google attests ownership, and bump the epoch
|
||||
# to revoke any session that pre-set password may have minted. The owner can set a fresh
|
||||
# password via password-reset later if they also want email+password sign-in.
|
||||
if not user.email_verified:
|
||||
user.password_hash = None
|
||||
bump_session_epoch(user)
|
||||
# SEC-10: drop any password pre-set on this account while its email was still unverified,
|
||||
# now that Google attests ownership (bumps the epoch to revoke any session it minted).
|
||||
revoke_preset_password_if_unverified(user)
|
||||
# Google verified the email and is_allowed granted access, so finish activating a
|
||||
# previously pending password registration (otherwise current_user would bounce it).
|
||||
user.email_verified = True
|
||||
@@ -817,6 +812,18 @@ def bump_session_epoch(user: User) -> None:
|
||||
user.session_epoch = (user.session_epoch or 0) + 1
|
||||
|
||||
|
||||
def revoke_preset_password_if_unverified(user: User) -> None:
|
||||
"""SEC-10 (account pre-emption): a password set on an account whose email was NEVER verified is
|
||||
untrusted — whoever registered the address (to pre-empt the real owner) could have chosen it. When
|
||||
ownership is finally attested (Google adoption, or admin approval), drop that password and bump the
|
||||
epoch to revoke any session it minted; the owner re-sets one via password-reset. No-op for an
|
||||
already-verified account, so a legit no-SMTP registration is unaffected. The single source for this
|
||||
rule — both the Google-callback and admin-activation paths call it so they can't drift."""
|
||||
if not user.email_verified:
|
||||
user.password_hash = None
|
||||
bump_session_epoch(user)
|
||||
|
||||
|
||||
@router.post("/logout-others")
|
||||
def logout_others(
|
||||
request: Request, user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||
|
||||
Reference in New Issue
Block a user