feat(email): add provider abstraction and HTML templates

Add pluggable email provider system supporting SMTP, Mailgun, and SendGrid
with factory pattern for runtime provider selection. Includes branded HTML
email templates for verification, password reset, MFA notifications, and
organization invites.

Also rebrands all email content from Gatehouse to Secuird, adds email
provider configuration options, and fixes duplicate log handlers in
development mode.
This commit is contained in:
2026-04-04 16:55:00 +10:30
parent d90a06437e
commit 41bbdb4bef
17 changed files with 1068 additions and 76 deletions
+10 -2
View File
@@ -20,13 +20,13 @@ class DevelopmentConfig(BaseConfig):
# Reduced bcrypt rounds for faster dev cycles
BCRYPT_LOG_ROUNDS = 4
# Gatehouse React UI URL — OIDC authorize redirects here instead of showing raw HTML
# Secuird React UI URL — OIDC authorize redirects here instead of showing raw HTML
OIDC_UI_URL = os.getenv("OIDC_UI_URL", "http://localhost:8080")
# Add localhost:8080 (React UI) to CORS allowed origins for OIDC bridge endpoints
CORS_ORIGINS = os.getenv(
"CORS_ORIGINS",
"http://localhost:8080,http://localhost:3000,http://localhost:5173,https://ui.webauthn.local"
"http://192.168.50.124:8080,http://localhost:8080,http://localhost:3000,http://localhost:5173,https://ui.webauthn.local"
).split(",")
# ── Email / SMTP ──────────────────────────────────────────────────────────
@@ -40,3 +40,11 @@ class DevelopmentConfig(BaseConfig):
SMTP_USE_TLS = os.getenv("SMTP_USE_TLS", "").lower() == "true" if os.getenv("SMTP_USE_TLS") else int(os.getenv("SMTP_PORT", "1025")) not in (25, 1025)
FROM_ADDRESS = os.getenv("FROM_ADDRESS", "noreply@gatehouse.local")
EMAIL_FROM = FROM_ADDRESS # alias
# Email Provider Configuration
EMAIL_PROVIDER = os.getenv("EMAIL_PROVIDER", "smtp").lower()
MAILGUN_API_KEY = os.getenv("MAILGUN_API_KEY") or None
MAILGUN_DOMAIN = os.getenv("MAILGUN_DOMAIN") or None
MAILGUN_API_URL = os.getenv("MAILGUN_API_URL", "https://api.mailgun.net/v3")
SENDGRID_API_KEY = os.getenv("SENDGRID_API_KEY") or None
SENDGRID_FROM_EMAIL = os.getenv("SENDGRID_FROM_EMAIL") or None