fix: login endpoint - request variable shadowing Starlette Request
The login() function used 'request: LoginRequest' which shadowed FastAPI's Starlette Request object. Request.client was accessed on the LoginRequest Pydantic model instead of the HTTP request, causing AttributeError: 'LoginRequest' object has no attribute 'client'. Fix: rename the Pydantic parameter to 'body' and add explicit 'request: Request' for IP extraction and rate limiting.
This commit is contained in:
parent
17eea0559d
commit
2469026c1d
@ -89,14 +89,14 @@ async def auth_status():
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
async def login(request: LoginRequest, response: Response):
|
||||
async def login(body: LoginRequest, response: Response, request: Request):
|
||||
"""Authenticate a user. Returns access token and sets refresh cookie.
|
||||
|
||||
Implements timing-safe responses to prevent user enumeration:
|
||||
a failed login with an unknown user takes the same time as one
|
||||
with a known user (dummy hash is computed).
|
||||
"""
|
||||
user = get_user(request.username)
|
||||
user = get_user(body.username)
|
||||
|
||||
if not user:
|
||||
# Timing-safe: simulate hash computation to prevent user enumeration
|
||||
@ -111,11 +111,11 @@ async def login(request: LoginRequest, response: Response):
|
||||
if is_rate_limited(client_ip):
|
||||
raise HTTPException(429, "Trop de tentatives depuis cette adresse IP (15min)")
|
||||
|
||||
if is_locked(request.username):
|
||||
if is_locked(body.username):
|
||||
raise HTTPException(429, "Compte temporairement verrouillé (15min)")
|
||||
|
||||
if not verify_password(request.password, user["password_hash"]):
|
||||
attempts = record_login_failure(request.username)
|
||||
if not verify_password(body.password, user["password_hash"]):
|
||||
attempts = record_login_failure(body.username)
|
||||
rl_attempts, rl_remaining = rl_record_failure(client_ip)
|
||||
remaining = max(0, 5 - attempts)
|
||||
detail = "Identifiants invalides"
|
||||
@ -124,14 +124,14 @@ async def login(request: LoginRequest, response: Response):
|
||||
raise HTTPException(401, detail)
|
||||
|
||||
# Success — clear rate limits and generate tokens
|
||||
record_login_success(request.username)
|
||||
record_login_success(body.username)
|
||||
rl_record_success(client_ip)
|
||||
|
||||
access_token = create_access_token(user)
|
||||
refresh_token, refresh_jti = create_refresh_token(request.username)
|
||||
refresh_token, refresh_jti = create_refresh_token(body.username)
|
||||
|
||||
# Set refresh token as HttpOnly cookie (path-restricted to /api/auth/refresh)
|
||||
max_age = 2592000 if request.remember_me else 604800 # 30d or 7d
|
||||
max_age = 2592000 if body.remember_me else 604800 # 30d or 7d
|
||||
import os
|
||||
secure = os.environ.get("OBSIGATE_SECURE_COOKIES", "false").lower() == "true"
|
||||
response.set_cookie(
|
||||
@ -144,7 +144,7 @@ async def login(request: LoginRequest, response: Response):
|
||||
path="/api/auth/refresh",
|
||||
)
|
||||
|
||||
logger.info(f"User '{request.username}' logged in")
|
||||
logger.info(f"User '{body.username}' logged in")
|
||||
|
||||
# Set access token as cookie for same-origin requests (e.g. popout window)
|
||||
response.set_cookie(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user