diff --git a/api/api/testimonials.php b/api/api/testimonials.php index 4815440..37648f2 100644 --- a/api/api/testimonials.php +++ b/api/api/testimonials.php @@ -74,11 +74,29 @@ if ($method === 'DELETE' && $id) { } -// POST upload testimonial image (public) +// POST upload testimonial image (public) - rate limited per IP to prevent +// unauthenticated storage/bandwidth abuse (this endpoint has no login requirement). if ($method === "POST" && $id === "upload") { + $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown'; + $limitRow = $db->prepare("SELECT upload_count, window_start FROM upload_rate_limits WHERE ip_address = ?"); + $limitRow->execute([$ip]); + $limit = $limitRow->fetch(); + + $windowFresh = $limit && (strtotime($limit['window_start']) > time() - 3600); + if ($windowFresh && (int)$limit['upload_count'] >= 5) { + jsonResponse(["error" => "Too many uploads. Please try again later."], 429); + } + if (!isset($_FILES["file"])) jsonResponse(["error" => "No file uploaded"], 400); $result = handleImageUpload($_FILES["file"]); if (isset($result['error'])) jsonResponse(["error" => $result['error']], $result['status']); + + if ($windowFresh) { + $db->prepare("UPDATE upload_rate_limits SET upload_count = upload_count + 1 WHERE ip_address = ?")->execute([$ip]); + } else { + $db->prepare("REPLACE INTO upload_rate_limits (ip_address, upload_count, window_start) VALUES (?, 1, NOW())")->execute([$ip]); + } + jsonResponse(["url" => $result['url']]); } diff --git a/api/index.php b/api/index.php index 9437031..a1c8e4d 100644 --- a/api/index.php +++ b/api/index.php @@ -5,7 +5,7 @@ */ // Load configuration and dependencies -require_once __DIR__ . '/config.php'; +require_once '/home/epictravelexpeditions.com/api-secrets.php'; require_once __DIR__ . '/includes/database.php'; require_once __DIR__ . '/includes/jwt.php'; require_once __DIR__ . '/includes/functions.php';