implemented rate limit and set up redis container
This commit is contained in:
Vendored
+1
@@ -0,0 +1 @@
|
||||
#redis dir
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
from fastapi import Request
|
||||
|
||||
from src.cache.redis_client import redis_client
|
||||
from src.errors.http_errors.errors import Errors
|
||||
|
||||
|
||||
class RateLimit:
|
||||
def __init__(self) -> None:
|
||||
self.errors=Errors()
|
||||
|
||||
async def rate_limit(self, ip:str)->None:
|
||||
|
||||
key=f"action attempt {ip}"
|
||||
attempts = await redis_client.incrby(key)
|
||||
|
||||
if attempts == 1:
|
||||
await redis_client.expire(key, 60)
|
||||
|
||||
if attempts>5:
|
||||
raise self.errors.rate_limit_error(detail="too many attempts", retry_after=60)
|
||||
|
||||
async def check_rate_limit(self, request: Request) -> None:
|
||||
client_ip = request.headers.get('x-forwarded-for', '').split(',')[0].strip() or (request.client.host if request.client else 'unknown')
|
||||
await self.rate_limit(client_ip)
|
||||
|
||||
|
||||
rate_limiter=RateLimit()
|
||||
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
import redis.asyncio as redis
|
||||
|
||||
from src.models.configs_read.env import env_settings
|
||||
|
||||
|
||||
class RedisClient:
|
||||
def __init__(self) -> None:
|
||||
self.client = redis.Redis(
|
||||
host=env_settings.REDIS_HOST,
|
||||
port=env_settings.REDIS_PORT,
|
||||
password=env_settings.REDIS_PASSWORD,
|
||||
decode_responses=True,
|
||||
max_connections=10,
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.client, name)
|
||||
|
||||
async def close(self) -> None:
|
||||
await self.client.aclose()
|
||||
|
||||
redis_client = RedisClient()
|
||||
|
||||
@@ -16,6 +16,10 @@ class Env(Base):
|
||||
DB_HOST:str
|
||||
DB_PORT:str
|
||||
|
||||
REDIS_PASSWORD:str
|
||||
REDIS_PORT:int
|
||||
REDIS_HOST:str
|
||||
|
||||
model_config=SettingsConfigDict(env_file="configs/.env", extra=None)
|
||||
|
||||
env_settings=Env() # type: ignore[call-arg]
|
||||
@@ -1,6 +1,7 @@
|
||||
from fastapi import APIRouter, Cookie, Depends, Request, Response
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
|
||||
from src.cache.rate_limit import rate_limiter
|
||||
from src.models.configs_read.env import env_settings
|
||||
from src.models.pydantic_models.model import UserOut
|
||||
from src.service.auth.auth import CurrentUserService, auth_service
|
||||
@@ -12,14 +13,19 @@ oauth2_schema=OAuth2PasswordBearer(tokenUrl="/protected/token", refreshUrl="/pro
|
||||
def require_permissions(*permissions: str): #permissions check dependency
|
||||
async def checker(
|
||||
token: str = Depends(oauth2_schema),
|
||||
auth: CurrentUserService = Depends(auth_service), #noqa: B008
|
||||
auth: CurrentUserService = Depends(auth_service),
|
||||
) -> UserOut:
|
||||
return UserOut.model_validate(await auth.get_current_user(token, *permissions))
|
||||
return checker
|
||||
|
||||
|
||||
@router.post("/token")
|
||||
async def get_access_token(request: Request,response:Response,auth:CurrentUserService=Depends(auth_service), form_data:OAuth2PasswordRequestForm=Depends())->dict: # noqa: B008
|
||||
async def get_access_token(request: Request,
|
||||
response:Response,
|
||||
auth:CurrentUserService=Depends(auth_service),
|
||||
form_data:OAuth2PasswordRequestForm=Depends(),
|
||||
_:None = Depends(rate_limiter.check_rate_limit)
|
||||
)->dict:
|
||||
|
||||
access_token, refresh_token=await auth.login(form_data_email=form_data.username, form_data_password=form_data.password, request=request)
|
||||
|
||||
@@ -35,7 +41,10 @@ async def get_access_token(request: Request,response:Response,auth:CurrentUserSe
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
async def get_refresh_token(request:Request,response:Response, refresh_token: str = Cookie(), auth:CurrentUserService=Depends(auth_service))->dict: # noqa: B008
|
||||
async def get_refresh_token(request:Request,
|
||||
response:Response,
|
||||
refresh_token: str = Cookie(),
|
||||
auth:CurrentUserService=Depends(auth_service))->dict:
|
||||
|
||||
access_token, refresh_token= await auth.refresh_token(refresh_token=refresh_token,request=request)
|
||||
|
||||
@@ -51,10 +60,13 @@ async def get_refresh_token(request:Request,response:Response, refresh_token: st
|
||||
return {"access_token":access_token, "token_type": "bearer"}
|
||||
|
||||
@router.get("/logout")
|
||||
async def logout(response:Response,refresh_token: str = Cookie(),auth:CurrentUserService=Depends(auth_service),current_user:UserOut=Depends(require_permissions()))->bool: # noqa: B008
|
||||
async def logout(response:Response,
|
||||
refresh_token: str = Cookie(),
|
||||
auth:CurrentUserService=Depends(auth_service),
|
||||
current_user:UserOut=Depends(require_permissions()))->bool:
|
||||
response.delete_cookie("refresh_token")
|
||||
return await auth.logout(refresh_token)
|
||||
|
||||
@router.get("")
|
||||
async def protected(current_user:UserOut=Depends(require_permissions()))->dict: # noqa: B008
|
||||
async def protected(current_user:UserOut=Depends(require_permissions()))->dict:
|
||||
return {"protected router": "Hello, this is a protected router"}
|
||||
|
||||
@@ -7,22 +7,22 @@ from src.web.protected_routes.auth_routes import require_permissions
|
||||
router=APIRouter(prefix="/user")
|
||||
|
||||
@router.get("/get_by_email")
|
||||
async def get_current_user_by_email(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut: # noqa: B008
|
||||
async def get_current_user_by_email(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut:
|
||||
return await crud.get_user_by_email(email)
|
||||
|
||||
@router.post("/create_user")
|
||||
async def create_user(data:UserCreate, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut: #noqa: B008
|
||||
async def create_user(data:UserCreate, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut:
|
||||
return await crud.create_user(data)
|
||||
|
||||
@router.post("/delete_user_soft")
|
||||
async def delete_user_soft(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->bool: #noqa: B008
|
||||
async def delete_user_soft(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->bool:
|
||||
return await crud.delete_user_soft(email)
|
||||
|
||||
@router.post("/delete_user_hard")
|
||||
async def delete_user_hard(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->bool: #noqa: B008
|
||||
async def delete_user_hard(email:str, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->bool:
|
||||
return await crud.delete_user_hard(email, current_user)
|
||||
|
||||
@router.patch("/patch_user")
|
||||
async def patch_user(email:str, data:UserUpdate, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut: #noqa: B008
|
||||
async def patch_user(email:str, data:UserUpdate, crud:CrudService=Depends(crud_service), current_user=Depends(require_permissions("admin")))->UserOut:
|
||||
return await crud.update_user(email, data)
|
||||
|
||||
Reference in New Issue
Block a user