"""Auth endpoints — login / me with brute-force protection.""" from fastapi import APIRouter, HTTPException, Request from app.auth import authenticate, create_access_token, CurrentUser, get_current_user from app.config import settings from app.contracts import LoginRequest, TokenResponse from app.rate_limit import FixedWindowLimiter router = APIRouter() _login_limiter = FixedWindowLimiter( limit=settings.auth_login_max_attempts, window_seconds=settings.auth_login_window_seconds, block_seconds=settings.auth_login_lock_seconds, ) @router.post("/auth/login", response_model=TokenResponse) async def login(body: LoginRequest, request: Request): username = body.username.strip() source_ip = request.client.host if request.client else "unknown" keys = (f"ip:{source_ip}", f"account:{username.casefold()}") retry_after = max([await _login_limiter.check(key) for key in keys], default=0) if retry_after: raise HTTPException( 429, detail="登录尝试过多,请稍后重试", headers={"Retry-After": str(retry_after)}, ) user = await authenticate(username, body.password) if not user: retry_after = max([await _login_limiter.record(key) for key in keys], default=0) if retry_after: raise HTTPException( 429, detail="登录尝试过多,请稍后重试", headers={"Retry-After": str(retry_after)}, ) raise HTTPException(401, detail="Invalid credentials") for key in keys: await _login_limiter.reset(key) token = create_access_token({"sub": user["username"], "roles": user.get("roles", [])}) return TokenResponse(access_token=token) @router.get("/auth/me") def me(user: CurrentUser): return {"username": user["username"], "roles": user.get("roles", [])}