""" Security utilities: JWT, password hashing, encryption CRITICAL: Handles sensitive credentials """ from datetime import datetime, timedelta from typing import Optional from jose import JWTError, jwt from passlib.context import CryptContext from cryptography.fernet import Fernet import os from app.core.config import get_settings settings = get_settings() # Password hashing pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") # SSH credential encryption (for in-memory use only) # WARNING: Never store the cipher key in plaintext in production # Use AWS KMS, HashiCorp Vault, or similar ENCRYPTION_KEY = os.getenv("ENCRYPTION_KEY", Fernet.generate_key()) cipher_suite = Fernet(ENCRYPTION_KEY) def hash_password(password: str) -> str: """Hash password with bcrypt""" return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: """Verify password against hash""" return pwd_context.verify(plain_password, hashed_password) def create_access_token( data: dict, expires_delta: Optional[timedelta] = None ) -> str: """Create JWT access token""" to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta( minutes=settings.access_token_expire_minutes ) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode( to_encode, settings.secret_key, algorithm=settings.algorithm ) return encoded_jwt def decrypt_credential(encrypted: str) -> str: """ Decrypt SSH credential from storage. WARNING: Credentials should only be decrypted for immediate use. """ try: decrypted = cipher_suite.decrypt(encrypted.encode()) return decrypted.decode() except Exception: raise ValueError("Failed to decrypt credential") def encrypt_credential(credential: str) -> str: """ Encrypt SSH credential for storage. Use only if absolutely necessary. Prefer not storing passwords. """ encrypted = cipher_suite.encrypt(credential.encode()) return encrypted.decode()