mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 09:31:13 -05:00
Add rate limiting to location detection and standardize error messages
Introduces rate limiting to the `detect-location` function and refactors error responses in `upload-image` to provide consistent, descriptive messages. Replit-Commit-Author: Agent Replit-Commit-Session-Id: b3d7d4df-59a9-4a9c-971d-175b92dadbfa Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7cdf4e95-3f41-4180-b8e3-8ef56d032c0e/b3d7d4df-59a9-4a9c-971d-175b92dadbfa/1VcvPNb
This commit is contained in:
@@ -11,6 +11,40 @@ interface IPLocationResponse {
|
||||
measurementSystem: 'metric' | 'imperial';
|
||||
}
|
||||
|
||||
// Simple in-memory rate limiter
|
||||
const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
|
||||
const RATE_LIMIT_WINDOW = 60000; // 1 minute in milliseconds
|
||||
const MAX_REQUESTS = 10; // 10 requests per minute per IP
|
||||
|
||||
function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } {
|
||||
const now = Date.now();
|
||||
const existing = rateLimitMap.get(ip);
|
||||
|
||||
if (!existing || now > existing.resetAt) {
|
||||
// Create new entry or reset expired entry
|
||||
rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW });
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
if (existing.count >= MAX_REQUESTS) {
|
||||
const retryAfter = Math.ceil((existing.resetAt - now) / 1000);
|
||||
return { allowed: false, retryAfter };
|
||||
}
|
||||
|
||||
existing.count++;
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
// Clean up old entries periodically to prevent memory leak
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
for (const [ip, data] of rateLimitMap.entries()) {
|
||||
if (now > data.resetAt) {
|
||||
rateLimitMap.delete(ip);
|
||||
}
|
||||
}
|
||||
}, RATE_LIMIT_WINDOW);
|
||||
|
||||
serve(async (req) => {
|
||||
// Handle CORS preflight requests
|
||||
if (req.method === 'OPTIONS') {
|
||||
@@ -23,6 +57,26 @@ serve(async (req) => {
|
||||
const realIP = req.headers.get('x-real-ip');
|
||||
const clientIP = forwarded?.split(',')[0] || realIP || '8.8.8.8'; // fallback to Google DNS for testing
|
||||
|
||||
// Check rate limit
|
||||
const rateLimit = checkRateLimit(clientIP);
|
||||
if (!rateLimit.allowed) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Rate limit exceeded',
|
||||
message: 'Too many requests. Please try again later.',
|
||||
retryAfter: rateLimit.retryAfter
|
||||
}),
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'Retry-After': String(rateLimit.retryAfter || 60)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
console.log('Detecting location for IP:', clientIP);
|
||||
|
||||
// Use configurable geolocation service with proper error handling
|
||||
|
||||
Reference in New Issue
Block a user