fix(auth): close the account pre-emption + wrong-account link holes (E1 S5, SEC-10/11)
SEC-10 (account pre-emption / "pre-hijack"): 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. Both the Google-adoption callback and admin activation (_activate_user_by_email) now drop that pre-set password and bump the session epoch when the account isn't email_verified, so a later Google login / admin approval can't hand the pre-set password a working login. No-op for a verified account (the legit no-SMTP flow). +2 tests. SEC-11 (link/upgrade bound to the session default): the OAuth link/upgrade GETs resolved to the session DEFAULT account, so a tab acting as a secondary account would link Google / the YouTube grant to the wrong account. The frontend now threads the active account through the navigation URL (withActiveAccount -> ?account=<active>), and _complete_link accepts any wallet account, not only the default, so the identity lands on the account that started the flow.
This commit is contained in:
@@ -19,6 +19,7 @@ from app.auth import (
|
||||
)
|
||||
from app.db import get_db
|
||||
from app.demo import reset_demo_state
|
||||
from app.routes.auth import bump_session_epoch
|
||||
from app.models import (
|
||||
DemoWhitelist,
|
||||
Invite,
|
||||
@@ -62,6 +63,13 @@ def _activate_user_by_email(db: Session, email: str) -> None:
|
||||
it can sign in. No-op if no such user exists yet (a Google user is created at first login)."""
|
||||
user = db.execute(select(User).where(User.email == email.lower())).scalar_one_or_none()
|
||||
if user is not None and not user.is_active:
|
||||
# SEC-10: same guard as the Google-adoption path — approving an account whose email was never
|
||||
# verified must not activate a password set on it by whoever registered the address (a
|
||||
# pre-emption). Drop the untrusted password + revoke any session it minted; the owner re-sets
|
||||
# one via password-reset. (No-op when email_verified is True, e.g. legit no-SMTP registrations.)
|
||||
if not user.email_verified:
|
||||
user.password_hash = None
|
||||
bump_session_epoch(user)
|
||||
user.is_active = True
|
||||
db.commit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user