"""Redis-based rate limiting for auth endpoints.""" from __future__ import annotations from fastapi import HTTPException, Request, status from app.core.auth import get_redis async def check_rate_limit( redis_key: str, max_attempts: int, window_seconds: int, ) -> None: """Check rate limit using Redis INCR + EXPIRE. Raises 429 if limit exceeded. """ redis = get_redis() current = await redis.incr(redis_key) if current == 1: await redis.expire(redis_key, window_seconds) if current > max_attempts: ttl = await redis.ttl(redis_key) raise HTTPException( status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail={ "detail": "Rate limit exceeded", "code": "rate_limited", "retry_after": ttl, }, headers={"Retry-After": str(ttl)} if ttl > 0 else {}, ) async def reset_rate_limit(redis_key: str) -> None: """Reset a rate limit counter (e.g. on successful login).""" redis = get_redis() await redis.delete(redis_key) def get_client_ip(request: Request) -> str: """Extract client IP from request.""" forwarded = request.headers.get("x-forwarded-for") if forwarded: return forwarded.split(",")[0].strip() return request.client.host if request.client else "unknown"