"""Password hashing, API key generation, and Fernet encryption for credentials.""" from __future__ import annotations import secrets from passlib.context import CryptContext from cryptography.fernet import Fernet from ..config import settings _pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(plain: str) -> str: return _pwd_context.hash(plain) def verify_password(plain: str, hashed: str) -> bool: return _pwd_context.verify(plain, hashed) def generate_api_key() -> tuple[str, str, str]: """Returns (full_key, key_hash, key_prefix). full_key is shown to the user exactly once. key_hash is stored in the DB. key_prefix (first 8 chars) is stored for display. """ raw = secrets.token_hex(32) full_key = f"mtom_{raw}" key_hash = _pwd_context.hash(full_key) key_prefix = full_key[:12] return full_key, key_hash, key_prefix def verify_api_key(plain: str, hashed: str) -> bool: return _pwd_context.verify(plain, hashed) def _fernet() -> Fernet: key = settings.encryption_key if key == "change-me": raise RuntimeError("ENCRYPTION_KEY is not set. Generate one with: python -c \"from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())\"") try: return Fernet(key.encode()) except Exception: raise RuntimeError("ENCRYPTION_KEY is not a valid Fernet key.") def encrypt_credential(plain: str) -> str: return _fernet().encrypt(plain.encode()).decode() def decrypt_credential(enc: str) -> str: return _fernet().decrypt(enc.encode()).decode()