Primer version funcional
This commit is contained in:
123
core/RateLimiter.php
Executable file
123
core/RateLimiter.php
Executable file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
class RateLimiter {
|
||||
private $maxRequestsPerMinute = 60;
|
||||
private $maxRequestsPerHour = 1000;
|
||||
|
||||
public function __construct($maxPerMinute = 60, $maxPerHour = 1000) {
|
||||
$this->maxRequestsPerMinute = $maxPerMinute;
|
||||
$this->maxRequestsPerHour = $maxPerHour;
|
||||
}
|
||||
|
||||
public function checkRateLimit($identifier) {
|
||||
$now = time();
|
||||
$oneMinuteAgo = $now - 60;
|
||||
$oneHourAgo = $now - 3600;
|
||||
|
||||
$minuteKey = "rate_limit_minute:$identifier";
|
||||
$hourKey = "rate_limit_hour:$identifier";
|
||||
|
||||
// Obtener datos actuales
|
||||
$minuteData = $this->getRateData($minuteKey);
|
||||
$hourData = $this->getRateData($hourKey);
|
||||
|
||||
// Limpiar peticiones viejas
|
||||
$minuteRequests = array_filter($minuteData, function($timestamp) use ($oneMinuteAgo) {
|
||||
return $timestamp >= $oneMinuteAgo;
|
||||
});
|
||||
|
||||
$hourRequests = array_filter($hourData, function($timestamp) use ($oneHourAgo) {
|
||||
return $timestamp >= $oneHourAgo;
|
||||
});
|
||||
|
||||
// Calcular peticiones restantes
|
||||
$remainingMinute = max(0, $this->maxRequestsPerMinute - count($minuteRequests));
|
||||
$remainingHour = max(0, $this->maxRequestsPerHour - count($hourRequests));
|
||||
|
||||
// Si excede límite por minuto
|
||||
if (count($minuteRequests) >= $this->maxRequestsPerMinute) {
|
||||
$retryAfter = min($minuteRequests) + 60;
|
||||
$this->setRateLimitHeaders($remainingMinute, $this->maxRequestsPerMinute, $retryAfter - $now);
|
||||
throw new RateLimitException('Demasiadas peticiones. Máximo ' . $this->maxRequestsPerMinute . ' por minuto.', $retryAfter - $now);
|
||||
}
|
||||
|
||||
// Si excede límite por hora
|
||||
if (count($hourRequests) >= $this->maxRequestsPerHour) {
|
||||
$retryAfter = min($hourRequests) + 3600;
|
||||
$this->setRateLimitHeaders($remainingHour, $this->maxRequestsPerHour, $retryAfter - $now);
|
||||
throw new RateLimitException('Demasiadas peticiones. Máximo ' . $this->maxRequestsPerHour . ' por hora.', $retryAfter - $now);
|
||||
}
|
||||
|
||||
// Agregar petición actual
|
||||
$minuteRequests[] = $now;
|
||||
$hourRequests[] = $now;
|
||||
|
||||
// Guardar datos actualizados
|
||||
$this->setRateData($minuteKey, $minuteRequests, 120);
|
||||
$this->setRateData($hourKey, $hourRequests, 3600);
|
||||
|
||||
// Establecer headers de rate limit
|
||||
$this->setRateLimitHeaders($remainingMinute, $this->maxRequestsPerMinute, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getRateData($key) {
|
||||
// Usar sesión para rate limiting
|
||||
if (session_status() === PHP_SESSION_ACTIVE && isset($_SESSION[$key])) {
|
||||
return $_SESSION[$key];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function setRateData($key, $data, $ttl = null) {
|
||||
// Usar sesión para rate limiting
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
$_SESSION[$key] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
private function setRateLimitHeaders($remaining, $limit, $retryAfter = null) {
|
||||
header("X-RateLimit-Limit: $limit");
|
||||
header("X-RateLimit-Remaining: $remaining");
|
||||
|
||||
if ($retryAfter !== null) {
|
||||
header("X-RateLimit-Reset: $retryAfter");
|
||||
header("Retry-After: $retryAfter");
|
||||
}
|
||||
}
|
||||
|
||||
public function getIdentifierFromRequest() {
|
||||
// Usar IP como identificador por defecto
|
||||
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['HTTP_CLIENT_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
|
||||
// Si hay token JWT, usar user_id + IP para limitar por usuario
|
||||
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '';
|
||||
|
||||
if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
|
||||
require_once __DIR__ . '/JWT.php';
|
||||
$token = $matches[1];
|
||||
$decoded = JWT::decode($token);
|
||||
|
||||
if ($decoded && isset($decoded['user_id'])) {
|
||||
return 'user_' . $decoded['user_id'];
|
||||
}
|
||||
}
|
||||
|
||||
return 'ip_' . $ip;
|
||||
}
|
||||
}
|
||||
|
||||
class RateLimitException extends Exception {
|
||||
private $retryAfter;
|
||||
|
||||
public function __construct($message, $retryAfter) {
|
||||
parent::__construct($message);
|
||||
$this->retryAfter = $retryAfter;
|
||||
}
|
||||
|
||||
public function getRetryAfter() {
|
||||
return $this->retryAfter;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user