jilo-web/app/classes/ratelimitrer.php

183 lines
5.7 KiB
PHP
Raw Normal View History

2024-12-06 13:25:15 +00:00
<?php
class RateLimiter {
private $db;
private $maxAttempts = 5; // Maximum login attempts
private $decayMinutes = 15; // Time window in minutes
2024-12-11 14:08:55 +00:00
private $ratelimitTable = 'login_attempts';
2024-12-12 14:11:41 +00:00
private $whitelistTable = 'ip_whitelist';
2024-12-09 13:43:10 +00:00
private $whitelistedIps = []; // Whitelisted IPs
private $whitelistedNetworks = []; // Whitelisted CIDR ranges
2024-12-06 13:25:15 +00:00
public function __construct($database) {
$this->db = $database->getConnection();
2024-12-12 14:11:41 +00:00
$this->createTablesIfNotExists();
2024-12-09 13:43:10 +00:00
$this->loadWhitelist();
2024-12-06 13:25:15 +00:00
}
2024-12-11 14:00:13 +00:00
// Database preparation
2024-12-12 14:11:41 +00:00
private function createTablesIfNotExists() {
// Login attempts table
2024-12-11 14:08:55 +00:00
$sql = "CREATE TABLE IF NOT EXISTS {$this->ratelimitTable} (
2024-12-06 13:25:15 +00:00
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip_address VARCHAR(45) NOT NULL,
username VARCHAR(255) NOT NULL,
attempted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ip_username (ip_address, username)
)";
2024-12-12 14:11:41 +00:00
$this->db->exec($sql);
2024-12-06 13:25:15 +00:00
2024-12-12 14:11:41 +00:00
// IP whitelist table
$sql = "CREATE TABLE IF NOT EXISTS {$this->whitelistTable} (
id int(11) PRIMARY KEY AUTO_INCREMENT,
ip_address VARCHAR(45) NOT NULL,
is_network BOOLEAN DEFAULT FALSE,
description VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(255),
UNIQUE KEY unique_ip (ip_address)
)";
2024-12-06 13:25:15 +00:00
$this->db->exec($sql);
}
2024-12-11 14:00:13 +00:00
// List of IPs to bypass rate limiting
2024-12-09 13:43:10 +00:00
private function loadWhitelist() {
2024-12-11 14:00:13 +00:00
// FIXME Load from database or config
2024-12-09 13:43:10 +00:00
$this->whitelistedIps = [
'127.0.0.1', // localhost
'::1' // localhost IPv6
];
$this->whitelistedNetworks = [
'10.0.0.0/8', // Private network
'172.16.0.0/12', // Private network
'192.168.0.0/16' // Private network
];
}
2024-12-11 14:00:13 +00:00
// Check if IP is whitelisted
2024-12-09 13:43:10 +00:00
private function isIpWhitelisted($ip) {
// Check exact IP match
if (in_array($ip, $this->whitelistedIps)) {
return true;
}
// Check CIDR ranges
foreach ($this->whitelistedNetworks as $network) {
if ($this->ipInRange($ip, $network)) {
return true;
}
}
return false;
}
private function ipInRange($ip, $cidr) {
list($subnet, $bits) = explode('/', $cidr);
$ip = ip2long($ip);
$subnet = ip2long($subnet);
$mask = -1 << (32 - $bits);
$subnet &= $mask;
return ($ip & $mask) == $subnet;
}
2024-12-11 14:00:13 +00:00
// Add to whitelist
2024-12-09 13:44:00 +00:00
public function addToWhitelist($ip, $isNetwork = false) {
if ($isNetwork) {
if (!in_array($ip, $this->whitelistedNetworks)) {
$this->whitelistedNetworks[] = $ip;
}
} else {
if (!in_array($ip, $this->whitelistedIps)) {
$this->whitelistedIps[] = $ip;
}
}
}
2024-12-11 14:00:13 +00:00
// Remove from whitelist
2024-12-09 13:44:00 +00:00
public function removeFromWhitelist($ip) {
$indexIp = array_search($ip, $this->whitelistedIps);
if ($indexIp !== false) {
unset($this->whitelistedIps[$indexIp]);
}
$indexNetwork = array_search($ip, $this->whitelistedNetworks);
if ($indexNetwork !== false) {
unset($this->whitelistedNetworks[$indexNetwork]);
}
}
2024-12-06 13:25:15 +00:00
public function attempt($username, $ipAddress) {
2024-12-10 13:56:18 +00:00
// Skip rate limiting for whitelisted IPs
if ($this->isIpWhitelisted($ipAddress)) {
return true;
}
2024-12-06 13:25:15 +00:00
// Clean old attempts
$this->clearOldAttempts();
// Record this attempt
2024-12-11 14:08:55 +00:00
$sql = "INSERT INTO {$this->ratelimitTable} (ip_address, username) VALUES (:ip, :username)";
2024-12-06 13:25:15 +00:00
$stmt = $this->db->prepare($sql);
$stmt->execute([
':ip' => $ipAddress,
':username' => $username
]);
// Check if too many attempts
return !$this->tooManyAttempts($username, $ipAddress);
}
public function tooManyAttempts($username, $ipAddress) {
2024-12-10 13:56:58 +00:00
$sql = "SELECT COUNT(*) as attempts
2024-12-11 14:08:55 +00:00
FROM {$this->ratelimitTable}
2024-12-10 13:56:58 +00:00
WHERE ip_address = :ip
AND username = :username
2024-12-06 13:25:15 +00:00
AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':ip' => $ipAddress,
':username' => $username,
':minutes' => $this->decayMinutes
]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return $result['attempts'] >= $this->maxAttempts;
}
public function clearOldAttempts() {
2024-12-11 14:08:55 +00:00
$sql = "DELETE FROM {$this->ratelimitTable}
2024-12-06 13:25:15 +00:00
WHERE attempted_at < datetime('now', '-' || :minutes || ' minutes')";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':minutes' => $this->decayMinutes
]);
}
public function getRemainingAttempts($username, $ipAddress) {
2024-12-10 13:56:58 +00:00
$sql = "SELECT COUNT(*) as attempts
2024-12-11 14:08:55 +00:00
FROM {$this->ratelimitTable}
2024-12-10 13:56:58 +00:00
WHERE ip_address = :ip
AND username = :username
2024-12-06 13:25:15 +00:00
AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
$stmt = $this->db->prepare($sql);
$stmt->execute([
':ip' => $ipAddress,
':username' => $username,
':minutes' => $this->decayMinutes
]);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
return max(0, $this->maxAttempts - $result['attempts']);
}
public function getDecayMinutes() {
return $this->decayMinutes;
}
}