adding ratelimiting with reddis db
Deploy / deploy (push) Failing after 3s

This commit is contained in:
2026-05-06 09:27:02 +02:00
parent d82a08f77b
commit 5deb0e1056
16 changed files with 312 additions and 37 deletions
+144
View File
@@ -99,6 +99,150 @@ function remember_token_hash(string $token): string
return hash('sha256', $token);
}
function auth_token_hash(string $token): string
{
return hash('sha256', $token);
}
function client_ip(): string
{
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
}
function ensure_rate_limit_table(mysqli $conn): void
{
static $created = false;
if ($created) {
return;
}
$conn->query("CREATE TABLE IF NOT EXISTS rate_limits (
id VARCHAR(64) PRIMARY KEY,
attempts INT NOT NULL,
reset_at INT NOT NULL
)");
$created = true;
}
function redis_rate_limit_connection()
{
static $redis = null;
static $checked = false;
if ($checked) {
return $redis;
}
$checked = true;
if (!class_exists('Redis')) {
return null;
}
$host = $GLOBALS['REDIS_HOST'] ?? getenv('REDIS_HOST') ?: null;
if (!$host) {
$host = 'jakach-login-redis';
}
$port = $GLOBALS['REDIS_PORT'] ?? getenv('REDIS_PORT') ?: null;
$port = $port ? (int)$port : 6379;
try {
$candidate = new Redis();
if (!$candidate->connect($host, $port, 0.2)) {
return null;
}
$redis = $candidate;
} catch (Throwable $ex) {
$redis = null;
}
return $redis;
}
function rate_limit_key(string $bucket, string $identifier = ''): string
{
return hash('sha256', $bucket . '|' . client_ip() . '|' . strtolower($identifier));
}
function check_rate_limit(mysqli $conn, string $bucket, int $max_attempts, int $window_seconds, string $identifier = ''): void
{
$redis = redis_rate_limit_connection();
if ($redis !== null) {
$key = 'rl:' . rate_limit_key($bucket, $identifier);
$attempts = (int) $redis->incr($key);
if ($attempts === 1) {
$redis->expire($key, $window_seconds);
}
if ($attempts > $max_attempts) {
json_response([
'success' => false,
'status' => 'failure',
'message' => 'Too many attempts. Please try again later.'
], 429);
}
return;
}
ensure_rate_limit_table($conn);
$now = time();
$key = rate_limit_key($bucket, $identifier);
$attempts = 0;
$reset_at = 0;
$sql = "SELECT attempts, reset_at FROM rate_limits WHERE id = ?";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 's', $key);
mysqli_stmt_execute($stmt);
mysqli_stmt_store_result($stmt);
mysqli_stmt_bind_result($stmt, $attempts, $reset_at);
$found = mysqli_stmt_fetch($stmt);
mysqli_stmt_close($stmt);
if (!$found || $reset_at <= $now) {
$attempts = 1;
$reset_at = $now + $window_seconds;
$sql = "REPLACE INTO rate_limits (id, attempts, reset_at) VALUES (?, ?, ?)";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'sii', $key, $attempts, $reset_at);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
return;
}
if ($attempts >= $max_attempts) {
json_response([
'success' => false,
'status' => 'failure',
'message' => 'Too many attempts. Please try again later.'
], 429);
}
$attempts++;
$sql = "UPDATE rate_limits SET attempts = ? WHERE id = ?";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 'is', $attempts, $key);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
}
function clear_rate_limit(mysqli $conn, string $bucket, string $identifier = ''): void
{
$redis = redis_rate_limit_connection();
if ($redis !== null) {
$redis->del('rl:' . rate_limit_key($bucket, $identifier));
return;
}
ensure_rate_limit_table($conn);
$key = rate_limit_key($bucket, $identifier);
$sql = "DELETE FROM rate_limits WHERE id = ?";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, 's', $key);
mysqli_stmt_execute($stmt);
mysqli_stmt_close($stmt);
}
function set_secure_cookie(string $name, string $value, int $expires): void
{
$is_https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')