<?php
|
namespace JVBase\rest;
|
|
use WP_REST_Request;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
/**
|
* Handles rate limiting for REST requests
|
*/
|
class RateLimits
|
{
|
protected string $cacheGroup = 'jvb_rate_limits';
|
|
protected array $defaults = [
|
'GET' => ['count' => 1000, 'window' => 3600],
|
'POST' => ['count' => 100, 'window' => 3600],
|
'PUT' => ['count' => 100, 'window' => 3600],
|
'PATCH' => ['count' => 100, 'window' => 3600],
|
'DELETE' => ['count' => 50, 'window' => 3600],
|
];
|
|
/**
|
* Check if request is within rate limits
|
*
|
* @param WP_REST_Request $request
|
* @param int|null $limit Optional custom limit (overrides defaults)
|
* @param int|null $window Optional custom window in seconds (overrides defaults)
|
* @return bool True if within limits, false if exceeded
|
*/
|
public function checkLimit(WP_REST_Request $request, ?int $limit = null, ?int $window = null): bool
|
{
|
$method = $request->get_method();
|
$default = $this->defaults[$method] ?? $this->defaults['GET'];
|
|
$limit = $limit ?? $default['count'];
|
$window = $window ?? $default['window'];
|
|
$key = $this->getCacheKey($request, $window);
|
$current = (int) wp_cache_get($key, $this->cacheGroup);
|
|
if ($current >= $limit) {
|
return false;
|
}
|
|
// Increment or initialize
|
if ($current === 0) {
|
wp_cache_set($key, 1, $this->cacheGroup, $window);
|
} else {
|
wp_cache_incr($key, 1, $this->cacheGroup);
|
}
|
|
return true;
|
}
|
|
/**
|
* Get remaining requests for current window
|
*/
|
public function getRemaining(WP_REST_Request $request, ?int $limit = null, ?int $window = null): int
|
{
|
$method = $request->get_method();
|
$default = $this->defaults[$method] ?? $this->defaults['GET'];
|
|
$limit = $limit ?? $default['count'];
|
$window = $window ?? $default['window'];
|
|
$key = $this->getCacheKey($request, $window);
|
$current = (int) wp_cache_get($key, $this->cacheGroup);
|
|
return max(0, $limit - $current);
|
}
|
|
/**
|
* Reset rate limit for a request pattern
|
*/
|
public function reset(WP_REST_Request $request, ?int $window = null): void
|
{
|
$method = $request->get_method();
|
$default = $this->defaults[$method] ?? $this->defaults['GET'];
|
$window = $window ?? $default['window'];
|
|
$key = $this->getCacheKey($request, $window);
|
wp_cache_delete($key, $this->cacheGroup);
|
}
|
|
protected function getCacheKey(WP_REST_Request $request, int $window): string
|
{
|
$ip = $request->get_header('X-Forwarded-For') ?: ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
|
$userId = get_current_user_id();
|
$method = $request->get_method();
|
$route = $request->get_route();
|
|
// Include window in key so different windows don't collide
|
return "rate:{$ip}:{$userId}:{$method}:{$route}:{$window}";
|
}
|
}
|