Skip to content

Add email signup and spam protection - #5

Merged
adman234 merged 2 commits into
mainfrom
signup-turnstile-monthly
Sep 22, 2026
Merged

adman234 merged 2 commits into
mainfrom
signup-turnstile-monthly

Conversation

@adman234

Copy link
Copy Markdown
Contributor

No description provided.

adman234 and others added 2 commits September 22, 2026 07:34
Email updates. A "Get updates by email" band sits above the footer on every
page: inline, never a popup. It says plainly that the list carries new classes
and events and the occasional organization update, and nothing else. Addresses
go to a Cloudflare KV namespace via src/subscribe.js.

The KV binding in wrangler.jsonc has no id on purpose. Wrangler 4.45+ creates
the namespace on the first deploy and keeps it linked, and Workers Builds'
default token has KV edit permission, so there is nothing to set up by hand.
(It has no D1 permission, which is why this is KV rather than D1.)

Tested end to end against wrangler dev with local KV and Cloudflare's test
Turnstile secret: valid signups store and return to the page they came from;
mixed-case duplicates collapse to one record; bad addresses, missing Turnstile
tokens and off-site redirect targets are rejected; the honeypot pretends to
succeed without storing; the sixth signup in an hour from one connection is
refused; the CSV export 404s without its token.

Spam protection. Turnstile now covers the signup as well as the contact form,
both switched on by params.turnstileSiteKey. On the signup, Turnstile's script
loads only when someone focuses the form, and a submit that beats the check is
held and sent automatically once it passes.

Donate. One-time and monthly are separate Givebutter widgets. Setting
givebutterMonthlyWidgetId adds a labelled "Monthly gift" button beside the
existing one; until it is set, the page is unchanged, so there is never a
monthly button that quietly takes a one-time gift. Also removes "Choose monthly
on the form", which pointed donors at an option the form does not have.

Also gitignores .dev.vars, Wrangler's local secrets file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sets the Turnstile site key, so the widget now appears on the contact form
and the email signup. The secret is not in the repo: it goes into the Worker
as TURNSTILE_SECRET once this has deployed.

Server-side verification moves into src/turnstile.js, shared by both handlers,
and now checks more than `success`. Each widget carries an action (contact,
subscribe) and the server requires the token's action to match the form and
its hostname to match the site, so a token minted for the other form or on
another domain is refused. The handlers are otherwise unchanged: the check
gates them rather than replacing anything. Cloudflare's published test secret
answers for a dummy host with no action and flags the response, so flagged
test responses skip those two checks; the real secret never sets the flag.

Tested: unit tests against every siteverify outcome (genuine, wrong action,
wrong host, replayed, forged, network failure, missing token, no secret), all
passing; and end to end through wrangler dev against Cloudflare's live
siteverify with the always-pass and always-fail test secrets.

Donate: the Givebutter form now offers one-time, monthly and yearly gifts, so
the second-widget mechanism is removed and the "choose Monthly" guidance is
restored, since it is true again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adman234
adman234 merged commit e7edd45 into main Sep 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant