""" Security utilities for authentication. """ import hashlib from datetime import datetime, timedelta, timezone from typing import Optional import bcrypt import jwt from jwt.exceptions import PyJWTError from app.core.config import get_settings settings = get_settings() def sha256_hash(password: str) -> str: """SHA-256 hash a raw password (matches client-side hashing).""" return hashlib.sha256(password.encode()).hexdigest() def verify_password(sha256_password: str, hashed_password: str) -> bool: """Verify a SHA-256 hashed password against its bcrypt hash.""" return bcrypt.checkpw( sha256_password.encode(), hashed_password.encode() ) def get_password_hash(raw_password: str) -> str: """Hash a raw password: SHA-256 then bcrypt.""" return bcrypt.hashpw( sha256_hash(raw_password).encode(), bcrypt.gensalt() ).decode() def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: """Create a JWT access token.""" to_encode = data.copy() if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: expire = datetime.now(timezone.utc) + timedelta( minutes=settings.access_token_expire_minutes ) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode( to_encode, settings.jwt_secret, algorithm=settings.jwt_algorithm ) return encoded_jwt def decode_access_token(token: str) -> Optional[dict]: """Decode and verify a JWT access token.""" try: payload = jwt.decode( token, settings.jwt_secret, algorithms=[settings.jwt_algorithm] ) return payload except PyJWTError: return None