Sign-up deny list beside the allow list

fraud-allow.json says "never block this person". There was nothing for the opposite case.
fraud-block.json: an address on it cannot open an account through /api/signup or the
email-code door, and the admin fraud report carries the list with a matching
/api/admin/fraud/block route. Existing accounts are untouched; Suspend covers those.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-19 05:46:04 -05:00
parent b061053ccf
commit 39572c0ff5
2 changed files with 25 additions and 2 deletions
+17 -1
View File
@@ -55,6 +55,22 @@ function allowAdd(email, note, by) {
}
function allowRemove(email) { const e = norm(email); const d = allowLoad(); if (d[e]) { delete d[e]; allowSave(); } return { ok: true, list: allowList() }; }
// The opposite list. An address here can never open an account, through any door. Same
// shape and file pattern as the allow list so it behaves identically in DB and JSON mode.
const BLOCK_FILE = () => path.join(DATA_DIR, 'fraud-block.json');
let blockDb = null; // { "<email>": { note, at, by } }
function blockLoad() { if (blockDb) return blockDb; try { blockDb = JSON.parse(fs.readFileSync(BLOCK_FILE(), 'utf8')); } catch (e) { blockDb = {}; } if (!blockDb || typeof blockDb !== 'object') blockDb = {}; return blockDb; }
function blockSave() { try { fs.writeFileSync(BLOCK_FILE(), JSON.stringify(blockLoad())); } catch (e) { console.error('fraud block save', e.message); } }
function isBlocked(email) { const e = norm(email); return !!(e && blockLoad()[e]); }
function blockList() { const d = blockLoad(); return Object.keys(d).sort().map(e => ({ email: e, note: d[e].note || '', at: d[e].at || 0, by: d[e].by || '' })); }
function blockAdd(email, note, by) {
const e = norm(email); if (!e || !e.includes('@')) return { error: 'Enter an email address.' };
blockLoad()[e] = { note: String(note || '').slice(0, 200), at: Date.now(), by: String(by || '').slice(0, 120) };
blockSave();
return { ok: true, list: blockList() };
}
function blockRemove(email) { const e = norm(email); const d = blockLoad(); if (d[e]) { delete d[e]; blockSave(); } return { ok: true, list: blockList() }; }
async function init(opts) {
DATA_DIR = opts.dataDir;
if (db.enabled()) {
@@ -199,4 +215,4 @@ async function report() {
function mask(e) { return String(e || '').replace(/^(.{2}).*(@.*)$/, '$1***$2'); }
module.exports = { init, checkSignup, recordSignup, recordSeen, addFlags, clearFlags, suspend, unsuspend, isSuspended, excluded, report, get, deviceOf, hasAccountOnDevice, newDeviceId, deviceCookie, ipOf, HARD,
isAllowed, allowList, allowAdd, allowRemove };
isAllowed, allowList, allowAdd, allowRemove, isBlocked, blockList, blockAdd, blockRemove };