from datetime import datetime, timedelta from typing import Optional from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import bcrypt from jose import JWTError, jwt from shared.config import settings from shared.schemas.user import TokenData _bearer = HTTPBearer(auto_error=False) def hash_password(password: str) -> str: return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() def verify_password(plain: str, hashed: str) -> bool: return bcrypt.checkpw(plain.encode(), hashed.encode()) def create_access_token(user_id: str, role: str) -> str: expire = datetime.utcnow() + timedelta(minutes=settings.jwt_expire_minutes) return jwt.encode( {"sub": user_id, "role": role, "exp": expire}, settings.jwt_secret, algorithm=settings.jwt_algorithm, ) def _decode(token: str) -> TokenData: try: payload = jwt.decode(token, settings.jwt_secret, algorithms=[settings.jwt_algorithm]) return TokenData(user_id=payload.get("sub"), role=payload.get("role")) except JWTError: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid or expired token") def get_current_user( credentials: Optional[HTTPAuthorizationCredentials] = Depends(_bearer), ) -> TokenData: if not credentials: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated") return _decode(credentials.credentials) def get_optional_user( credentials: Optional[HTTPAuthorizationCredentials] = Depends(_bearer), ) -> Optional[TokenData]: if not credentials: return None try: return _decode(credentials.credentials) except HTTPException: return None def require_admin(user: TokenData = Depends(get_current_user)) -> TokenData: if user.role != "admin": raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin only") return user