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:
2026-04-22 06:47:06 -05:00
parent 67c848f329
commit d9090f5055
16 changed files with 1671 additions and 39 deletions

View File

@@ -490,6 +490,26 @@ a:focus-visible {
margin-top: var(--space-2);
}
/* Phase 5: inline field errors + top-level banner. Scoped to the
contact form so the red tone does not bleed into other forms. */
.contact-form__field-error,
.contact-form__error {
margin: var(--space-1) 0 0;
color: #8b2e2e;
font-size: 0.95rem;
}
.contact-form__error {
padding: var(--space-2) var(--space-3);
background-color: #fbe9e7;
border: 1px solid #d9a8a1;
border-radius: var(--radius);
}
.contact-form__captcha {
margin-top: var(--space-2);
}
/* Generic button. */
.btn {
display: inline-block;