2026-06-29 09:10:05 +05:30

53 lines
1.6 KiB
Python

"""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()