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")
|
@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.
|
"""Authenticate a user. Returns access token and sets refresh cookie.
|
||||||
|
|
||||||
Implements timing-safe responses to prevent user enumeration:
|
Implements timing-safe responses to prevent user enumeration:
|
||||||
a failed login with an unknown user takes the same time as one
|
a failed login with an unknown user takes the same time as one
|
||||||
with a known user (dummy hash is computed).
|
with a known user (dummy hash is computed).
|
||||||
"""
|
"""
|
||||||
user = get_user(request.username)
|
user = get_user(body.username)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
# Timing-safe: simulate hash computation to prevent user enumeration
|
# 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):
|
if is_rate_limited(client_ip):
|
||||||
raise HTTPException(429, "Trop de tentatives depuis cette adresse IP (15min)")
|
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)")
|
raise HTTPException(429, "Compte temporairement verrouillé (15min)")
|
||||||
|
|
||||||
if not verify_password(request.password, user["password_hash"]):
|
if not verify_password(body.password, user["password_hash"]):
|
||||||
attempts = record_login_failure(request.username)
|
attempts = record_login_failure(body.username)
|
||||||
rl_attempts, rl_remaining = rl_record_failure(client_ip)
|
rl_attempts, rl_remaining = rl_record_failure(client_ip)
|
||||||
remaining = max(0, 5 - attempts)
|
remaining = max(0, 5 - attempts)
|
||||||
detail = "Identifiants invalides"
|
detail = "Identifiants invalides"
|
||||||
@ -124,14 +124,14 @@ async def login(request: LoginRequest, response: Response):
|
|||||||
raise HTTPException(401, detail)
|
raise HTTPException(401, detail)
|
||||||
|
|
||||||
# Success — clear rate limits and generate tokens
|
# Success — clear rate limits and generate tokens
|
||||||
record_login_success(request.username)
|
record_login_success(body.username)
|
||||||
rl_record_success(client_ip)
|
rl_record_success(client_ip)
|
||||||
|
|
||||||
access_token = create_access_token(user)
|
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)
|
# 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
|
import os
|
||||||
secure = os.environ.get("OBSIGATE_SECURE_COOKIES", "false").lower() == "true"
|
secure = os.environ.get("OBSIGATE_SECURE_COOKIES", "false").lower() == "true"
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
@ -144,7 +144,7 @@ async def login(request: LoginRequest, response: Response):
|
|||||||
path="/api/auth/refresh",
|
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)
|
# Set access token as cookie for same-origin requests (e.g. popout window)
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user