""" Security utilities for authentication and password hashing. Implements JWT token management and bcrypt password hashing with salt. """ from datetime import datetime, timedelta, timezone from typing import Optional from fastapi import Header, HTTPException, status from jose import JWTError, jwt from passlib.context import CryptContext from app.core.settings import get_settings settings = get_settings() pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def verify_password(plain_password: str, hashed_password: str) -> bool: """Verify a plain password against a bcrypt hash.""" return pwd_context.verify(plain_password, hashed_password) def hash_password(password: str) -> str: """Hash a password using bcrypt with configurable rounds.""" return pwd_context.hash(password, rounds=settings.BCRYPT_ROUNDS) def create_access_token( subject: str, privlevel: str = "User", expires_delta: Optional[timedelta] = None, ) -> str: """Create a JWT access token.""" if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: expire = datetime.now(timezone.utc) + timedelta( minutes=settings.JWT_ACCESS_TOKEN_EXPIRE_MINUTES ) # Use JWT_SECRET_KEY if available, fall back to SECRET_KEY secret = getattr(settings, 'JWT_SECRET_KEY', None) or settings.SECRET_KEY payload = { "sub": subject, "exp": expire, "iat": datetime.now(timezone.utc), "type": "access", "privlevel": privlevel, } return jwt.encode(payload, secret, algorithm=settings.JWT_ALGORITHM) def create_refresh_token(subject: str, privlevel: str = "User") -> str: """Create a JWT refresh token with longer expiry.""" expire = datetime.now(timezone.utc) + timedelta( days=settings.JWT_REFRESH_TOKEN_EXPIRE_DAYS ) # Use JWT_SECRET_KEY if available, fall back to SECRET_KEY secret = getattr(settings, 'JWT_SECRET_KEY', None) or settings.SECRET_KEY payload = { "sub": subject, "exp": expire, "iat": datetime.now(timezone.utc), "type": "refresh", "privlevel": privlevel, } return jwt.encode(payload, secret, algorithm=settings.JWT_ALGORITHM) def decode_token(token: str) -> Optional[dict]: """Decode and validate a JWT token.""" try: # Use JWT_SECRET_KEY if available, fall back to SECRET_KEY secret = getattr(settings, 'JWT_SECRET_KEY', None) or settings.SECRET_KEY payload = jwt.decode( token, secret, algorithms=[settings.JWT_ALGORITHM], ) return payload except JWTError: return None def get_current_user(authorization: str = Header(...)) -> dict: """Extract user info from JWT token in Authorization header.""" # Parse Bearer token parts = authorization.split() if len(parts) != 2 or parts[0].lower() != 'bearer': raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication scheme" ) token = parts[1] payload = decode_token(token) if not payload: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) if payload.get("type") != "access": raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token type" ) return { "user_id": payload.get("sub"), "privlevel": payload.get("privlevel", "User"), "token_type": payload.get("type"), }