Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions src/lib/server/contact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
15 changes: 6 additions & 9 deletions src/lib/server/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion src/routes/contact/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
});
}

Expand Down
Loading