Sign-up code guard: honeypot, form age, per-IP + global limits, progressive icon check, burst alert
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+25
-1
@@ -143,5 +143,29 @@ window.IAP = (function () {
|
||||
el.hidden = false;
|
||||
} catch (e) {}
|
||||
}
|
||||
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, reportAd, $ };
|
||||
// ── sign-up code request with the invisible guard fields (form age + honeypot)
|
||||
// and the icon check the server asks for only after an IP trips a limit ──
|
||||
const FORM_TS = Date.now();
|
||||
function iconCheck(host, ch, note) {
|
||||
return new Promise(resolve => {
|
||||
host.hidden = false;
|
||||
host.innerHTML = '<div class="small" style="margin:0 0 8px">' + (note ? esc(note) + ' ' : '') + 'Tap the <b>' + esc(ch.prompt) + '</b>.</div>'
|
||||
+ '<div class="icon-check">' + ch.options.map(o => '<button type="button" class="ic-btn">' + esc(o) + '</button>').join('') + '</div>';
|
||||
host.querySelectorAll('.ic-btn').forEach(b => b.addEventListener('click', () => { host.innerHTML = ''; host.hidden = true; resolve(b.textContent); }, { once: true }));
|
||||
});
|
||||
}
|
||||
async function requestCode(email, opts) {
|
||||
const o = opts || {};
|
||||
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();
|
||||
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;
|
||||
}
|
||||
throw new Error('Could not verify. Refresh the page and try again.');
|
||||
}
|
||||
const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
||||
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, reportAd, requestCode, $ };
|
||||
})();
|
||||
|
||||
@@ -43,8 +43,9 @@
|
||||
function busy(btn, fn) {
|
||||
return async () => { if (btn.disabled) return; btn.disabled = true; err(''); try { await fn(); } catch (e) { err(e.message || 'Something went wrong.'); } finally { btn.disabled = false; } };
|
||||
}
|
||||
const codeOpts = () => ({ honeypot: $('jnWebsite'), host: $('jnCheck') });
|
||||
const send = busy($('jnSend'), async () => {
|
||||
const r = await api('/api/auth/email/start', { email: $('jnEmail').value });
|
||||
const r = await IAP.requestCode($('jnEmail').value, codeOpts());
|
||||
$('jnCodeRow').hidden = false; $('jnVerify').hidden = false; $('jnSend').hidden = true; $('jnResend').hidden = false;
|
||||
if (r.devCode) $('jnCode').value = r.devCode;
|
||||
// the last thing they see before the account is created: who they are joining under
|
||||
@@ -54,7 +55,7 @@
|
||||
});
|
||||
$('jnSend').addEventListener('click', send);
|
||||
$('jnResend').addEventListener('click', busy($('jnResend'), async () => {
|
||||
const r = await api('/api/auth/email/start', { email: $('jnEmail').value });
|
||||
const r = await IAP.requestCode($('jnEmail').value, codeOpts());
|
||||
if (r.devCode) $('jnCode').value = r.devCode;
|
||||
IAP.status('Fresh code sent.', 'ok');
|
||||
}));
|
||||
|
||||
+3
-2
@@ -1610,8 +1610,9 @@
|
||||
if (!cfg.emailAuth) return;
|
||||
$('passCards').hidden = true;
|
||||
$('magicCard').hidden = false;
|
||||
const codeOpts = () => ({ honeypot: $('mcWebsite'), host: $('mcCheck') });
|
||||
const start = busy($('mcSendBtn'), async () => {
|
||||
const r = await api('/api/auth/email/start', { email: $('mcEmail').value });
|
||||
const r = await IAP.requestCode($('mcEmail').value, codeOpts());
|
||||
$('mcCodeRow').hidden = false;
|
||||
$('mcVerifyBtn').hidden = false;
|
||||
$('mcSendBtn').hidden = true;
|
||||
@@ -1622,7 +1623,7 @@
|
||||
});
|
||||
$('mcSendBtn').addEventListener('click', start);
|
||||
$('mcResend').addEventListener('click', busy($('mcResend'), async () => {
|
||||
const r = await api('/api/auth/email/start', { email: $('mcEmail').value });
|
||||
const r = await IAP.requestCode($('mcEmail').value, codeOpts());
|
||||
if (r.devCode) $('mcCode').value = r.devCode;
|
||||
IAP.status('Fresh code sent.', 'ok');
|
||||
}));
|
||||
|
||||
@@ -628,6 +628,10 @@ img{max-width:100%}
|
||||
/* ── promo tools (2026-09-09) ── */
|
||||
.promo-strip{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;padding:16px 20px}
|
||||
.angle-list{display:flex;flex-direction:column;gap:8px}
|
||||
.hp-field{position:absolute;left:-9999px;top:auto;width:1px;height:1px;opacity:0;overflow:hidden}
|
||||
.icon-check{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 12px}
|
||||
.icon-check .ic-btn{font-size:26px;line-height:1;padding:10px 14px;border-radius:12px;border:1px solid var(--line-strong);background:var(--panel);cursor:pointer}
|
||||
.icon-check .ic-btn:hover{border-color:var(--mint)}
|
||||
.angle-row.pb-acc>summary{padding:12px 14px;font-size:14.5px}
|
||||
.angle-row .angle-body{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;padding:0 14px 14px}
|
||||
.angle-row .angle-txt{flex:1 1 320px;min-width:0}
|
||||
|
||||
+4
-2
@@ -111,6 +111,8 @@
|
||||
<h3 id="jnCapH">Join free</h3>
|
||||
<p class="muted small" style="margin:0 0 12px" id="jnCapSub">Type your email and we send a 6-digit code. No password, no wallet needed today.</p>
|
||||
<p><input id="jnEmail" type="email" placeholder="Your email" autocomplete="email"></p>
|
||||
<input id="jnWebsite" class="hp-field" type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true">
|
||||
<div id="jnCheck" hidden></div>
|
||||
<p id="jnCodeRow" hidden><input id="jnCode" inputmode="numeric" placeholder="6-digit code from your inbox"></p>
|
||||
<p id="jnUnder" class="small muted" hidden style="margin:-4px 0 12px"></p>
|
||||
<label class="small muted" style="display:flex;gap:8px;align-items:flex-start;margin:0 0 12px;cursor:pointer">
|
||||
@@ -153,7 +155,7 @@
|
||||
InstantAdPay · <a href="/contract">Contract</a> · <a href="/terms">Terms</a> · <a href="/privacy">Privacy</a> · <a href="/disclaimer">Disclaimer</a>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260910c"></script>
|
||||
<script src="/assets/join.js?v=20260910d"></script>
|
||||
<script src="/assets/common.js?v=20260911a"></script>
|
||||
<script src="/assets/join.js?v=20260911a"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+5
-3
@@ -5,7 +5,7 @@
|
||||
<title>Member area | InstantAdPay</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="icon" type="image/png" href="/logo-icon.png">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260911a">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260911b">
|
||||
</head>
|
||||
<body class="bo-body">
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
<p class="muted small">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.</p>
|
||||
<p><input id="mcEmail" type="email" placeholder="Email" autocomplete="email" style="width:100%"></p>
|
||||
<input id="mcWebsite" class="hp-field" type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true">
|
||||
<div id="mcCheck" hidden></div>
|
||||
<p id="mcCodeRow" hidden><input id="mcCode" inputmode="numeric" placeholder="6-digit code" style="width:100%"></p>
|
||||
<button class="btn" id="mcSendBtn">Email me a code</button>
|
||||
<button class="btn" id="mcVerifyBtn" hidden>Sign in</button>
|
||||
@@ -879,10 +881,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260910c"></script>
|
||||
<script src="/assets/common.js?v=20260911a"></script>
|
||||
<script src="/assets/wallet.js?v=20260910a"></script>
|
||||
<script src="/assets/promo.js?v=20260911a"></script>
|
||||
<script src="/assets/my.js?v=20260910m"></script>
|
||||
<script src="/assets/my.js?v=20260911a"></script>
|
||||
<script src="/assets/chat.js?v=20260907l"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user