diff --git a/l4d2web/auth.py b/l4d2web/auth.py index 74688f4..718c8d8 100644 --- a/l4d2web/auth.py +++ b/l4d2web/auth.py @@ -27,7 +27,10 @@ def load_current_user() -> None: g.user = None return with session_scope() as db: - g.user = db.scalar(select(User).where(User.id == int(user_id))) + user = db.scalar(select(User).where(User.id == int(user_id))) + # Treat deactivated users as logged-out so existing sessions stop + # working as soon as an admin flips active=False. + g.user = user if user is not None and user.active else None def current_user() -> User | None: diff --git a/l4d2web/routes/auth_routes.py b/l4d2web/routes/auth_routes.py index 1f83a57..6a8559a 100644 --- a/l4d2web/routes/auth_routes.py +++ b/l4d2web/routes/auth_routes.py @@ -48,7 +48,9 @@ def login() -> Response: user = db.scalar(select(User).where(User.username == username)) digest = user.password_digest if user is not None else _TIMING_DUMMY_DIGEST password_ok = verify_password(password, digest) - if user is None or not password_ok: + if user is None or not password_ok or not user.active: + # Same generic response for missing user, wrong password, or + # deactivated account — no timing oracle for deactivation status. return Response("invalid credentials", status=401) login_user(user.id) LOGIN_ATTEMPTS_BY_IP.pop(remote_addr, None)