DRE customer portal: FastAPI + SQLite backend (18 endpoints)

- Magic-link auth (sha256-only token storage, 15-min single-use, 7-day sessions)
- Staff-key auth via X-DRE-Staff-Key (constant-time compare)
- SQLite WAL, foreign_keys, parameterized queries, atomic DRE/CLT sequence allocation
- Intake validator rejects SSN/PAN patterns (FDCPA/TDCPA compliance)
- Document upload allowlist + magic-byte check, 20MB cap
- Unified error envelope, money as integer cents
- systemd unit (port 8093, User=root, hardening directives)
- Fixes import bug (auth.py relative imports) and audit_log placeholder mismatch
This commit is contained in:
root
2026-08-21 18:43:33 -04:00
parent c573ed1a14
commit be0750d001
14 changed files with 2662 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
"""Best-effort SMTP email via the germainebrown.com relay (mail.germainebrown.com:2525, STARTTLS).
Email failure must NEVER fail the API request — wrap every send in try/except, log, continue.
Per conductor decision #3.
"""
from __future__ import annotations
import logging
import os
import smtplib
import ssl
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
logger = logging.getLogger("dre.email")
def _env(name: str, default: str = "") -> str:
return os.environ.get(name, default)
def send_email(to_addr: str, subject: str, body_text: str, html: str | None = None) -> bool:
"""Send an email via the configured SMTP relay. Returns True on success, False on failure.
Never raises — caller proceeds regardless."""
host = _env("DRE_SMTP_HOST", "mail.germainebrown.com")
port = int(_env("DRE_SMTP_PORT", "2525"))
user = _env("DRE_SMTP_USER", "")
pw = _env("DRE_SMTP_PASS", "")
sender = _env("DRE_SMTP_FROM", "dre@debtrecoveryexperts.com")
try:
msg = MIMEMultipart("alternative")
msg["From"] = sender
msg["To"] = to_addr
msg["Subject"] = subject
msg.attach(MIMEText(body_text, "plain", "utf-8"))
if html:
msg.attach(MIMEText(html, "html", "utf-8"))
with smtplib.SMTP(host, port, timeout=15) as server:
server.starttls(context=ssl.create_default_context())
if user and pw:
server.login(user, pw)
server.sendmail(sender, [to_addr], msg.as_string())
return True
except Exception as exc: # noqa: BLE001 — best-effort
logger.error("email send failed to=%s subject=%s err=%s", to_addr, subject, exc)
return False
def notify_team_intake(claim_number: str, client_number: str, company_name: str,
amount_cents: int, debtor_name: str) -> bool:
team = _env("DRE_TEAM_NOTIFY", "dre@debtrecoveryexperts.com")
base = _env("DRE_BASE_URL", "https://portal.debtrecoveryexperts.com")
dollars = amount_cents / 100.0
body = (
f"New claim submitted via portal.\n\n"
f"Claim: {claim_number}\n"
f"Client: {client_number}{company_name}\n"
f"Debtor: {debtor_name}\n"
f"Amount: ${dollars:,.2f}\n\n"
f"Review at: {base}/\n"
)
html = (
f"<h2>New claim submitted</h2>"
f"<p><b>Claim:</b> {claim_number}<br>"
f"<b>Client:</b> {client_number}{company_name}<br>"
f"<b>Debtor:</b> {debtor_name}<br>"
f"<b>Amount:</b> ${dollars:,.2f}</p>"
f"<p><a href=\"{base}/\">Review in portal</a></p>"
)
return send_email(team, f"New DRE Claim: {claim_number}", body, html)
def send_magic_link(to_addr: str, raw_token: str, client_number: str) -> bool:
base = _env("DRE_BASE_URL", "https://portal.debtrecoveryexperts.com")
link = f"{base}/portal/verify?token={raw_token}"
body = (
f"Hello,\n\n"
f"Click the link below to log in to your DRE client portal. "
f"This link expires in 15 minutes and can only be used once.\n\n"
f"{link}\n\n"
f"If you did not request this link, you can ignore this email.\n"
)
html = (
f"<p>Hello,</p>"
f"<p>Click the button below to log in to your DRE client portal. "
f"This link expires in 15 minutes and can only be used once.</p>"
f"<p><a href=\"{link}\" style=\"...\">Log In</a></p>"
f"<p>If the button doesn't work, copy this link: {link}</p>"
f"<p>If you did not request this link, you can ignore this email.</p>"
)
return send_email(to_addr, "Your DRE Portal Login Link", body, html)
def notify_team_message(claim_number: str, subject: str, content: str,
author: str) -> bool:
team = _env("DRE_TEAM_NOTIFY", "dre@debtrecoveryexperts.com")
body = (
f"New client message on claim {claim_number}.\n\n"
f"From: {author}\n"
f"Subject: {subject}\n\n"
f"{content}\n"
)
return send_email(team, f"Client message on {claim_number}: {subject}", body)