diff --git a/src/lib/server/contact.test.ts b/src/lib/server/contact.test.ts index 5761392..454abc7 100644 --- a/src/lib/server/contact.test.ts +++ b/src/lib/server/contact.test.ts @@ -132,17 +132,16 @@ describe('turnstile verification', () => { expect(r.reason).toContain('invalid-input-response'); }); - it('allows a submission with no token rather than blocking the form', async () => { + it('rejects a submission with no token, without calling the network', async () => { let called = false; const fetcher = async () => { called = true; return ok(); }; - // A missing token means the widget did not render or was blocked. Failing - // closed here would take the contact form down for anyone running a - // script blocker. + // A bot that never runs JavaScript produces no token at all, so this is + // the case that catches the crudest spam. const r = await verifyTurnstile('', 'sec', undefined, fetcher as typeof fetch); - expect(r.ok).toBe(true); + expect(r.ok).toBe(false); expect(r.reason).toContain('missing'); expect(called).toBe(false); }); diff --git a/src/lib/server/contact.ts b/src/lib/server/contact.ts index 1dfd5f1..1a3b945 100644 --- a/src/lib/server/contact.ts +++ b/src/lib/server/contact.ts @@ -19,15 +19,12 @@ export async function verifyTurnstile( fetcher: typeof fetch, ): Promise<{ ok: boolean; reason?: string }> { if (!secret) return { ok: true }; - // A missing token means the widget never rendered or was blocked, not that - // the sender is a bot. Blocking here takes the whole contact form down for - // anyone running a script blocker, which is worse than the spam it prevents. - // Cloudflare only sees real submissions anyway, and the rate limiter still - // applies. Log it so a widget that silently stops working is visible. - if (!token) { - console.warn('Contact submission had no Turnstile token; allowing it through'); - return { ok: true, reason: 'missing turnstile token' }; - } + // Rejected on purpose. A bot that does not run JavaScript never produces a + // token at all, so letting these through would wave past the crudest and + // most common kind of spam. The cost is that anyone whose blocker eats + // challenges.cloudflare.com cannot send, which is why the form points them + // at another way to reach out. + if (!token) return { ok: false, reason: 'missing turnstile token' }; const body = new URLSearchParams({ secret, response: token }); if (remoteip) body.set('remoteip', remoteip); diff --git a/src/routes/contact/+page.server.ts b/src/routes/contact/+page.server.ts index 72964cb..c94e54e 100644 --- a/src/routes/contact/+page.server.ts +++ b/src/routes/contact/+page.server.ts @@ -47,7 +47,8 @@ export const actions = { console.warn('Turnstile rejected a submission:', turnstile.reason); return fail(400, { success: false, - message: 'Could not verify you are human. Please try again.', + message: + 'Could not verify you are human. Reload the page and try again, or if you block scripts, reach me on one of the links below.', }); }