from datetime import datetime, timedelta, timezone from typing import Any, Optional from jose import jwt, JWTError from passlib.context import CryptContext from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from redis.asyncio import Redis from app.core.config import get_settings settings = get_settings() pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") security_scheme = HTTPBearer() def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) def hash_password(password: str) -> str: return pwd_context.hash(password) def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: to_encode = data.copy() expire = datetime.now(timezone.utc) + ( expires_delta or timedelta(hours=24) ) to_encode.update({"exp": expire, "iat": datetime.now(timezone.utc)}) return jwt.encode(to_encode, settings.secret_key, algorithm="HS256") def decode_access_token(token: str) -> Optional[dict]: try: return jwt.decode(token, settings.secret_key, algorithms=["HS256"]) except (JWTError, Exception): return None async def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security_scheme), ) -> dict: payload = decode_access_token(credentials.credentials) if payload is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token", ) return payload class RateLimiter: def __init__(self, redis_client: Redis): self.redis = redis_client async def is_rate_limited(self, user_id: str) -> bool: minute_key = f"rate_limit:minute:{user_id}" hour_key = f"rate_limit:hour:{user_id}" current_minute = await self.redis.get(minute_key) current_hour = await self.redis.get(hour_key) if current_minute and int(current_minute) >= settings.rate_limit_per_minute: return True if current_hour and int(current_hour) >= settings.rate_limit_per_hour: return True async with self.redis.pipeline() as pipe: pipe.incr(minute_key) pipe.expire(minute_key, 60) pipe.incr(hour_key) pipe.expire(hour_key, 3600) await pipe.execute() return False