Follow-up to 208638e. Code review surfaced a few real issues; this
commit addresses them.
- login.post.js now uses the new sendMagicLink util instead of
duplicating the jti/jwt/Resend/logActivity logic. Reduces 60 lines.
- sendMagicLink accepts an optional pre-loaded Member doc, skipping
the redundant findOne when the caller already has one. customer.post.js
passes the just-created/upgraded member, dropping signup from 3
Mongo round-trips to 1 (lookup is gone; jti burn remains).
- sendMagicLink now lowercases the email defensively so callers don't
have to remember.
- rateLimit.js: replaced an effectively-dead eviction line with a
probabilistic sweep (~1% of calls scan and evict keys whose newest
entry has aged out). Caps unbounded Map growth under random-key
spraying.
- reconcile-payments.post.js: 401/403/404 from Helcim now bails out
immediately instead of burning all 3 retry attempts; dry-run
summary filters via the same RECONCILABLE_STATUSES set as apply
mode so counts match.
- Deleted WHAT-comments and section banners per CLAUDE.md no-comment
rule. Kept genuine WHY-comments (validateBeforeSave rationale,
amount-IGNORED-for-tickets, sendConfirmation deliberately-omitted).
Tests: 758/760 passing (unchanged).
31 lines
930 B
JavaScript
31 lines
930 B
JavaScript
// Tiny in-memory sliding-window rate limiter.
|
|
// Acceptable for single-instance Nitro on Netlify; swap to Mongo/Upstash if
|
|
// we move to multi-instance.
|
|
const buckets = new Map()
|
|
|
|
export function rateLimit(key, { max, windowMs }) {
|
|
const now = Date.now()
|
|
|
|
// Probabilistic sweep: ~1% of calls evict keys whose newest entry has fully
|
|
// aged out, so the Map doesn't grow unbounded under random-key spraying.
|
|
if (Math.random() < 0.01) {
|
|
for (const [k, arr] of buckets) {
|
|
const last = arr[arr.length - 1]
|
|
if (last === undefined || now - last >= windowMs) buckets.delete(k)
|
|
}
|
|
}
|
|
|
|
const arr = (buckets.get(key) || []).filter((t) => now - t < windowMs)
|
|
if (arr.length >= max) {
|
|
buckets.set(key, arr)
|
|
return false
|
|
}
|
|
arr.push(now)
|
|
buckets.set(key, arr)
|
|
return true
|
|
}
|
|
|
|
// Test helper — clears all buckets so each test starts clean.
|
|
export function resetRateLimit() {
|
|
buckets.clear()
|
|
}
|