Block example.com emails and homesites under 10
Spam submissions were still getting through with placeholder data. Two more content filters on the public form endpoints: - Reject emails from reserved documentation domains (example.com/.org/ /.net/.edu and subdomains) and reserved TLDs (.test/.example/.invalid/ localhost). testing@example.com and friends are never real leads. - Reject a homesites count below 10. Real associations are larger; the junk uses 0/1/2. Both are validated server-side in security.js (validateEmail gains a domain blocklist, new validateHomesites) and mirrored client-side in app.js for immediate feedback. The homesites input min attribute goes from 1 to 10. Blocked submissions return 400 with a `field` hint and store nothing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
25
app.js
25
app.js
@@ -125,12 +125,24 @@
|
||||
// gives immediate feedback instead of a round-trip.
|
||||
const EMAIL_RX = /^[A-Za-z0-9](?:[A-Za-z0-9._%+-]{0,62}[A-Za-z0-9])?@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*\.[A-Za-z]{2,24}$/;
|
||||
|
||||
// Reserved documentation domains / TLDs — mirrors security.js. Real leads
|
||||
// never come from these; spam bots use them.
|
||||
const BLOCKED_EMAIL_DOMAINS = ['example.com', 'example.org', 'example.net', 'example.edu'];
|
||||
const BLOCKED_EMAIL_TLDS = ['test', 'example', 'invalid', 'localhost'];
|
||||
|
||||
function isValidEmail(v) {
|
||||
if (v.length > 254 || v.indexOf('@') > 64) return false;
|
||||
if (v.includes('..')) return false;
|
||||
return EMAIL_RX.test(v);
|
||||
if (!EMAIL_RX.test(v)) return false;
|
||||
const domain = v.slice(v.indexOf('@') + 1).toLowerCase();
|
||||
const tld = domain.slice(domain.lastIndexOf('.') + 1);
|
||||
if (BLOCKED_EMAIL_TLDS.includes(tld)) return false;
|
||||
if (BLOCKED_EMAIL_DOMAINS.some(d => domain === d || domain.endsWith('.' + d))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
const MIN_HOMESITES = 10;
|
||||
|
||||
function showCalcError(msg) {
|
||||
if (!calcErr) return;
|
||||
calcErr.textContent = msg;
|
||||
@@ -159,6 +171,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (homesites < MIN_HOMESITES) {
|
||||
showCalcError(`Please enter the number of homesites in your community (minimum ${MIN_HOMESITES}).`);
|
||||
document.getElementById('calcHomesites')?.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Email is optional, but anything entered must be a real address.
|
||||
if (calcEmail && !isValidEmail(calcEmail)) {
|
||||
showCalcError('Please enter a valid email address.');
|
||||
@@ -243,10 +261,11 @@
|
||||
// is stored. AI/service errors (502/503) fall through to the local result.
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (data.blocked || data.field === 'email') {
|
||||
if (data.blocked || data.field) {
|
||||
setCalcLoading(false);
|
||||
showCalcError(data.error || 'We could not verify this submission. Please try again.');
|
||||
if (data.field === 'email') document.getElementById('calcEmail')?.focus();
|
||||
if (data.field === 'email') document.getElementById('calcEmail')?.focus();
|
||||
if (data.field === 'homesites') document.getElementById('calcHomesites')?.focus();
|
||||
resetCaptcha();
|
||||
await fetchFormToken(); // tokens are single-use; issue a fresh one
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user