Files

48 lines
1.4 KiB
Python
Raw Permalink Normal View History

"""Redis-based rate limiting for auth endpoints."""
from __future__ import annotations
from fastapi import HTTPException, Request, status
from app.core.auth import get_redis
from app.config import get_settings
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"