import hmac import hashlib from datetime import datetime, timedelta, timezone from typing import Optional, Dict, Any import jwt from app.core.config import settings def verify_password(plain_password: str, stored_password: str) -> bool: """Constant-time password comparison to prevent timing attacks.""" if not plain_password or not stored_password: return False # Supports direct string comparison via hmac or sha256 hash if stored_password.startswith("sha256$"): _, salt, expected_hash = stored_password.split("$", 2) calc_hash = hashlib.sha256((salt + plain_password).encode("utf-8")).hexdigest() return hmac.compare_digest(calc_hash, expected_hash) return hmac.compare_digest(plain_password, stored_password) def hash_password(plain_password: str, salt: str = "merica_dlh_salt") -> str: """Generate salted sha256 password hash.""" calc_hash = hashlib.sha256((salt + plain_password).encode("utf-8")).hexdigest() return f"sha256${salt}${calc_hash}" def create_session_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str: """Create a unified session JWT token signed with SECRET_KEY.""" to_encode = data.copy() now = datetime.now(timezone.utc) if expires_delta: expire = now + expires_delta else: expire = now + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({ "exp": int(expire.timestamp()), "iat": int(now.timestamp()) }) encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) return encoded_jwt def decode_session_token(token: str) -> Optional[Dict[str, Any]]: """Decode and validate a session JWT token.""" try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) return payload except Exception: return None