From cbbb41dfb196a1ef8eee3690dbdd0a5579814a8a Mon Sep 17 00:00:00 2001 From: Ashvin <76151462+ashvinctrl@users.noreply.github.com> Date: Sun, 7 Jun 2026 18:40:53 +0530 Subject: [PATCH] fix: avoid double bcrypt on login by using create_session_trusted (#3236) * fix: avoid double bcrypt on login by adding create_session_trusted * fix: update test to expect create_session_trusted instead of create_session --- core/auth.py | 6 ++++++ routes/auth_routes.py | 6 ++---- tests/test_auth_event_loop.py | 6 +++--- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/core/auth.py b/core/auth.py index ed083b008..011b1af2c 100644 --- a/core/auth.py +++ b/core/auth.py @@ -447,6 +447,12 @@ class AuthManager: username = username.strip().lower() if not self.verify_password(username, password): return None + return self.create_session_trusted(username) + + def create_session_trusted(self, username: str) -> str: + """Issue a session token for an already-verified user. + Call only after verify_password (and TOTP if enabled) have passed.""" + username = username.strip().lower() token = secrets.token_hex(32) with self._sessions_lock: self._sessions[token] = { diff --git a/routes/auth_routes.py b/routes/auth_routes.py index 96284e4d0..9379bced8 100644 --- a/routes/auth_routes.py +++ b/routes/auth_routes.py @@ -131,10 +131,8 @@ def setup_auth_routes(auth_manager: AuthManager) -> APIRouter: return {"ok": False, "requires_totp": True, "username": username} if not auth_manager.totp_verify(username, body.totp_code): raise HTTPException(401, "Invalid 2FA code") - # All checks passed — create session - token = await asyncio.to_thread(auth_manager.create_session, username, body.password) - if not token: - raise HTTPException(401, "Invalid credentials") + # All checks passed — create session (password already verified above) + token = await asyncio.to_thread(auth_manager.create_session_trusted, username) cookie_kwargs = dict( key=SESSION_COOKIE, value=token, diff --git a/tests/test_auth_event_loop.py b/tests/test_auth_event_loop.py index a53f57972..112e19d74 100644 --- a/tests/test_auth_event_loop.py +++ b/tests/test_auth_event_loop.py @@ -95,7 +95,7 @@ def test_login_offloads_bcrypt_bearing_calls(monkeypatch): monkeypatch.setattr("routes.auth_routes.asyncio.to_thread", fake_to_thread) auth.verify_password.return_value = True auth.totp_enabled.return_value = False - auth.create_session.return_value = "tok-123" + auth.create_session_trusted.return_value = "tok-123" login = _login_endpoint(auth) @@ -107,7 +107,7 @@ def test_login_offloads_bcrypt_bearing_calls(monkeypatch): assert result["ok"] is True auth.verify_password.assert_called_once() - auth.create_session.assert_called_once() + auth.create_session_trusted.assert_called_once() # The whole point: the expensive bcrypt-bearing calls go through # asyncio.to_thread rather than running inline in the request coroutine. - assert calls == [auth.verify_password, auth.create_session] + assert calls == [auth.verify_password, auth.create_session_trusted]