chat-dev / server /guestRequestLimiter.js
incognitolm
Per-IP Guest Rate Limiter
8dde1b0
import { loadEncryptedJson, saveEncryptedJson } from './cryptoUtils.js';
const REQUESTS_FILE = '/data/guest_request_counts.json';
const WINDOW_MS = 24 * 60 * 60 * 1000;
const MAX_LOGGED_OUT_REQUESTS = 50;
const ipCounters = new Map();
let loaded = false;
async function loadGuestCounters() {
if (loaded) return;
loaded = true;
const data = await loadEncryptedJson(REQUESTS_FILE);
if (!data) return;
for (const [ip, entry] of Object.entries(data)) {
ipCounters.set(ip, {
count: typeof entry.count === 'number' ? entry.count : 0,
resetAt: typeof entry.resetAt === 'number' ? entry.resetAt : Date.now() + WINDOW_MS,
});
}
}
async function saveGuestCounters() {
const data = {};
for (const [ip, entry] of ipCounters) {
data[ip] = { count: entry.count, resetAt: entry.resetAt };
}
await saveEncryptedJson(REQUESTS_FILE, data);
}
function cleanupExpired() {
const now = Date.now();
for (const [ip, entry] of ipCounters) {
if (entry.resetAt <= now) {
ipCounters.delete(ip);
}
}
}
export async function initGuestRequestLimiter() {
await loadGuestCounters().catch(err => console.error('Failed to load guest request counters:', err));
cleanupExpired();
setInterval(() => {
cleanupExpired();
}, 60 * 60 * 1000);
}
export async function consumeGuestRequest(ip) {
await loadGuestCounters();
const now = Date.now();
let entry = ipCounters.get(ip);
if (!entry || entry.resetAt <= now) {
entry = { count: 0, resetAt: now + WINDOW_MS };
ipCounters.set(ip, entry);
}
if (entry.count >= MAX_LOGGED_OUT_REQUESTS) {
return false;
}
entry.count += 1;
await saveGuestCounters().catch(err => console.error('Failed to save guest request counters:', err));
return true;
}
export function getGuestRequestsRemaining(ip) {
const now = Date.now();
const entry = ipCounters.get(ip);
if (!entry || entry.resetAt <= now) return MAX_LOGGED_OUT_REQUESTS;
return Math.max(0, MAX_LOGGED_OUT_REQUESTS - entry.count);
}