security.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. from datetime import datetime, timedelta, timezone
  2. from typing import Any, Optional
  3. from jose import jwt, JWTError
  4. from passlib.context import CryptContext
  5. from fastapi import Depends, HTTPException, status
  6. from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
  7. from redis.asyncio import Redis
  8. from app.core.config import get_settings
  9. settings = get_settings()
  10. pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
  11. security_scheme = HTTPBearer()
  12. def verify_password(plain_password: str, hashed_password: str) -> bool:
  13. return pwd_context.verify(plain_password, hashed_password)
  14. def hash_password(password: str) -> str:
  15. return pwd_context.hash(password)
  16. def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
  17. to_encode = data.copy()
  18. expire = datetime.now(timezone.utc) + (
  19. expires_delta or timedelta(hours=24)
  20. )
  21. to_encode.update({"exp": expire, "iat": datetime.now(timezone.utc)})
  22. return jwt.encode(to_encode, settings.secret_key, algorithm="HS256")
  23. def decode_access_token(token: str) -> Optional[dict]:
  24. try:
  25. return jwt.decode(token, settings.secret_key, algorithms=["HS256"])
  26. except (JWTError, Exception):
  27. return None
  28. async def get_current_user(
  29. credentials: HTTPAuthorizationCredentials = Depends(security_scheme),
  30. ) -> dict:
  31. payload = decode_access_token(credentials.credentials)
  32. if payload is None:
  33. raise HTTPException(
  34. status_code=status.HTTP_401_UNAUTHORIZED,
  35. detail="Invalid or expired token",
  36. )
  37. return payload
  38. class RateLimiter:
  39. def __init__(self, redis_client: Redis):
  40. self.redis = redis_client
  41. async def is_rate_limited(self, user_id: str) -> bool:
  42. minute_key = f"rate_limit:minute:{user_id}"
  43. hour_key = f"rate_limit:hour:{user_id}"
  44. current_minute = await self.redis.get(minute_key)
  45. current_hour = await self.redis.get(hour_key)
  46. if current_minute and int(current_minute) >= settings.rate_limit_per_minute:
  47. return True
  48. if current_hour and int(current_hour) >= settings.rate_limit_per_hour:
  49. return True
  50. async with self.redis.pipeline() as pipe:
  51. pipe.incr(minute_key)
  52. pipe.expire(minute_key, 60)
  53. pipe.incr(hour_key)
  54. pipe.expire(hour_key, 3600)
  55. await pipe.execute()
  56. return False