| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  | <?php | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RateLimiter { | 
					
						
							| 
									
										
										
										
											2025-01-04 10:30:44 +00:00
										 |  |  |     public $db; | 
					
						
							| 
									
										
										
										
											2025-01-03 15:02:49 +00:00
										 |  |  |     private $log; | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |     public $maxAttempts = 5;           // Maximum login attempts
 | 
					
						
							|  |  |  |     public $decayMinutes = 15;         // Time window in minutes
 | 
					
						
							| 
									
										
										
										
											2025-01-04 10:30:44 +00:00
										 |  |  |     public $autoBlacklistThreshold = 10; // Attempts before auto-blacklist
 | 
					
						
							|  |  |  |     public $autoBlacklistDuration = 24;  // Hours to blacklist for
 | 
					
						
							| 
									
										
										
										
											2025-02-23 17:22:47 +00:00
										 |  |  |     public $authRatelimitTable = 'login_attempts';  // For username/password attempts
 | 
					
						
							|  |  |  |     public $pagesRatelimitTable = 'pages_rate_limits';  // For page requests
 | 
					
						
							| 
									
										
										
										
											2025-01-04 10:30:44 +00:00
										 |  |  |     public $whitelistTable = 'ip_whitelist'; | 
					
						
							|  |  |  |     public $blacklistTable = 'ip_blacklist'; | 
					
						
							| 
									
										
										
										
											2025-02-17 13:04:50 +00:00
										 |  |  |     private $pageLimits = [ | 
					
						
							|  |  |  |         // Default rate limits per minute
 | 
					
						
							|  |  |  |         'default' => 60, | 
					
						
							|  |  |  |         'admin' => 120, | 
					
						
							|  |  |  |         'message' => 20, | 
					
						
							|  |  |  |         'contact' => 30, | 
					
						
							|  |  |  |         'call' => 30, | 
					
						
							|  |  |  |         'register' => 5, | 
					
						
							|  |  |  |         'config' => 10 | 
					
						
							|  |  |  |     ]; | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public function __construct($database) { | 
					
						
							|  |  |  |         $this->db = $database->getConnection(); | 
					
						
							| 
									
										
										
										
											2025-01-03 15:02:49 +00:00
										 |  |  |         $this->log = new Log($database); | 
					
						
							| 
									
										
										
										
											2024-12-29 15:35:15 +00:00
										 |  |  |         $this->createTablesIfNotExist(); | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 14:00:13 +00:00
										 |  |  |     // Database preparation
 | 
					
						
							| 
									
										
										
										
											2024-12-29 15:35:15 +00:00
										 |  |  |     private function createTablesIfNotExist() { | 
					
						
							| 
									
										
										
										
											2025-02-17 13:04:50 +00:00
										 |  |  |         // Authentication attempts table
 | 
					
						
							| 
									
										
										
										
											2025-02-17 12:52:24 +00:00
										 |  |  |         $sql = "CREATE TABLE IF NOT EXISTS {$this->authRatelimitTable} (
 | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |             id INTEGER PRIMARY KEY AUTOINCREMENT, | 
					
						
							| 
									
										
										
										
											2025-02-23 17:22:47 +00:00
										 |  |  |             ip_address TEXT NOT NULL, | 
					
						
							| 
									
										
										
										
											2024-12-21 15:11:15 +00:00
										 |  |  |             username TEXT NOT NULL, | 
					
						
							| 
									
										
										
										
											2025-01-03 16:44:08 +00:00
										 |  |  |             attempted_at TEXT DEFAULT (DATETIME('now')) | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |         )";
 | 
					
						
							| 
									
										
										
										
											2024-12-12 14:11:41 +00:00
										 |  |  |         $this->db->exec($sql); | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-17 13:04:50 +00:00
										 |  |  |         // Pages rate limits table
 | 
					
						
							|  |  |  |         $sql = "CREATE TABLE IF NOT EXISTS {$this->pagesRatelimitTable} (
 | 
					
						
							|  |  |  |             id INTEGER PRIMARY KEY AUTOINCREMENT, | 
					
						
							|  |  |  |             ip_address TEXT NOT NULL, | 
					
						
							|  |  |  |             endpoint TEXT NOT NULL, | 
					
						
							|  |  |  |             request_time DATETIME DEFAULT CURRENT_TIMESTAMP | 
					
						
							|  |  |  |         )";
 | 
					
						
							|  |  |  |         $this->db->exec($sql); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-12 14:11:41 +00:00
										 |  |  |         // IP whitelist table
 | 
					
						
							|  |  |  |         $sql = "CREATE TABLE IF NOT EXISTS {$this->whitelistTable} (
 | 
					
						
							| 
									
										
										
										
											2024-12-20 15:05:45 +00:00
										 |  |  |             id INTEGER PRIMARY KEY AUTOINCREMENT, | 
					
						
							| 
									
										
										
										
											2025-01-02 15:46:28 +00:00
										 |  |  |             ip_address TEXT NOT NULL UNIQUE, | 
					
						
							| 
									
										
										
										
											2024-12-21 15:11:15 +00:00
										 |  |  |             is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)), | 
					
						
							|  |  |  |             description TEXT, | 
					
						
							|  |  |  |             created_at TEXT DEFAULT (DATETIME('now')), | 
					
						
							| 
									
										
										
										
											2025-01-02 15:46:28 +00:00
										 |  |  |             created_by TEXT | 
					
						
							|  |  |  |         )";
 | 
					
						
							|  |  |  |         $this->db->exec($sql); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // IP blacklist table
 | 
					
						
							|  |  |  |         $sql = "CREATE TABLE IF NOT EXISTS {$this->blacklistTable} (
 | 
					
						
							|  |  |  |             id INTEGER PRIMARY KEY AUTOINCREMENT, | 
					
						
							|  |  |  |             ip_address TEXT NOT NULL UNIQUE, | 
					
						
							|  |  |  |             is_network BOOLEAN DEFAULT 0 CHECK(is_network IN (0,1)), | 
					
						
							|  |  |  |             reason TEXT, | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |             expiry_time TEXT  NULL, | 
					
						
							| 
									
										
										
										
											2025-01-02 15:46:28 +00:00
										 |  |  |             created_at TEXT DEFAULT (DATETIME('now')), | 
					
						
							|  |  |  |             created_by TEXT | 
					
						
							| 
									
										
										
										
											2024-12-12 14:11:41 +00:00
										 |  |  |         )";
 | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |         $this->db->exec($sql); | 
					
						
							| 
									
										
										
										
											2024-12-17 14:41:23 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Default IPs to whitelist (local interface and private networks IPs)
 | 
					
						
							|  |  |  |         $defaultIps = [ | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |             ['127.0.0.1', false, 'localhost IPv4'], | 
					
						
							|  |  |  |             ['::1', false, 'localhost IPv6'], | 
					
						
							|  |  |  |             ['10.0.0.0/8', true, 'Private network (Class A)'], | 
					
						
							|  |  |  |             ['172.16.0.0/12', true, 'Private network (Class B)'], | 
					
						
							|  |  |  |             ['192.168.0.0/16', true, 'Private network (Class C)'] | 
					
						
							| 
									
										
										
										
											2024-12-17 14:41:23 +00:00
										 |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Insert default whitelisted IPs if they don't exist
 | 
					
						
							| 
									
										
										
										
											2024-12-21 15:18:53 +00:00
										 |  |  |         $stmt = $this->db->prepare("INSERT OR IGNORE INTO {$this->whitelistTable}
 | 
					
						
							|  |  |  |             (ip_address, is_network, description, created_by) | 
					
						
							| 
									
										
										
										
											2024-12-17 14:41:23 +00:00
										 |  |  |             VALUES (?, ?, ?, 'system')");
 | 
					
						
							|  |  |  |         foreach ($defaultIps as $ip) { | 
					
						
							|  |  |  |             $stmt->execute([$ip[0], $ip[1], $ip[2]]); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-01-03 15:49:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Insert known malicious networks
 | 
					
						
							|  |  |  |         $defaultBlacklist = [ | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |             ['0.0.0.0/8', true, 'Reserved address space - RFC 1122'], | 
					
						
							|  |  |  |             ['100.64.0.0/10', true, 'Carrier-grade NAT space - RFC 6598'], | 
					
						
							|  |  |  |             ['192.0.2.0/24', true, 'TEST-NET-1 Documentation space - RFC 5737'], | 
					
						
							|  |  |  |             ['198.51.100.0/24', true, 'TEST-NET-2 Documentation space - RFC 5737'], | 
					
						
							|  |  |  |             ['203.0.113.0/24', true, 'TEST-NET-3 Documentation space - RFC 5737'] | 
					
						
							| 
									
										
										
										
											2025-01-03 15:49:36 +00:00
										 |  |  |         ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-04 11:41:02 +00:00
										 |  |  |         $stmt = $this->db->prepare("INSERT OR IGNORE INTO {$this->blacklistTable}
 | 
					
						
							|  |  |  |             (ip_address, is_network, reason, created_by) | 
					
						
							| 
									
										
										
										
											2025-01-03 15:49:36 +00:00
										 |  |  |             VALUES (?, ?, ?, 'system')");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         foreach ($defaultBlacklist as $ip) { | 
					
						
							|  |  |  |             $stmt->execute([$ip[0], $ip[1], $ip[2]]); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Get number of recent login attempts for an IP | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getRecentAttempts($ip) { | 
					
						
							| 
									
										
										
										
											2025-02-17 12:52:24 +00:00
										 |  |  |         $stmt = $this->db->prepare("SELECT COUNT(*) as attempts FROM {$this->authRatelimitTable} 
 | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |             WHERE ip_address = ? AND attempted_at > datetime('now', '-' || :minutes || ' minutes')");
 | 
					
						
							|  |  |  |         $stmt->execute([$ip, $this->decayMinutes]); | 
					
						
							|  |  |  |         $result = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  |         return intval($result['attempts']); | 
					
						
							| 
									
										
										
										
											2024-12-09 13:43:10 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Check if an IP is blacklisted | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function isIpBlacklisted($ip) { | 
					
						
							| 
									
										
										
										
											2025-01-03 15:58:19 +00:00
										 |  |  |         // First check if IP is explicitly blacklisted or in a blacklisted range
 | 
					
						
							| 
									
										
										
										
											2025-02-21 09:44:52 +00:00
										 |  |  |         $stmt = $this->db->prepare("SELECT ip_address, is_network, expiry_time FROM {$this->blacklistTable} WHERE ip_address = ?"); | 
					
						
							|  |  |  |         $stmt->execute([$ip]); | 
					
						
							|  |  |  |         $row = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if ($row) { | 
					
						
							|  |  |  |             // Skip expired entries
 | 
					
						
							|  |  |  |             if ($row['expiry_time'] !== null && strtotime($row['expiry_time']) < time()) { | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Check network ranges
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare("SELECT ip_address, expiry_time FROM {$this->blacklistTable} WHERE is_network = 1"); | 
					
						
							| 
									
										
										
										
											2025-01-03 15:58:19 +00:00
										 |  |  |         $stmt->execute(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { | 
					
						
							|  |  |  |             // Skip expired entries
 | 
					
						
							|  |  |  |             if ($row['expiry_time'] !== null && strtotime($row['expiry_time']) < time()) { | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-21 09:44:52 +00:00
										 |  |  |             if ($this->ipInRange($ip, $row['ip_address'])) { | 
					
						
							|  |  |  |                 return true; | 
					
						
							| 
									
										
										
										
											2025-01-03 15:58:19 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Check if an IP is whitelisted | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function isIpWhitelisted($ip) { | 
					
						
							| 
									
										
										
										
											2025-02-23 17:22:47 +00:00
										 |  |  |         // Check exact IP match first
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare("SELECT ip_address FROM {$this->whitelistTable} WHERE ip_address = ?"); | 
					
						
							|  |  |  |         $stmt->execute([$ip]); | 
					
						
							|  |  |  |         $row = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  |         if ($row) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-23 17:22:47 +00:00
										 |  |  |         // Only check ranges for IPv4 addresses
 | 
					
						
							|  |  |  |         if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { | 
					
						
							|  |  |  |             // Check network ranges
 | 
					
						
							|  |  |  |             $stmt = $this->db->prepare("SELECT ip_address FROM {$this->whitelistTable} WHERE is_network = 1"); | 
					
						
							|  |  |  |             $stmt->execute(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |                 if ($this->ipInRange($ip, $row['ip_address'])) { | 
					
						
							|  |  |  |                     return true; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-09 13:43:10 +00:00
										 |  |  |     private function ipInRange($ip, $cidr) { | 
					
						
							| 
									
										
										
										
											2025-02-23 17:22:47 +00:00
										 |  |  |         // Only work with IPv4 addresses
 | 
					
						
							|  |  |  |         if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-09 13:43:10 +00:00
										 |  |  |         list($subnet, $bits) = explode('/', $cidr); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-23 17:22:47 +00:00
										 |  |  |         // Make sure subnet is IPv4
 | 
					
						
							|  |  |  |         if (!filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-09 13:43:10 +00:00
										 |  |  |         $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-21 15:14:31 +00:00
										 |  |  |     public function addToWhitelist($ip, $isNetwork = false, $description = '', $createdBy = 'system', $userId = null) { | 
					
						
							|  |  |  |         try { | 
					
						
							| 
									
										
										
										
											2025-01-03 16:02:44 +00:00
										 |  |  |             // Check if IP is blacklisted first
 | 
					
						
							|  |  |  |             if ($this->isIpBlacklisted($ip)) { | 
					
						
							|  |  |  |                 $message = "Cannot whitelist {$ip} - IP is currently blacklisted"; | 
					
						
							|  |  |  |                 if ($userId) { | 
					
						
							|  |  |  |                     $this->log->insertLog($userId, "IP Whitelist: {$message}", 'system'); | 
					
						
							| 
									
										
										
										
											2025-02-16 08:18:26 +00:00
										 |  |  |                     Feedback::flash('ERROR', 'DEFAULT', $message); | 
					
						
							| 
									
										
										
										
											2025-01-03 16:02:44 +00:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-04 11:41:02 +00:00
										 |  |  |             $stmt = $this->db->prepare("INSERT OR REPLACE INTO {$this->whitelistTable}
 | 
					
						
							| 
									
										
										
										
											2024-12-21 15:14:31 +00:00
										 |  |  |                 (ip_address, is_network, description, created_by) | 
					
						
							| 
									
										
										
										
											2025-01-04 11:41:02 +00:00
										 |  |  |                 VALUES (?, ?, ?, ?)");
 | 
					
						
							| 
									
										
										
										
											2024-12-21 15:14:31 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 $result = $stmt->execute([$ip, $isNetwork, $description, $createdBy]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if ($result) { | 
					
						
							|  |  |  |                     $logMessage = sprintf( | 
					
						
							|  |  |  |                         'IP Whitelist: Added %s "%s" by %s. Description: %s', | 
					
						
							|  |  |  |                         $isNetwork ? 'network' : 'IP', | 
					
						
							|  |  |  |                         $ip, | 
					
						
							|  |  |  |                         $createdBy, | 
					
						
							|  |  |  |                         $description | 
					
						
							|  |  |  |                     ); | 
					
						
							|  |  |  |                     $this->log->insertLog($userId ?? 0, $logMessage, 'system'); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return $result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         } catch (Exception $e) { | 
					
						
							|  |  |  |             if ($userId) { | 
					
						
							|  |  |  |                 $this->log->insertLog($userId, "IP Whitelist: Failed to add {$ip}: " . $e->getMessage(), 'system'); | 
					
						
							| 
									
										
										
										
											2025-02-16 08:18:26 +00:00
										 |  |  |                 Feedback::flash('ERROR', 'DEFAULT', "IP Whitelist: Failed to add {$ip}: " . $e->getMessage()); | 
					
						
							| 
									
										
										
										
											2024-12-21 15:14:31 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-12-09 13:44:00 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-11 14:00:13 +00:00
										 |  |  |     // Remove from whitelist
 | 
					
						
							| 
									
										
										
										
											2025-01-04 11:41:02 +00:00
										 |  |  |     public function removeFromWhitelist($ip, $removedBy = 'system', $userId = null) { | 
					
						
							| 
									
										
										
										
											2024-12-22 15:21:24 +00:00
										 |  |  |         try { | 
					
						
							|  |  |  |             // Get IP details before removal for logging
 | 
					
						
							|  |  |  |             $stmt = $this->db->prepare("SELECT * FROM {$this->whitelistTable} WHERE ip_address = ?"); | 
					
						
							|  |  |  |             $stmt->execute([$ip]); | 
					
						
							|  |  |  |             $ipDetails = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Remove the IP
 | 
					
						
							|  |  |  |             $stmt = $this->db->prepare("DELETE FROM {$this->whitelistTable} WHERE ip_address = ?"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $result = $stmt->execute([$ip]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if ($result && $ipDetails) { | 
					
						
							|  |  |  |                 $logMessage = sprintf( | 
					
						
							|  |  |  |                     'IP Whitelist: Removed %s "%s" by %s. Was added by: %s', | 
					
						
							|  |  |  |                     $ipDetails['is_network'] ? 'network' : 'IP', | 
					
						
							|  |  |  |                     $ip, | 
					
						
							|  |  |  |                     $removedBy, | 
					
						
							|  |  |  |                     $ipDetails['created_by'] | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |                 $this->log->insertLog($userId ?? 0, $logMessage, 'system'); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-12-09 13:44:00 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-22 15:21:24 +00:00
										 |  |  |             return $result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         } catch (Exception $e) { | 
					
						
							|  |  |  |             if ($userId) { | 
					
						
							|  |  |  |                 $this->log->insertLog($userId, "IP Whitelist: Failed to remove {$ip}: " . $e->getMessage(), 'system'); | 
					
						
							| 
									
										
										
										
											2025-02-16 08:18:26 +00:00
										 |  |  |                 Feedback::flash('ERROR', 'DEFAULT', "IP Whitelist: Failed to remove {$ip}: " . $e->getMessage()); | 
					
						
							| 
									
										
										
										
											2024-12-22 15:21:24 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-12-12 14:16:48 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-03 16:06:02 +00:00
										 |  |  |     public function addToBlacklist($ip, $isNetwork = false, $reason = '', $createdBy = 'system', $userId = null, $expiryHours = null) { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             // Check if IP is whitelisted first
 | 
					
						
							|  |  |  |             if ($this->isIpWhitelisted($ip)) { | 
					
						
							|  |  |  |                 $message = "Cannot blacklist {$ip} - IP is currently whitelisted"; | 
					
						
							|  |  |  |                 if ($userId) { | 
					
						
							|  |  |  |                     $this->log->insertLog($userId, "IP Blacklist: {$message}", 'system'); | 
					
						
							| 
									
										
										
										
											2025-02-16 08:18:26 +00:00
										 |  |  |                     Feedback::flash('ERROR', 'DEFAULT', $message); | 
					
						
							| 
									
										
										
										
											2025-01-03 16:06:02 +00:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 return false; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             $expiryTime = $expiryHours ? date('Y-m-d H:i:s', strtotime("+{$expiryHours} hours")) : null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-04 11:41:02 +00:00
										 |  |  |             $stmt = $this->db->prepare("INSERT OR REPLACE INTO {$this->blacklistTable}
 | 
					
						
							| 
									
										
										
										
											2025-01-03 16:06:02 +00:00
										 |  |  |                 (ip_address, is_network, reason, expiry_time, created_by) | 
					
						
							| 
									
										
										
										
											2025-01-04 11:41:02 +00:00
										 |  |  |                 VALUES (?, ?, ?, ?, ?)");
 | 
					
						
							| 
									
										
										
										
											2025-01-03 16:06:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             $result = $stmt->execute([$ip, $isNetwork, $reason, $expiryTime, $createdBy]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if ($result) { | 
					
						
							|  |  |  |                 $logMessage = sprintf( | 
					
						
							|  |  |  |                     'IP Blacklist: Added %s "%s" by %s. Reason: %s. Expires: %s', | 
					
						
							|  |  |  |                     $isNetwork ? 'network' : 'IP', | 
					
						
							|  |  |  |                     $ip, | 
					
						
							|  |  |  |                     $createdBy, | 
					
						
							|  |  |  |                     $reason, | 
					
						
							|  |  |  |                     $expiryTime ?? 'never' | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |                 $this->log->insertLog($userId ?? 0, $logMessage, 'system'); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return $result; | 
					
						
							|  |  |  |         } catch (Exception $e) { | 
					
						
							|  |  |  |             if ($userId) { | 
					
						
							|  |  |  |                 $this->log->insertLog($userId, "IP Blacklist: Failed to add {$ip}: " . $e->getMessage(), 'system'); | 
					
						
							| 
									
										
										
										
											2025-02-16 08:18:26 +00:00
										 |  |  |                 Feedback::flash('ERROR', 'DEFAULT', "IP Blacklist: Failed to add {$ip}: " . $e->getMessage()); | 
					
						
							| 
									
										
										
										
											2025-01-03 16:06:02 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-04 11:41:02 +00:00
										 |  |  |     public function removeFromBlacklist($ip, $removedBy = 'system', $userId = null) { | 
					
						
							| 
									
										
										
										
											2025-01-03 16:06:02 +00:00
										 |  |  |         try { | 
					
						
							|  |  |  |             // Get IP details before removal for logging
 | 
					
						
							|  |  |  |             $stmt = $this->db->prepare("SELECT * FROM {$this->blacklistTable} WHERE ip_address = ?"); | 
					
						
							|  |  |  |             $stmt->execute([$ip]); | 
					
						
							|  |  |  |             $ipDetails = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Remove the IP
 | 
					
						
							|  |  |  |             $stmt = $this->db->prepare("DELETE FROM {$this->blacklistTable} WHERE ip_address = ?"); | 
					
						
							| 
									
										
										
										
											2025-02-21 09:44:52 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-03 16:06:02 +00:00
										 |  |  |             $result = $stmt->execute([$ip]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if ($result && $ipDetails) { | 
					
						
							|  |  |  |                 $logMessage = sprintf( | 
					
						
							|  |  |  |                     'IP Blacklist: Removed %s "%s" by %s. Was added by: %s. Reason was: %s', | 
					
						
							|  |  |  |                     $ipDetails['is_network'] ? 'network' : 'IP', | 
					
						
							|  |  |  |                     $ip, | 
					
						
							|  |  |  |                     $removedBy, | 
					
						
							|  |  |  |                     $ipDetails['created_by'], | 
					
						
							|  |  |  |                     $ipDetails['reason'] | 
					
						
							|  |  |  |                 ); | 
					
						
							|  |  |  |                 $this->log->insertLog($userId ?? 0, $logMessage, 'system'); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return $result; | 
					
						
							|  |  |  |         } catch (Exception $e) { | 
					
						
							|  |  |  |             if ($userId) { | 
					
						
							|  |  |  |                 $this->log->insertLog($userId, "IP Blacklist: Failed to remove {$ip}: " . $e->getMessage(), 'system'); | 
					
						
							| 
									
										
										
										
											2025-02-16 08:18:26 +00:00
										 |  |  |                 Feedback::flash('ERROR', 'DEFAULT', "IP Blacklist: Failed to remove {$ip}: " . $e->getMessage()); | 
					
						
							| 
									
										
										
										
											2025-01-03 16:06:02 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-12 14:16:48 +00:00
										 |  |  |     public function getWhitelistedIps() { | 
					
						
							|  |  |  |         $stmt = $this->db->prepare("SELECT * FROM {$this->whitelistTable} ORDER BY created_at DESC"); | 
					
						
							|  |  |  |         $stmt->execute(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $stmt->fetchAll(PDO::FETCH_ASSOC); | 
					
						
							| 
									
										
										
										
											2024-12-09 13:44:00 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-03 16:07:34 +00:00
										 |  |  |     public function getBlacklistedIps() { | 
					
						
							|  |  |  |         $stmt = $this->db->prepare("SELECT * FROM {$this->blacklistTable} ORDER BY created_at DESC"); | 
					
						
							|  |  |  |         $stmt->execute(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $stmt->fetchAll(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-03 16:08:59 +00:00
										 |  |  |     public function cleanupExpiredEntries() { | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             // Remove expired blacklist entries
 | 
					
						
							|  |  |  |             $stmt = $this->db->prepare("DELETE FROM {$this->blacklistTable}
 | 
					
						
							| 
									
										
										
										
											2025-01-04 09:46:07 +00:00
										 |  |  |                 WHERE expiry_time IS NOT NULL AND expiry_time < datetime('now')");
 | 
					
						
							| 
									
										
										
										
											2025-01-03 16:08:59 +00:00
										 |  |  |             $stmt->execute(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Clean old login attempts
 | 
					
						
							| 
									
										
										
										
											2025-02-17 12:52:24 +00:00
										 |  |  |             $stmt = $this->db->prepare("DELETE FROM {$this->authRatelimitTable}
 | 
					
						
							| 
									
										
										
										
											2025-01-04 09:46:07 +00:00
										 |  |  |                 WHERE attempted_at < datetime('now', '-' || :minutes || ' minutes')");
 | 
					
						
							| 
									
										
										
										
											2025-01-04 09:32:19 +00:00
										 |  |  |             $stmt->execute([':minutes' => $this->decayMinutes]); | 
					
						
							| 
									
										
										
										
											2025-01-03 16:08:59 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } catch (Exception $e) { | 
					
						
							|  |  |  |             $this->log->insertLog(0, "Failed to cleanup expired entries: " . $e->getMessage(), 'system'); | 
					
						
							| 
									
										
										
										
											2025-02-16 08:18:26 +00:00
										 |  |  |             Feedback::flash('ERROR', 'DEFAULT', "Failed to cleanup expired entries: " . $e->getMessage()); | 
					
						
							| 
									
										
										
										
											2025-01-03 16:08:59 +00:00
										 |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-04 09:32:19 +00:00
										 |  |  |     public function isAllowed($username, $ipAddress) { | 
					
						
							|  |  |  |         // First check if IP is blacklisted
 | 
					
						
							|  |  |  |         if ($this->isIpBlacklisted($ipAddress)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Then check if IP is whitelisted
 | 
					
						
							| 
									
										
										
										
											2024-12-10 13:56:18 +00:00
										 |  |  |         if ($this->isIpWhitelisted($ipAddress)) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |         // Clean old attempts
 | 
					
						
							|  |  |  |         $this->clearOldAttempts(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-04 09:32:19 +00:00
										 |  |  |         // Check if we've hit the rate limit
 | 
					
						
							|  |  |  |         if ($this->tooManyAttempts($username, $ipAddress)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Check total attempts across all usernames from this IP
 | 
					
						
							|  |  |  |         $sql = "SELECT COUNT(*) as total_attempts
 | 
					
						
							| 
									
										
										
										
											2025-02-17 12:52:24 +00:00
										 |  |  |                 FROM {$this->authRatelimitTable} | 
					
						
							| 
									
										
										
										
											2025-01-04 09:32:19 +00:00
										 |  |  |                 WHERE ip_address = :ip | 
					
						
							| 
									
										
										
										
											2025-01-04 09:46:07 +00:00
										 |  |  |                 AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
 | 
					
						
							| 
									
										
										
										
											2025-01-04 09:32:19 +00:00
										 |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         $stmt->execute([ | 
					
						
							|  |  |  |             ':ip'      => $ipAddress, | 
					
						
							|  |  |  |             ':minutes' => $this->decayMinutes | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |         $result = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Check if we would hit auto-blacklist threshold
 | 
					
						
							|  |  |  |         return $result['total_attempts'] < $this->autoBlacklistThreshold; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function attempt($username, $ipAddress) { | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |         // Record this attempt
 | 
					
						
							| 
									
										
										
										
											2025-02-17 12:52:24 +00:00
										 |  |  |         $sql = "INSERT INTO {$this->authRatelimitTable} (ip_address, username) VALUES (:ip, :username)"; | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							| 
									
										
										
										
											2025-02-23 17:22:47 +00:00
										 |  |  |         try { | 
					
						
							|  |  |  |             $stmt->execute([ | 
					
						
							|  |  |  |                 ':ip'           => $ipAddress, | 
					
						
							|  |  |  |                 ':username'     => $username | 
					
						
							|  |  |  |             ]); | 
					
						
							|  |  |  |         } catch (PDOException $e) { | 
					
						
							| 
									
										
										
										
											2025-01-04 09:32:19 +00:00
										 |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return true; | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function tooManyAttempts($username, $ipAddress) { | 
					
						
							| 
									
										
										
										
											2024-12-10 13:56:58 +00:00
										 |  |  |         $sql = "SELECT COUNT(*) as attempts
 | 
					
						
							| 
									
										
										
										
											2025-02-17 12:52:24 +00:00
										 |  |  |                 FROM {$this->authRatelimitTable} | 
					
						
							| 
									
										
										
										
											2024-12-10 13:56:58 +00:00
										 |  |  |                 WHERE ip_address = :ip | 
					
						
							|  |  |  |                 AND username = :username | 
					
						
							| 
									
										
										
										
											2025-01-04 09:46:07 +00:00
										 |  |  |                 AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
 | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         $stmt->execute([ | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |             ':ip'           => $ipAddress, | 
					
						
							|  |  |  |             ':username'     => $username, | 
					
						
							|  |  |  |             ':minutes'      => $this->decayMinutes | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $result = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							| 
									
										
										
										
											2025-02-23 17:22:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Also check what's in the table
 | 
					
						
							|  |  |  |         $sql = "SELECT * FROM {$this->authRatelimitTable} WHERE ip_address = :ip"; | 
					
						
							|  |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         $stmt->execute([':ip' => $ipAddress]); | 
					
						
							|  |  |  |         $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-23 17:35:38 +00:00
										 |  |  |         $tooMany = $result['attempts'] >= $this->maxAttempts; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Auto-blacklist if too many attempts
 | 
					
						
							|  |  |  |         if ($tooMany) { | 
					
						
							|  |  |  |             $this->addToBlacklist( | 
					
						
							|  |  |  |                 $ipAddress, | 
					
						
							|  |  |  |                 false, | 
					
						
							|  |  |  |                 'Auto-blacklisted due to excessive login attempts', | 
					
						
							|  |  |  |                 'system', | 
					
						
							|  |  |  |                 null, | 
					
						
							|  |  |  |                 $this->autoBlacklistDuration | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return $tooMany; | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function clearOldAttempts() { | 
					
						
							| 
									
										
										
										
											2025-02-17 12:52:24 +00:00
										 |  |  |         $sql = "DELETE FROM {$this->authRatelimitTable}
 | 
					
						
							| 
									
										
										
										
											2025-01-04 09:46:07 +00:00
										 |  |  |                 WHERE attempted_at < datetime('now', '-' || :minutes || ' minutes')";
 | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         $stmt->execute([ | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |             ':minutes'      => $this->decayMinutes | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |         ]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function getRemainingAttempts($username, $ipAddress) { | 
					
						
							| 
									
										
										
										
											2024-12-10 13:56:58 +00:00
										 |  |  |         $sql = "SELECT COUNT(*) as attempts
 | 
					
						
							| 
									
										
										
										
											2025-02-17 12:52:24 +00:00
										 |  |  |                 FROM {$this->authRatelimitTable} | 
					
						
							| 
									
										
										
										
											2024-12-10 13:56:58 +00:00
										 |  |  |                 WHERE ip_address = :ip | 
					
						
							|  |  |  |                 AND username = :username | 
					
						
							| 
									
										
										
										
											2025-01-04 09:46:07 +00:00
										 |  |  |                 AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
 | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         $stmt->execute([ | 
					
						
							| 
									
										
										
										
											2025-01-06 09:13:28 +00:00
										 |  |  |             ':ip'           => $ipAddress, | 
					
						
							|  |  |  |             ':username'     => $username, | 
					
						
							|  |  |  |             ':minutes'      => $this->decayMinutes | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $result = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  |         return max(0, $this->maxAttempts - $result['attempts']); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public function getDecayMinutes() { | 
					
						
							|  |  |  |         return $this->decayMinutes; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-02-17 13:04:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Check if a page request is allowed | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function isPageRequestAllowed($ipAddress, $endpoint, $userId = null) { | 
					
						
							|  |  |  |         // First check if IP is blacklisted
 | 
					
						
							|  |  |  |         if ($this->isIpBlacklisted($ipAddress)) { | 
					
						
							|  |  |  |             return false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Then check if IP is whitelisted
 | 
					
						
							|  |  |  |         if ($this->isIpWhitelisted($ipAddress)) { | 
					
						
							|  |  |  |             return true; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Clean old requests
 | 
					
						
							|  |  |  |         $this->cleanOldPageRequests(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Get limit based on endpoint type and user role
 | 
					
						
							|  |  |  |         $limit = $this->getPageLimitForEndpoint($endpoint, $userId); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-02-21 09:44:52 +00:00
										 |  |  |         // Count recent requests, including this one
 | 
					
						
							| 
									
										
										
										
											2025-02-17 13:04:50 +00:00
										 |  |  |         $sql = "SELECT COUNT(*) as request_count
 | 
					
						
							|  |  |  |                 FROM {$this->pagesRatelimitTable} | 
					
						
							|  |  |  |                 WHERE ip_address = :ip | 
					
						
							|  |  |  |                 AND endpoint = :endpoint | 
					
						
							| 
									
										
										
										
											2025-02-21 09:44:52 +00:00
										 |  |  |                 AND request_time >= DATETIME('now', '-1 minute')";
 | 
					
						
							| 
									
										
										
										
											2025-02-17 13:04:50 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         $stmt->execute([ | 
					
						
							|  |  |  |             ':ip' => $ipAddress, | 
					
						
							|  |  |  |             ':endpoint' => $endpoint | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $result = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  |         return $result['request_count'] < $limit; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Record a page request | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function recordPageRequest($ipAddress, $endpoint) { | 
					
						
							|  |  |  |         $sql = "INSERT INTO {$this->pagesRatelimitTable} (ip_address, endpoint)
 | 
					
						
							|  |  |  |                 VALUES (:ip, :endpoint)";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         return $stmt->execute([ | 
					
						
							|  |  |  |             ':ip' => $ipAddress, | 
					
						
							|  |  |  |             ':endpoint' => $endpoint | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Clean old page requests | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private function cleanOldPageRequests() { | 
					
						
							|  |  |  |         $sql = "DELETE FROM {$this->pagesRatelimitTable} 
 | 
					
						
							|  |  |  |                 WHERE request_time < DATETIME('now', '-1 minute')";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         $stmt->execute(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get page rate limit for endpoint | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private function getPageLimitForEndpoint($endpoint, $userId = null) { | 
					
						
							|  |  |  |         // Admin users get higher limits
 | 
					
						
							|  |  |  |         if ($userId) { | 
					
						
							|  |  |  |             $userObj = new User($this->db); | 
					
						
							|  |  |  |             if ($userObj->hasRight($userId, 'admin')) { | 
					
						
							|  |  |  |                 return $this->pageLimits['admin']; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Get endpoint type from the endpoint path
 | 
					
						
							|  |  |  |         $endpointType = $this->getEndpointType($endpoint); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // Return specific limit if exists, otherwise default
 | 
					
						
							|  |  |  |         return isset($this->pageLimits[$endpointType]) | 
					
						
							|  |  |  |             ? $this->pageLimits[$endpointType] | 
					
						
							|  |  |  |             : $this->pageLimits['default']; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get endpoint type from path | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private function getEndpointType($endpoint) { | 
					
						
							|  |  |  |         if (strpos($endpoint, 'message') !== false) return 'message'; | 
					
						
							|  |  |  |         if (strpos($endpoint, 'contact') !== false) return 'contact'; | 
					
						
							|  |  |  |         if (strpos($endpoint, 'call') !== false) return 'call'; | 
					
						
							|  |  |  |         if (strpos($endpoint, 'register') !== false) return 'register'; | 
					
						
							|  |  |  |         if (strpos($endpoint, 'config') !== false) return 'config'; | 
					
						
							|  |  |  |         return 'default'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Get remaining page requests | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public function getRemainingPageRequests($ipAddress, $endpoint, $userId = null) { | 
					
						
							|  |  |  |         $limit = $this->getPageLimitForEndpoint($endpoint, $userId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $sql = "SELECT COUNT(*) as request_count
 | 
					
						
							|  |  |  |                 FROM {$this->pagesRatelimitTable} | 
					
						
							|  |  |  |                 WHERE ip_address = :ip | 
					
						
							|  |  |  |                 AND endpoint = :endpoint | 
					
						
							|  |  |  |                 AND request_time > DATETIME('now', '-1 minute')";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $stmt = $this->db->prepare($sql); | 
					
						
							|  |  |  |         $stmt->execute([ | 
					
						
							|  |  |  |             ':ip' => $ipAddress, | 
					
						
							|  |  |  |             ':endpoint' => $endpoint | 
					
						
							|  |  |  |         ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         $result = $stmt->fetch(PDO::FETCH_ASSOC); | 
					
						
							|  |  |  |         return max(0, $limit - $result['request_count']); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2024-12-06 13:25:15 +00:00
										 |  |  | } |