import bcrypt from jose import jwt, JWTError from datetime import datetime, timedelta from typing import TYPE_CHECKING from uuid import UUID from fastapi import Depends, HTTPException from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from src.config.settings import settings if TYPE_CHECKING: from src.models import User # HTTPBearer security scheme security = HTTPBearer() def hash_password(password: str) -> str: password_bytes = password.encode('utf-8') salt = bcrypt.gensalt() return bcrypt.hashpw(password_bytes, salt).decode('utf-8') def verify_password(plain_password: str, hashed_password: str) -> bool: password_bytes = plain_password.encode('utf-8') hashed_bytes = hashed_password.encode('utf-8') return bcrypt.checkpw(password_bytes, hashed_bytes) def create_access_token(data: dict) -> str: to_encode = data.copy() expire = datetime.utcnow() + timedelta(minutes=settings.access_token_expire_minutes) to_encode.update({"exp": expire}) return jwt.encode(to_encode, settings.jwt_secret, algorithm=settings.jwt_algorithm) def decode_access_token(token: str) -> dict: try: payload = jwt.decode(token, settings.jwt_secret, algorithms=[settings.jwt_algorithm]) return payload except JWTError: return None async def get_current_user( credentials: HTTPAuthorizationCredentials = Depends(security) ) -> "User": """ Dependency to get the current authenticated user from JWT token. Gets its own database session internally to avoid circular import issues. Routes using this don't need to pass db separately. """ from src.models import User from src.config import get_db # Get database session async for db in get_db(): token = credentials.credentials payload = decode_access_token(token) if not payload: raise HTTPException(status_code=401, detail="Invalid or expired token") user_id = payload.get("sub") if not user_id: raise HTTPException(status_code=401, detail="Invalid token payload") try: user_uuid = UUID(str(user_id)) except Exception: raise HTTPException(status_code=401, detail="Invalid token payload") result = await db.execute(select(User).where(User.id == user_uuid)) user = result.scalar_one_or_none() if not user: raise HTTPException(status_code=401, detail="User not found") return user raise HTTPException(status_code=500, detail="Database connection failed")