fix(auth): catch IntegrityError on register race → 409 + locustfile pre-auth rewrite

Two fixes from manual SLA validation (06-LOCUST-01):

1. backend/api/auth.py: concurrent registrations with same email/handle caused
   UniqueViolation to bubble as 500. Now wraps session.commit() in try/except
   IntegrityError → 409 Conflict. Regression covered by existing auth API tests.

2. backend/load_tests/locustfile.py: rewrote from single-shared-account to
   pre-authenticated unique accounts. New @events.test_start listener creates
   50 accounts in 9-user batches (65 s pause between batches to stay under
   10 req/min IP rate limit). Added min-request guard to SLA check so a
   run-time-consumed-by-setup result never silently "passes".
   Run command updated to --run-time 12m.

Known open issue: _account_key in services/rate_limiting.py falls back to IP
when request.state.current_user is not yet set (rate limiter key_func runs
before handler body). Fix: extract sub from JWT directly. See next session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-06-05 12:25:04 +02:00
co-authored by Claude Sonnet 4.6
parent 3f0e2ab44c
commit 0fa23f5211
2 changed files with 104 additions and 30 deletions
+13 -5
View File
@@ -26,6 +26,7 @@ from typing import Literal, Optional
from fastapi import APIRouter, Depends, HTTPException, Request, Response, status
from pydantic import BaseModel, EmailStr
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession
from config import settings
@@ -148,11 +149,18 @@ async def register(
limit_bytes=104857600, # 100 MB default (STORE-01)
used_bytes=0,
)
session.add(new_user)
await session.flush() # persist User before Quota FK
session.add(quota)
await session.commit()
await session.refresh(new_user)
try:
session.add(new_user)
await session.flush() # persist User before Quota FK
session.add(quota)
await session.commit()
await session.refresh(new_user)
except IntegrityError:
await session.rollback()
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Email or handle already in use",
)
return {
"id": str(new_user.id),