Specs stay at repo root (cross-VM). Move deploy and code into logical projects with README per domain, updated manifest.yaml, and symlinks at legacy paths for VM122 backward compatibility.
135 lines
4.5 KiB
Python
135 lines
4.5 KiB
Python
"""Send notification emails via Postfix (SMTP)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import smtplib
|
|
from email.message import EmailMessage
|
|
|
|
ROOT_NOTIFY_EMAIL = os.getenv("DESK_ROOT_NOTIFY_EMAIL", "admin@ligbox.com.br")
|
|
DESK_PUBLIC_URL = os.getenv("DESK_PUBLIC_URL", "https://desk.ligbox.com.br")
|
|
MAIL_FROM = os.getenv("DESK_MAIL_FROM", "ligbox-ops@ligbox.com.br")
|
|
SMTP_HOST = os.getenv("DESK_SMTP_HOST", "10.10.10.122")
|
|
SMTP_PORT = int(os.getenv("DESK_SMTP_PORT", "25"))
|
|
|
|
|
|
def send_email(to: str, subject: str, body: str) -> bool:
|
|
to = (to or "").strip()
|
|
if not to:
|
|
return False
|
|
msg = EmailMessage()
|
|
msg["From"] = MAIL_FROM
|
|
msg["To"] = to
|
|
msg["Subject"] = subject
|
|
msg.set_content(body)
|
|
try:
|
|
with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=15) as smtp:
|
|
smtp.send_message(msg)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def notify_root_registration_pending(email: str, request_id: int) -> bool:
|
|
body = (
|
|
f"Novo pedido de cadastro Ligbox Ops Desk\n\n"
|
|
f"E-mail: {email}\n"
|
|
f"ID: {request_id}\n\n"
|
|
f"Aprovar em: {DESK_PUBLIC_URL}/\n"
|
|
f"(Menu Mensagens)\n"
|
|
)
|
|
return send_email(ROOT_NOTIFY_EMAIL, f"[Ligbox Ops] Pedido de cadastro: {email}", body)
|
|
|
|
|
|
def notify_candidate_approved(email: str, activation_url: str, role: str) -> bool:
|
|
body = (
|
|
f"Seu pedido de acesso ao Ligbox Ops Desk foi APROVADO.\n\n"
|
|
f"Perfil atribuído: {role}\n\n"
|
|
f"Ative sua conta (complete 2 de 3 fatores: e-mail, telefone ou app 2FA):\n{activation_url}\n\n"
|
|
f"Este link expira em 48 horas.\n"
|
|
)
|
|
return send_email(email, "[Ligbox Ops] Conta aprovada — ative agora", body)
|
|
|
|
|
|
def notify_candidate_rejected(email: str, reason: str | None = None) -> bool:
|
|
body = "Seu pedido de acesso ao Ligbox Ops Desk foi rejeitado."
|
|
if reason:
|
|
body += f"\n\nMotivo: {reason}"
|
|
return send_email(email, "[Ligbox Ops] Pedido de cadastro rejeitado", body)
|
|
|
|
|
|
def send_otp_email(email: str, code: str, purpose: str) -> bool:
|
|
body = (
|
|
f"Código de verificação Ligbox Ops Desk\n\n"
|
|
f"Finalidade: {purpose}\n"
|
|
f"Código: {code}\n\n"
|
|
f"Válido por 10 minutos.\n"
|
|
)
|
|
return send_email(email, f"[Ligbox Ops] Código: {code}", body)
|
|
|
|
|
|
def mask_email(email: str) -> str:
|
|
email = (email or "").strip()
|
|
if "@" not in email:
|
|
return email
|
|
local, domain = email.split("@", 1)
|
|
if len(local) <= 2:
|
|
masked_local = local[0] + "***"
|
|
else:
|
|
masked_local = local[0] + "***" + local[-1]
|
|
return f"{masked_local}@{domain}"
|
|
|
|
|
|
def notify_mfa_recovery_started(username: str, email: str) -> bool:
|
|
body = (
|
|
f"Recuperação de 2FA iniciada no Ligbox Ops Desk\n\n"
|
|
f"Utilizador: {username}\n"
|
|
f"E-mail de verificação: {email}\n\n"
|
|
f"Se não foi você, contacte o root imediatamente.\n"
|
|
)
|
|
return send_email(
|
|
ROOT_NOTIFY_EMAIL,
|
|
f"[Ligbox Ops] Recuperação 2FA: {username}",
|
|
body,
|
|
)
|
|
|
|
|
|
def notify_mfa_recovery_completed(username: str) -> bool:
|
|
body = (
|
|
f"Recuperação de 2FA concluída no Ligbox Ops Desk\n\n"
|
|
f"Utilizador: {username}\n"
|
|
f"Novo autenticador configurado e códigos de backup gerados.\n"
|
|
)
|
|
return send_email(
|
|
ROOT_NOTIFY_EMAIL,
|
|
f"[Ligbox Ops] 2FA reconfigurado: {username}",
|
|
body,
|
|
)
|
|
|
|
|
|
def notify_admin_2fa_reset(target_username: str, target_email: str, admin_username: str) -> bool:
|
|
body = (
|
|
f"O administrador {admin_username} resetou o 2FA da conta:\n\n"
|
|
f"Utilizador: {target_username}\n"
|
|
f"E-mail: {target_email}\n\n"
|
|
f"O utilizador pode entrar só com senha e reconfigurar o autenticador em:\n"
|
|
f"{DESK_PUBLIC_URL}/login.html\n"
|
|
f"(Perdi acesso ao autenticador)\n"
|
|
)
|
|
send_email(target_email, "[Ligbox Ops] 2FA resetado pelo administrador", body)
|
|
return send_email(
|
|
ROOT_NOTIFY_EMAIL,
|
|
f"[Ligbox Ops] Admin resetou 2FA: {target_username}",
|
|
body,
|
|
)
|
|
|
|
|
|
def send_backup_codes_email(email: str, codes: list[str]) -> bool:
|
|
lines = "\n".join(f" • {c}" for c in codes)
|
|
body = (
|
|
f"Códigos de backup — Ligbox Ops Desk\n\n"
|
|
f"Guarde estes códigos em local seguro. Cada código só pode ser usado uma vez.\n\n"
|
|
f"{lines}\n\n"
|
|
f"Use-os no login se perder acesso ao aplicativo autenticador.\n"
|
|
)
|
|
return send_email(email, "[Ligbox Ops] Códigos de backup 2FA", body)
|