Complete Phase 1: MVC, DB migrations, Auth, RBAC, Security, and Views

This commit is contained in:
Hamza-Ayed
2026-06-05 00:56:41 +03:00
parent 7ffbc8bafa
commit bed7624ae9
51 changed files with 3295 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Middleware;
use App\Core\Request;
use App\Core\Response;
use Predis\Client as RedisClient;
use Exception;
use Throwable;
class RateLimit implements MiddlewareInterface
{
private ?RedisClient $redis = null;
private int $limit = 100; // Allow 100 requests
private int $window = 60; // Per 60 seconds
public function __construct()
{
$config = require __DIR__ . '/../../config/redis.php';
if (!empty($config['host'])) {
try {
$this->redis = new RedisClient([
'scheme' => 'tcp',
'host' => $config['host'],
'port' => $config['port'],
'password' => $config['password'],
'timeout' => 0.5, // 500ms connection timeout to fail fast
]);
$this->redis->connect();
} catch (Throwable $e) {
// Degrade gracefully if Redis server is down
$this->redis = null;
}
}
}
/**
* Handle rate limiting logic.
*/
public function handle(Request $request, Response $response, callable $next): void
{
if ($this->redis === null) {
// Redis unavailable, skip throttle check to avoid service outage
$next();
return;
}
$ip = $request->getIp();
$path = $request->getPath();
$key = "rate_limit:" . md5($ip . ":" . $path);
try {
$current = $this->redis->get($key);
if ($current !== null && (int)$current >= $this->limit) {
$ttl = $this->redis->ttl($key);
$response->header('Retry-After', (string)max(1, $ttl));
throw new Exception("Too Many Requests. Rate limit exceeded.", 429);
}
if ($current === null) {
// First request in the time frame window
$this->redis->setex($key, $this->window, 1);
$current = 0;
} else {
$this->redis->incr($key);
}
// Set rate limit headers
$remaining = $this->limit - ((int)$current + 1);
$response->header('X-RateLimit-Limit', (string)$this->limit);
$response->header('X-RateLimit-Remaining', (string)max(0, $remaining));
} catch (Throwable $e) {
if ($e->getCode() === 429) {
throw $e;
}
// Logging or catching connection dropping mid-request
}
$next();
}
}