Files
intaleq_v3_pure_php/core/Auth/RateLimiter.php
2026-04-28 13:04:27 +03:00

83 lines
3.1 KiB
PHP

<?php
// ============================================================
// core/Auth/RateLimiter.php
// Sliding Window Rate Limiting باستخدام Redis
// ============================================================
class RateLimiter
{
private ?Redis $redis;
// حدود مختلفة لكل نوع endpoint
private const LIMITS = [
'login' => ['requests' => 5, 'window' => 60], // 5 محاولات / دقيقة
'otp' => ['requests' => 3, 'window' => 300], // 3 محاولات / 5 دقائق
'register' => ['requests' => 3, 'window' => 3600], // 3 محاولات / ساعة
'api' => ['requests' => 120, 'window' => 60], // 120 طلب / دقيقة
'ride' => ['requests' => 30, 'window' => 60], // 30 طلب / دقيقة
'upload' => ['requests' => 10, 'window' => 300], // 10 رفع / 5 دقائق
];
public function __construct(?Redis $redis)
{
$this->redis = $redis;
}
// ── فحص الحد ─────────────────────────────────────────────
// $identifier: IP:userId أو IP فقط
// $type: login | otp | api | ride | upload
public function check(string $identifier, string $type = 'api'): bool
{
if (!$this->redis) {
return true; // بدون Redis نمرر (fallback)
}
$limit = self::LIMITS[$type] ?? self::LIMITS['api'];
$window = $limit['window'];
$max = $limit['requests'];
$key = "rate:{$type}:{$identifier}";
$current = $this->redis->incr($key);
if ($current === 1) {
$this->redis->expire($key, $window);
}
return $current <= $max;
}
// ── تطبيق الحد وإيقاف الطلب إن تجاوز ─────────────────────
public function enforce(string $identifier, string $type = 'api'): void
{
if (!$this->check($identifier, $type)) {
$limit = self::LIMITS[$type] ?? self::LIMITS['api'];
$window = $limit['window'];
error_log("[RATE_LIMIT] Blocked: $identifier | type: $type");
http_response_code(429);
header("Retry-After: $window");
echo json_encode([
'error' => 'Too many requests. Please slow down.',
'retry_after' => $window,
]);
exit;
}
}
// ── بناء معرّف المستخدم ────────────────────────────────────
public static function identifier(?string $userId = null): string
{
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
return $userId ? "{$ip}:{$userId}" : $ip;
}
// ── إعادة تعيين عداد (مثلاً بعد تسجيل دخول ناجح) ───────────
public function reset(string $identifier, string $type = 'login'): void
{
if ($this->redis) {
$this->redis->del("rate:{$type}:{$identifier}");
}
}
}