Bot de Telegram para monitoreo de notificaciones judiciales del PJ Paraguay. Multi-tenant con onboarding conversacional, audit log, y polling automático. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
56 lines
1.9 KiB
Python
56 lines
1.9 KiB
Python
"""
|
|
vault.py — Cifrado de credenciales por tenant.
|
|
|
|
Cada tenant tiene sus credenciales cifradas con una clave derivada de:
|
|
FERNET_KEY (global, del .env) + str(chat_id) (único por tenant)
|
|
|
|
Esto asegura que las credenciales de un tenant no se pueden descifrar
|
|
con solo la FERNET_KEY — hace falta también el chat_id.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import hashlib
|
|
import json
|
|
|
|
import structlog
|
|
from cryptography.fernet import Fernet, InvalidToken
|
|
|
|
from utils.sanitize import scrub
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
def _derive_key(fernet_key: str, chat_id: int) -> bytes:
|
|
"""Deriva una clave Fernet única por tenant usando SHA-256(fernet_key + chat_id)."""
|
|
raw = (fernet_key + str(chat_id)).encode()
|
|
digest = hashlib.sha256(raw).digest()
|
|
return base64.urlsafe_b64encode(digest)
|
|
|
|
|
|
def cifrar_credenciales(chat_id: int, usuario: str, password: str, fernet_key: str) -> str:
|
|
"""Retorna JSON cifrado con Fernet listo para guardar en DB."""
|
|
key = _derive_key(fernet_key, chat_id)
|
|
f = Fernet(key)
|
|
payload = json.dumps({"usuario": usuario, "password": password}).encode()
|
|
encrypted = f.encrypt(payload).decode()
|
|
logger.info("credenciales_cifradas", chat_id=chat_id)
|
|
return encrypted
|
|
|
|
|
|
def descifrar_credenciales(chat_id: int, credentials_enc: str, fernet_key: str) -> tuple[str, str]:
|
|
"""
|
|
Retorna (usuario, password).
|
|
Levanta ValueError si no se puede descifrar (clave incorrecta o datos corruptos).
|
|
"""
|
|
key = _derive_key(fernet_key, chat_id)
|
|
f = Fernet(key)
|
|
try:
|
|
payload = json.loads(f.decrypt(credentials_enc.encode()).decode())
|
|
return payload["usuario"], payload["password"]
|
|
except (InvalidToken, KeyError, ValueError) as e:
|
|
logger.error("error_descifrar_credenciales", chat_id=chat_id, error=scrub(str(e)))
|
|
raise ValueError(
|
|
f"No se pudieron descifrar credenciales para chat_id={chat_id}"
|
|
) from e
|