50 lines
2.1 KiB
Python
50 lines
2.1 KiB
Python
# core/rate_limit.py
|
|
|
|
from fastapi import Request, HTTPException, status
|
|
from db.redis import redis_client
|
|
|
|
class RateLimiter:
|
|
def __init__(self, requests: int, window_seconds: int, scope: str = "global"):
|
|
"""
|
|
:param requests: number of requests allowed
|
|
:param window_seconds: window time in seconds
|
|
:param scope: type of limit ("global" for all site, "endpoint" for a specific path)
|
|
"""
|
|
self.requests = requests
|
|
self.window_seconds = window_seconds
|
|
self.scope = scope
|
|
|
|
async def __call__(self, request: Request):
|
|
# getting client ip
|
|
client_ip = request.client.host if request.client else "127.0.0.1"
|
|
|
|
# when project is in docker and behind nginx, the real ip is in the headers
|
|
real_ip = request.headers.get("x-real-ip", request.headers.get("x-forwarded-for", client_ip))
|
|
# if there are multiple ips, take the first ip (the real user ip)
|
|
real_ip = real_ip.split(",")[0].strip()
|
|
|
|
# creating redis key based on scope
|
|
if self.scope == "global":
|
|
# key for global limit (e.g., rate_limit:global:192.168.1.5)
|
|
key = f"rate_limit:global:{real_ip}"
|
|
else:
|
|
# key for endpoint limit (e.g., rate_limit:endpoint:192.168.1.5:/admin/login)
|
|
path = request.scope["path"]
|
|
key = f"rate_limit:endpoint:{real_ip}:{path}"
|
|
|
|
# adding 1 to the number of requests for this ip in redis
|
|
current_count = await redis_client.incr(key)
|
|
|
|
# if this is the first request in this time window, set the expiration time (TTL)
|
|
if current_count == 1:
|
|
await redis_client.expire(key, self.window_seconds)
|
|
|
|
# if the number of requests exceeds the limit, access is blocked
|
|
if current_count > self.requests:
|
|
# penalty: if someone spams, the time they are blocked is extended from zero again
|
|
await redis_client.expire(key, self.window_seconds)
|
|
|
|
raise HTTPException(
|
|
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
detail="Too many requests. Please try again later."
|
|
) |