feat(auth): B-2.3 emails FR + DictIA branding (SMTP Resend)

Rebrand src/services/email.py IN PLACE: French + DictIA + brand gradient
(#0062ff/#00bdd8/#00c896) — replaces legacy "Speakr" / #2563eb. Greetings now
use user.name with fallback to user.username. Subjects:
"Vérifiez votre courriel — DictIA" + "Réinitialiser votre mot de passe — DictIA".
SMTP_FROM_NAME defaults to DictIA. Footer points to info@dictia.ca with the
Loi 25 tagline.

Refonte 4 auth templates IN PLACE pour étendre marketing/base.html : check_email,
forgot_password, reset_password, verify_success. Tokens DictIA (brand-navy,
brand-bg, grad-bg, shadow-cta), French copy, WCAG patterns (label for,
focus-visible:outline-2, role=alert, aria-required, text-brand-navy/70 minimum,
NBSP français pour Loi 25 / 24 heures / 1 heure / 8 caractères).

Translate inline French flash messages in src/api/auth.py for /verify-email,
/resend-verification, /forgot-password, /reset-password. Anti-enumeration fix:
forgot_password no longer flashes the cooldown remaining (would leak account
existence) — silently skips resend, generic flash unchanged. Cooldown logic
in src/services/email.py UNCHANGED (60s — verified by test).

config/env.email.example: defaults to Resend SMTP at the top + adds Resend
to the provider examples list (preserves Gmail/SendGrid/Mailgun/SES/M365).

Tests: tests/test_email_service_dictia.py — 12 tests covering DictIA branding,
French copy, display-name fallback, anti-enumeration parity (forgot_password
returns identical message for known/unknown emails), 60s cooldown, SMTP-not-
configured returns False (no exception), check_email.html extends marketing/base
(no var(--text-primary) leaks). Includes Windows manual driver
(_run_email_service_dictia_windows.py) since pytest cannot collect on Windows
native (fcntl POSIX-only).

NO new dependency added (no resend SDK — SMTP via existing _send_email).
NO new route added or removed.
NO src/auth_extended/ created.
NO change to itsdangerous-based token logic.
templates/auth/**/*.html already in tailwind.config.js content array (B-2.2).

Verified locally on Windows manual driver: 12/12 PASS B-2.3, 9/9 PASS regression
on B-2.2 signup, 9/9 PASS regression on B-2.1 ConsentLog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Allison
2026-04-27 23:02:20 -04:00
parent 3b324ad0b9
commit 37639a7d09
10 changed files with 666 additions and 520 deletions

View File

@@ -14,25 +14,25 @@ ENABLE_EMAIL_VERIFICATION=false
REQUIRE_EMAIL_VERIFICATION=false
###############################################################################
# SMTP Configuration
# SMTP Configuration (Resend recommended for DictIA — Loi 25 compliant via DKIM/SPF/DMARC)
###############################################################################
# SMTP server hostname (required for email functionality)
# Examples: smtp.gmail.com, smtp.sendgrid.net, smtp.mailgun.org
SMTP_HOST=smtp.gmail.com
# DictIA default: Resend SMTP relay (https://resend.com)
SMTP_HOST=smtp.resend.com
# SMTP server port
# Common ports: 587 (TLS/STARTTLS), 465 (SSL), 25 (unencrypted)
# Common ports: 587 (TLS/STARTTLS), 465 (SSL), 2587 (alt-TLS)
# Default: 587
SMTP_PORT=587
# SMTP authentication username (usually your email address)
SMTP_USERNAME=your-email@gmail.com
# SMTP authentication username
# For Resend: literal "resend"
SMTP_USERNAME=resend
# SMTP authentication password
# For Gmail: Use an App Password (not your regular password)
# https://support.google.com/accounts/answer/185833
SMTP_PASSWORD=your-app-password
# For Resend: an API key from https://resend.com/api-keys (starts with "re_")
SMTP_PASSWORD=re_xxxxxxxxxxxxxxxxxxxxxxxxxxx
# Use TLS/STARTTLS encryption (recommended for port 587)
# Default: true
@@ -44,17 +44,27 @@ SMTP_USE_TLS=true
SMTP_USE_SSL=false
# Email address that appears in the "From" field
# Should be a valid email address, ideally matching your domain
SMTP_FROM_ADDRESS=noreply@yourdomain.com
# Domain MUST be verified in your Resend dashboard (DKIM + SPF + DMARC)
# Canonical for DictIA: noreply@dictia.ca
SMTP_FROM_ADDRESS=noreply@dictia.ca
# Display name that appears alongside the from address
# Default: Speakr
SMTP_FROM_NAME=Speakr
# Default: DictIA
SMTP_FROM_NAME=DictIA
###############################################################################
# Provider-Specific Examples
###############################################################################
# --- Resend (recommended for DictIA — TLS, DKIM/SPF/DMARC, Cloudflare-friendly) ---
# SMTP_HOST=smtp.resend.com
# SMTP_PORT=587
# SMTP_USE_TLS=true
# SMTP_USERNAME=resend
# SMTP_PASSWORD=re_xxxxxxxxxxxxxxxxxxxxxxxxxxx # Get from https://resend.com/api-keys
# SMTP_FROM_ADDRESS=noreply@dictia.ca # Domain MUST be verified in Resend dashboard
# SMTP_FROM_NAME=DictIA
# --- Gmail ---
# SMTP_HOST=smtp.gmail.com
# SMTP_PORT=587
@@ -104,6 +114,6 @@ SMTP_FROM_NAME=Speakr
# Security Recommendations:
# - Always use TLS or SSL encryption
# - Use app-specific passwords when available (Gmail, etc.)
# - Consider using a dedicated email service (SendGrid, Mailgun, SES)
# - Use app-specific passwords or API keys when available (Resend, Gmail, etc.)
# - For DictIA: prefer Resend (DKIM/SPF/DMARC handled, Loi 25-friendly logs in EU)
# - Set a strong SECRET_KEY in your Flask configuration