diff --git a/public/assets/common.js b/public/assets/common.js index f38cc84..4a9309a 100644 --- a/public/assets/common.js +++ b/public/assets/common.js @@ -159,7 +159,7 @@ window.IAP = (function () { let pick = null; for (let i = 0; i < 4; i++) { const r = await (await fetch('/api/auth/email/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ email, fts: FORM_TS, website: (o.honeypot && o.honeypot.value) || '', pick }) })).json(); + body: JSON.stringify({ email, fts: FORM_TS, hp_field_x9: (o.honeypot && o.honeypot.value) || '', pick }) })).json(); if (r.challenge && o.host) { pick = await iconCheck(o.host, r.challenge, r.error); continue; } if (r.error) throw new Error(r.error); return r; diff --git a/public/join.html b/public/join.html index f98a03e..3233636 100644 --- a/public/join.html +++ b/public/join.html @@ -111,7 +111,7 @@

Join free

Type your email and we send a 6-digit code. No password, no wallet needed today.

- + @@ -155,7 +155,7 @@ InstantAdPay · Contract · Terms · Privacy · Disclaimer - + diff --git a/public/launch.html b/public/launch.html index aedd743..c219f6d 100644 --- a/public/launch.html +++ b/public/launch.html @@ -85,7 +85,7 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees.
- + diff --git a/public/my.html b/public/my.html index 923f313..3bce48b 100644 --- a/public/my.html +++ b/public/my.html @@ -34,7 +34,7 @@

Type your email and we send a 6-digit code. No password to invent, no password to forget. New emails get a free account automatically.

- + @@ -899,7 +899,7 @@ - + diff --git a/public/plays.html b/public/plays.html index 21338fc..9d99c4c 100644 --- a/public/plays.html +++ b/public/plays.html @@ -192,7 +192,7 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.
- + diff --git a/public/wallets.html b/public/wallets.html index ad7138d..a1dcba0 100644 --- a/public/wallets.html +++ b/public/wallets.html @@ -121,7 +121,7 @@
Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never share your recovery phrase.
- + diff --git a/server.js b/server.js index 2c2e1db..f72d814 100644 --- a/server.js +++ b/server.js @@ -133,14 +133,18 @@ function codeChallenge(rec) { return { prompt: pick[answer][1], options: pick.map(x => x[0]) }; } // returns null to allow the send, or { status, body } to answer with instead +const guardLog = (req, why, b) => console.log('signup-guard', why, clientIp(req), String(b.email || '').replace(/^(.).*(@.*)$/, '$1***$2')); function codeGuard(req, b) { const now = Date.now(); - if (b.website) return { status: 200, body: { ok: true, sent: true } }; // honeypot: bots fill it, humans never see it + // honeypot: bots fill it, humans never see it. The field carries a nonsense name so browser + // autofill (which likes "website" and "url") cannot fill it for a real person. + if (b.hp_field_x9) { guardLog(req, 'honeypot', b); return { status: 200, body: { ok: true, sent: true } }; } const fts = Number(b.fts) || 0; - if (!fts || now - fts < CODE_LIMITS.minFormMs || now - fts > 12 * 3600 * 1000) return { status: 400, body: { error: 'Give the page a second, then tap again.' } }; + if (!fts || now - fts < CODE_LIMITS.minFormMs) { guardLog(req, 'form-age', b); return { status: 400, body: { error: 'Give the page a second, then tap again.' } }; } + if (now - fts > 12 * 3600 * 1000) { guardLog(req, 'form-stale', b); return { status: 400, body: { error: 'This page has been open a long time. Refresh it, then tap again.' } }; } const minute = Math.floor(now / 60000); if (codeGlobal.minute !== minute) { codeGlobal.minute = minute; codeGlobal.n = 0; } - if (codeGlobal.n >= CODE_LIMITS.globalPerMin) { codeTrip(req, 'global'); return { status: 429, body: { error: 'Busy right now. Try again in a minute.' } }; } + if (codeGlobal.n >= CODE_LIMITS.globalPerMin) { guardLog(req, 'global-limit', b); codeTrip(req, 'global'); return { status: 429, body: { error: 'Busy right now. Try again in a minute.' } }; } const ip = clientIp(req); const rec = codeHits.get(ip) || { t: [], passUntil: 0, chal: null }; rec.t = rec.t.filter(ts => now - ts < 24 * 3600 * 1000); @@ -150,7 +154,7 @@ function codeGuard(req, b) { const pick = String(b.pick || ''); if (pick && rec.chal && rec.chal.exp > now && pick === rec.chal.answer) { rec.passUntil = now + CODE_LIMITS.passMs; rec.chal = null; } else { - codeTrip(req, ip); + guardLog(req, pick ? 'wrong-pick' : 'ip-limit', b); codeTrip(req, ip); const challenge = codeChallenge(rec); codeHits.set(ip, rec); return { status: 429, body: { error: pick ? 'That was not it. Try once more.' : 'Quick check before we send another code.', challenge } }; } @@ -722,7 +726,7 @@ const server = http.createServer(async (req, res) => { const e = String(b.email || '').trim().toLowerCase(); if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(e)) return json(res, 400, { error: 'That email address does not look right.' }); const prev = emailCodes.get(e); - if (prev && Date.now() < prev.nextAt) return json(res, 429, { error: 'Code already sent. Give it a minute, then try again.' }); + if (prev && Date.now() < prev.nextAt) { console.log('signup-guard cooldown', clientIp(req), e.replace(/^(.).*(@.*)$/, '$1***$2')); return json(res, 429, { error: 'Code already sent. Give it a minute, then try again.' }); } const guard = codeGuard(req, b); // honeypot, form age, per-IP + global limits, icon check once limited if (guard) return json(res, guard.status, guard.body); const code = String(Math.floor(100000 + Math.random() * 900000));