Files
ibiza_sistema/core/RateLimiter.php
Administrador Ibiza 5289fd4133 Primer version funcional
2025-12-29 23:37:11 -06:00

124 lines
4.2 KiB
PHP
Executable File

<?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;
}
}