""" Security utilities: JWT tokens, password hashing. """ from datetime import datetime, timedelta, timezone from typing import Any from jose import JWTError, jwt from passlib.context import CryptContext from app.config import settings # Password hashing context pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def create_access_token(data: dict[str, Any]) -> str: """ Create JWT access token. Args: data: Payload to encode (typically {"sub": user_id}) Returns: Encoded JWT token string """ to_encode = data.copy() expire = datetime.now(timezone.utc) + timedelta( minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES ) to_encode.update({"exp": expire, "type": "access"}) return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) def create_refresh_token(data: dict[str, Any]) -> str: """ Create JWT refresh token. Args: data: Payload to encode (typically {"sub": user_id}) Returns: Encoded JWT token string """ to_encode = data.copy() expire = datetime.now(timezone.utc) + timedelta( days=settings.REFRESH_TOKEN_EXPIRE_DAYS ) to_encode.update({"exp": expire, "type": "refresh"}) return jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) def verify_token(token: str, expected_type: str = "access") -> dict[str, Any] | None: """ Verify and decode JWT token. Args: token: JWT token string expected_type: Expected token type ("access" or "refresh") Returns: Decoded payload if valid, None otherwise """ try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) token_type: str = payload.get("type") if token_type != expected_type: return None return payload except JWTError: return None def verify_password(plain_password: str, hashed_password: str) -> bool: """ Verify a plain password against a hashed password. Args: plain_password: Plain text password hashed_password: Hashed password from database Returns: True if password matches, False otherwise """ return pwd_context.verify(plain_password, hashed_password) def hash_password(password: str) -> str: """ Hash a password using bcrypt. Args: password: Plain text password Returns: Hashed password """ return pwd_context.hash(password)