// Approved exceptions: people Marty has okayed to hold more than one account. // // The guard blocks a second sign-up from a browser that already has an account. For an // approved person it must let them through, must NOT leave a hard flag (that is what // silently drops an account off the leaderboard and out of the holding tank), and must // still respect suspension. // // node qa/fraud-allow.mjs import { spawn } from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; const PORT = 8801; const B = 'http://127.0.0.1:' + PORT; const DATA = path.join(os.tmpdir(), 'iap-fraud-allow-' + Date.now()); fs.mkdirSync(DATA, { recursive: true }); const ok = [], bad = []; const t = (n, c, extra) => { (c ? ok : bad).push(n + (c || !extra ? '' : ' -> ' + extra)); }; const srv = spawn(process.execPath, ['server.js'], { env: { ...process.env, PORT: String(PORT), DATA_DIR: DATA, ADMIN_PASSWORD: 'localtest', ADMIN_EMAIL: 'admin@example.com', NODE_ENV: 'test' }, stdio: ['ignore', 'pipe', 'pipe'] }); const bye = c => { try { srv.kill(); } catch (e) {} try { fs.rmSync(DATA, { recursive: true, force: true }); } catch (e) {} process.exit(c); }; for (let i = 0; i < 60; i++) { try { if ((await fetch(B + '/api/stats')).ok) break; } catch (e) {} await new Promise(r => setTimeout(r, 500)); } const DEV = 'dededededededededededededededede'; const post = (p, body, hdrs) => fetch(B + p, { method: 'POST', headers: Object.assign({ 'Content-Type': 'application/json' }, hdrs || {}), body: JSON.stringify(body) }) .then(async r => ({ status: r.status, body: await r.json().catch(() => ({})) })); // isAdmin() accepts a bearer token, which is far simpler here than a portal session const ADMIN = { Authorization: 'Bearer localtest' }; // create the first account on this browser // NOTE: the duplicate checks run at VERIFY, not at start. Starting a code is harmless; // creating the account is the act that gets blocked. const signUp = async (email) => { const s = await post('/api/auth/email/start', { email, fts: Date.now() - 20000 }, { Cookie: 'iap.dev=' + DEV }); if (s.status !== 200 || !s.body.devCode) return s; return await post('/api/auth/email/verify', { email, code: s.body.devCode }, { Cookie: 'iap.dev=' + DEV }); }; const first = await signUp('primary@example.com'); t('the first account on a browser is created', first.status === 200 && !first.body.error, JSON.stringify(first.body).slice(0, 140)); // a SECOND account on the same browser is blocked, as designed const blocked = await signUp('second@example.com'); t('a second account on the same browser is blocked by default', blocked.status === 403, blocked.status + ' ' + JSON.stringify(blocked.body).slice(0, 120)); t('and the refusal points at Qualified Start', /Qualified Start/i.test(String(blocked.body.error || '')), String(blocked.body.error || '').slice(0, 90)); // admin approves the exception on the EXISTING account, which is the usual case const added = await post('/api/admin/fraud/allow', { email: 'primary@example.com', note: 'business partner, approved' }, ADMIN); t('the exception is accepted', added.status === 200 && Array.isArray(added.body.list), JSON.stringify(added.body).slice(0, 140)); t('and it is listed with its note', (added.body.list || []).some(a => a.email === 'primary@example.com' && /partner/.test(a.note)), JSON.stringify(added.body.list)); // now the same second sign-up goes through const allowed = await signUp('second@example.com'); t('the approved person can now create the second account', allowed.status === 200 && !allowed.body.error, allowed.status + ' ' + JSON.stringify(allowed.body).slice(0, 140)); // and it must not be left carrying a hard flag const rep = await fetch(B + '/api/admin/fraud', { headers: ADMIN }).then(r => r.json()); // Only the HARD flags matter: those are what drop an account off the leaderboard and out // of the holding tank. 'shared-ip' is informational and carries no penalty, so an approved // person may legitimately still show it. const HARD = ['dup-device', 'ip-burst', 'sponsor-device', 'sponsor-ip', 'multi-account']; const row = (rep.flagged || []).find(f => f.email === 'second@example.com'); const hard = ((row && row.flags) || []).filter(f => HARD.includes(f)); t('the new account carries NO hard flag', hard.length === 0, JSON.stringify(row || {})); t('and it is marked as allowlisted so the admin sees why', !row || (row.flags || []).includes('allowlisted'), JSON.stringify(row || {})); t('the exception shows in the admin report', (rep.allow || []).some(a => a.email === 'primary@example.com'), JSON.stringify(rep.allow)); // removing the exception restores the normal rule await post('/api/admin/fraud/allow', { email: 'primary@example.com', remove: true }, ADMIN); const reBlocked = await signUp('third@example.com'); t('removing the exception blocks again', reBlocked.status === 403, reBlocked.status + ' ' + JSON.stringify(reBlocked.body).slice(0, 110)); // a bad address is rejected rather than stored const badAdd = await post('/api/admin/fraud/allow', { email: 'not-an-email', note: '' }, ADMIN); t('a malformed address is refused', badAdd.status === 400, JSON.stringify(badAdd.body).slice(0, 100)); // and the endpoint is admin-only const noAuth = await post('/api/admin/fraud/allow', { email: 'x@example.com' }); t('the endpoint requires admin', noAuth.status === 401, String(noAuth.status)); console.log('PASS ' + ok.length); for (const b of bad) console.log('FAIL ' + b); bye(bad.length ? 1 : 0);