Files
leocrm/app/core/rate_limit.py
T
leocrm-bot 3ab4925783 T01: core infrastructure + auth + multi-tenant + RLS
- 10 models: tenants, users, user_tenants, roles, sessions, audit_log, deletion_log, notifications, password_reset_tokens, api_tokens
- Session-based auth (Redis + PostgreSQL audit trail)
- Multi-tenant with ORM-level filtering + PostgreSQL RLS (set_config)
- RBAC with roles/permissions + field-level permissions
- CSRF protection via Origin header validation
- Auth rate limiting (Redis counters with TTL)
- CORS with explicit origins (no wildcard)
- Health endpoint (no auth required)
- Notification service + audit log middleware
- 29 tests, 26 ACs, all passing
- Coverage: 62% (infrastructure modules pending coverage in later tasks)
2026-06-29 08:02:14 +02:00

48 lines
1.4 KiB
Python

"""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"