from __future__ import annotations import uuid from datetime import UTC, datetime, timedelta from typing import Any from jose import JWTError, jwt from passlib.context import CryptContext from app.core.config import settings from app.core.aes import get_string_value pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(password: str) -> str: """Hash a password using bcrypt.""" return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: """Verify a plain password against a hashed password.""" return pwd_context.verify(plain_password, hashed_password) def create_access_token(data: dict[str, Any], expires_delta: timedelta | None = None) -> str: """Create a JWT access token.""" to_encode = data.copy() expire = datetime.now(UTC) + (expires_delta or timedelta(minutes=settings.jwt_access_token_expire_minutes)) to_encode.update({"exp": expire, "type": "access"}) return jwt.encode(to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm) def create_refresh_token(data: dict[str, Any], expires_delta: timedelta | None = None) -> str: """Create a JWT refresh token.""" to_encode = data.copy() expire = datetime.now(UTC) + (expires_delta or timedelta(days=settings.jwt_refresh_token_expire_days)) to_encode.update({ "exp": expire, "type": "refresh", "jti": str(uuid.uuid4()), }) return jwt.encode(to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm) def decode_token(token: str) -> dict[str, Any]: """Decode and validate a JWT token.""" decrypted_token = get_string_value( token, settings.session_encryption_secret, settings.session_encryption_secret_internal ) final_token = decrypted_token if decrypted_token else token try: payload = jwt.decode(final_token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm]) return payload except JWTError as e: raise InvalidTokenError(str(e)) from e class InvalidTokenError(Exception): """Raised when a JWT token is invalid or expired.""" def __init__(self, detail: str = "Invalid or expired token") -> None: self.detail = detail super().__init__(self.detail)