import { LETTERS_PER_HOUR, LETTERS_PER_DAY, NEW_ACCOUNT_HOURS, NEW_ACCOUNT_LETTERS } from "../src/constants.js"; import { inWords } from "./ratelimit.js"; /* * Wat één account per tijdseenheid kan schrijven — de eerste tranche sybil-kosten. * * De grootste dreiging voor een briefapp is niet de vervelende gebruiker (daar is melden voor) maar * de verkoper die zich als dater voordoet, en het script dat hetzelfde doet zonder te typen. Beide * hebben massa nodig. Deze grenzen maken massa duur en vragen daarvoor geen enkel nieuw * persoonsgegeven: er wordt alleen geteld wat er al ligt. * * Drie grenzen, van streng naar ruim: de eerste dag maar een paar brieven (een nieuw account is het * goedkoopste dat er is, dus is het ook het minst waard), daarna LETTERS_PER_HOUR per uur en * LETTERS_PER_DAY per etmaal. * * Wat dit met opzet níet doet: straffen, of een melding aanmaken. Wie hiertegenaan loopt is meestal * gewoon ijverig; het antwoord is een wachttijd, geen dossier. */ /* Seconden tot `from` + `windowHours` — het moment waarop er echt weer een plek vrij is */ const untilSec = (from, windowHours) => (new Date(from).getTime() + windowHours * 3600_000 - Date.now()) / 1000; /* * Mag deze gebruiker nu een brief versturen? `quota` komt van db.letterQuota(): * { createdAt, hour: { count, oldest }, day: { count, oldest } }. * Geeft null als het mag, anders { error, retryAfterSec } — de wachttijd is telkens het echte * moment waarop het weer kan, zodat het antwoord klopt in plaats van alleen afhoudt. */ export function letterDenial({ createdAt, hour, day }) { /* * Startdrempel. Voor een account jonger dan een etmaal is day.count meteen het totaal aantal * verstuurde brieven — daarom moet NEW_ACCOUNT_HOURS 24 of minder blijven (zie src/constants.js). */ const ageHours = (Date.now() - new Date(createdAt).getTime()) / 3600_000; if (ageHours < NEW_ACCOUNT_HOURS && day.count >= NEW_ACCOUNT_LETTERS) { const sec = untilSec(createdAt, NEW_ACCOUNT_HOURS); return { retryAfterSec: sec, error: `Een nieuw account kan de eerste dag ${NEW_ACCOUNT_LETTERS} brieven versturen. Je kunt ${inWords(sec)} weer verder, en daarna vervalt deze grens.`, }; } if (hour.count >= LETTERS_PER_HOUR) { const sec = untilSec(hour.oldest, 1); return { retryAfterSec: sec, error: `Je hebt het afgelopen uur ${LETTERS_PER_HOUR} brieven verstuurd. Je kunt ${inWords(sec)} weer verder.`, }; } if (day.count >= LETTERS_PER_DAY) { const sec = untilSec(day.oldest, 24); return { retryAfterSec: sec, error: `Je hebt in een dag ${LETTERS_PER_DAY} brieven verstuurd — dat is genoeg voor één dag schrijven. Je kunt ${inWords(sec)} weer verder.`, }; } return null; }