Files
worldmonitor/api/_rate-limit.js
Elie Habib 36e36d8b57 Cost/traffic hardening, runtime fallback controls, and PostHog removal (#638)
- Remove PostHog analytics runtime and configuration
- Add API rate limiting (api/_rate-limit.js)
- Harden traffic controls across edge functions
- Add runtime fallback controls and data-loader improvements
- Add military base data scripts (fetch-mirta-bases, fetch-osm-bases)
- Gitignore large raw data files
- Settings playground prototypes
2026-03-01 11:53:20 +04:00

59 lines
1.4 KiB
JavaScript

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
let ratelimit = null;
function getRatelimit() {
if (ratelimit) return ratelimit;
const url = process.env.UPSTASH_REDIS_REST_URL;
const token = process.env.UPSTASH_REDIS_REST_TOKEN;
if (!url || !token) return null;
ratelimit = new Ratelimit({
redis: new Redis({ url, token }),
limiter: Ratelimit.slidingWindow(300, '60 s'),
prefix: 'rl',
analytics: false,
});
return ratelimit;
}
function getClientIp(request) {
return (
request.headers.get('x-real-ip') ||
request.headers.get('cf-connecting-ip') ||
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
'0.0.0.0'
);
}
export async function checkRateLimit(request, corsHeaders) {
const rl = getRatelimit();
if (!rl) return null;
const ip = getClientIp(request);
try {
const { success, limit, reset } = await rl.limit(ip);
if (!success) {
return new Response(JSON.stringify({ error: 'Too many requests' }), {
status: 429,
headers: {
'Content-Type': 'application/json',
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': String(reset),
'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
...corsHeaders,
},
});
}
return null;
} catch {
return null;
}
}