feat: phase 5 contact form — hCaptcha, honeypot, rate limit, notify
Working /contact POST flow: honeypot → hCaptcha server-verify → field validation → SlowAPI 3/hr IP rate limit → contact_submissions row → best-effort Resend notification (Reply-To = submitter) → generic success page. Spam paths don't persist and render the same success page (anti-enumeration). Send failures don't break the request path — the row is already durable. New services: HCaptchaService (async httpx + dev fallback), ContactService. EmailService gains send_contact_notification. Production config validator now requires ADMIN_CONTACT_EMAIL, HCAPTCHA_SECRET, HCAPTCHA_SITE_KEY. 23 new tests, all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
16
app/main.py
16
app/main.py
@@ -66,8 +66,10 @@ from app.services.admin_pages import AdminPagesService
|
||||
from app.services.admin_posts import AdminPostsService
|
||||
from app.services.audit import AuditService
|
||||
from app.services.auth import AuthService
|
||||
from app.services.contact import ContactService
|
||||
from app.services.csrf import CSRF_COOKIE_NAME, CSRFService
|
||||
from app.services.email import EmailService
|
||||
from app.services.hcaptcha import HCaptchaService
|
||||
from app.services.markdown import MarkdownService
|
||||
from app.services.media import MediaService
|
||||
from app.services.pages import PageService
|
||||
@@ -236,6 +238,20 @@ def create_app() -> FastAPI:
|
||||
application.state.session_service = session_service
|
||||
application.state.auth_service = auth_service
|
||||
|
||||
# --- Phase 5 wiring -----------------------------------------------------
|
||||
# Public contact form. HCaptchaService has no DB; ContactService
|
||||
# persists via the shared engine and delegates email to the
|
||||
# existing EmailService so the dev-fallback semantics match.
|
||||
hcaptcha_service = HCaptchaService(settings)
|
||||
contact_service = ContactService(
|
||||
engine=engine,
|
||||
email=email_service,
|
||||
audit=audit_service,
|
||||
settings=settings,
|
||||
)
|
||||
application.state.hcaptcha_service = hcaptcha_service
|
||||
application.state.contact_service = contact_service
|
||||
|
||||
# --- Phase 4 wiring -----------------------------------------------------
|
||||
# CSRF signer: separate salt so a session cookie never validates
|
||||
# as a CSRF token (domain separation via salt).
|
||||
|
||||
Reference in New Issue
Block a user