fix(auth): use real bcrypt hash for anti-enumeration dummy verify

The hardcoded placeholder '$2b$12$abc...' wasn't a valid bcrypt
hash (checksum had wrong base64 length), so passlib raised
ValueError on every user-not-found login attempt:

    ValueError: malformed bcrypt hash (checksum must be ...)

Replace with hash_password('dummy-not-a-real-password') computed
once at module import. Same constant-time intent (verify cost is
identical to a real password check) but actually valid input.
This commit is contained in:
2026-05-12 19:33:08 +02:00
parent ba66964761
commit 1b50cd2cc5
+8 -2
View File
@@ -13,7 +13,7 @@ from datetime import datetime, timedelta
from sqlalchemy.orm import Session
from app.auth.jwt_handler import verify_password
from app.auth.jwt_handler import hash_password, verify_password
from app.auth.strategies.base import AuthError, AuthStrategy, ExternalIdentity
from app.models.user import AuthProvider, User
@@ -22,6 +22,12 @@ logger = logging.getLogger(__name__)
ACCOUNT_LOCKOUT_THRESHOLD = 5
ACCOUNT_LOCKOUT_DURATION_MIN = 15
# Pre-computed valid bcrypt hash used in the user-not-found branch so that
# verify cost is constant-time regardless of whether the username exists.
# Generated once at module import. The plaintext is irrelevant — it is never
# the user's secret and cannot match any real password.
_DUMMY_HASH = hash_password("dummy-not-a-real-password")
class LocalAuthStrategy(AuthStrategy):
"""
@@ -45,7 +51,7 @@ class LocalAuthStrategy(AuthStrategy):
if user is None or user.auth_provider != AuthProvider.LOCAL:
# Constant-time dummy verify to prevent timing-based enumeration.
verify_password("dummy", "$2b$12$abcdefghijklmnopqrstuvwxyz0123456789012345678901AB")
verify_password("anything", _DUMMY_HASH)
raise AuthError(
detail=f"unknown user '{username}'"
if user is None