Initial commit — Pedrito Hechakuaa v0

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>
This commit is contained in:
markosbenitez
2026-05-25 10:58:32 -03:00
commit 7743919695
24 changed files with 5242 additions and 0 deletions

55
vault.py Normal file
View File

@@ -0,0 +1,55 @@
"""
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