This commit is contained in:
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user