Compare commits
	
		
			4 Commits 
		
	
	
		
			645e98cd6a
			...
			bc1089be21
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
									
								
								 | 
						bc1089be21 | |
| 
							
							
								
									
								
								 | 
						a0747cfbc8 | |
| 
							
							
								
									
								
								 | 
						0f72f3bea4 | |
| 
							
							
								
									
								
								 | 
						38e4b002c8 | 
| 
						 | 
				
			
			@ -4,30 +4,45 @@ class RateLimiter {
 | 
			
		|||
    private $db;
 | 
			
		||||
    private $maxAttempts = 5;        // Maximum login attempts
 | 
			
		||||
    private $decayMinutes = 15;      // Time window in minutes
 | 
			
		||||
    private $tableName = 'login_attempts';
 | 
			
		||||
    private $ratelimitTable = 'login_attempts';
 | 
			
		||||
    private $whitelistTable = 'ip_whitelist';
 | 
			
		||||
    private $whitelistedIps = [];       // Whitelisted IPs
 | 
			
		||||
    private $whitelistedNetworks = [];  // Whitelisted CIDR ranges
 | 
			
		||||
 | 
			
		||||
    public function __construct($database) {
 | 
			
		||||
        $this->db = $database->getConnection();
 | 
			
		||||
        $this->createTableIfNotExists();
 | 
			
		||||
        $this->createTablesIfNotExists();
 | 
			
		||||
        $this->loadWhitelist();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createTableIfNotExists() {
 | 
			
		||||
        $sql = "CREATE TABLE IF NOT EXISTS {$this->tableName} (
 | 
			
		||||
    // Database preparation
 | 
			
		||||
    private function createTablesIfNotExists() {
 | 
			
		||||
        // Login attempts table
 | 
			
		||||
        $sql = "CREATE TABLE IF NOT EXISTS {$this->ratelimitTable} (
 | 
			
		||||
            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)
 | 
			
		||||
        )";
 | 
			
		||||
        $this->db->exec($sql);
 | 
			
		||||
 | 
			
		||||
        // 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)
 | 
			
		||||
        )";
 | 
			
		||||
        $this->db->exec($sql);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // List of IPs to bypass rate limiting
 | 
			
		||||
    private function loadWhitelist() {
 | 
			
		||||
        // Load from database or config
 | 
			
		||||
        // FIXME Load from database or config
 | 
			
		||||
        $this->whitelistedIps = [
 | 
			
		||||
            '127.0.0.1',        // localhost
 | 
			
		||||
            '::1'               // localhost IPv6
 | 
			
		||||
| 
						 | 
				
			
			@ -40,20 +55,25 @@ class RateLimiter {
 | 
			
		|||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check if IP is whitelisted
 | 
			
		||||
    private function isIpWhitelisted($ip) {
 | 
			
		||||
        // Check exact IP match
 | 
			
		||||
        if (in_array($ip, $this->whitelistedIps)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        // Check exact IP match and CIDR ranges
 | 
			
		||||
         $stmt = $this->db->prepare("SELECT ip_address, is_network FROM {$this->whitelistTable}");
 | 
			
		||||
         $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        // Check CIDR ranges
 | 
			
		||||
        foreach ($this->whitelistedNetworks as $network) {
 | 
			
		||||
            if ($this->ipInRange($ip, $network)) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
         while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
 | 
			
		||||
             if ($row['is_network']) {
 | 
			
		||||
                 if ($this->ipInRange($ip, $row['ip_address'])) {
 | 
			
		||||
                     return true;
 | 
			
		||||
                 }
 | 
			
		||||
             } else {
 | 
			
		||||
                 if ($ip === $row['ip_address']) {
 | 
			
		||||
                     return true;
 | 
			
		||||
                 }
 | 
			
		||||
             }
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
         return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function ipInRange($ip, $cidr) {
 | 
			
		||||
| 
						 | 
				
			
			@ -67,28 +87,31 @@ class RateLimiter {
 | 
			
		|||
        return ($ip & $mask) == $subnet;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    // Add to whitelist
 | 
			
		||||
    public function addToWhitelist($ip, $isNetwork = false, $description = '', $createdBy = 'system') {
 | 
			
		||||
        $stmt = $this->db->prepare("INSERT INTO {$this->whitelistTable}
 | 
			
		||||
            (ip_address, is_network, description, created_by)
 | 
			
		||||
            VALUES (?, ?, ?, ?)
 | 
			
		||||
            ON DUPLICATE KEY UPDATE
 | 
			
		||||
            is_network = VALUES(is_network),
 | 
			
		||||
            description = VALUES(description),
 | 
			
		||||
            created_by = VALUES(created_by)");
 | 
			
		||||
 | 
			
		||||
        return $stmt->execute([$ip, $isNetwork, $description, $createdBy]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Remove from whitelist
 | 
			
		||||
    public function removeFromWhitelist($ip) {
 | 
			
		||||
        $indexIp = array_search($ip, $this->whitelistedIps);
 | 
			
		||||
        if ($indexIp !== false) {
 | 
			
		||||
            unset($this->whitelistedIps[$indexIp]);
 | 
			
		||||
        }
 | 
			
		||||
        $stmt = $this->db->prepare("DELETE FROM {$this->whitelistTable} WHERE ip_address = ?");
 | 
			
		||||
 | 
			
		||||
        $indexNetwork = array_search($ip, $this->whitelistedNetworks);
 | 
			
		||||
        if ($indexNetwork !== false) {
 | 
			
		||||
            unset($this->whitelistedNetworks[$indexNetwork]);
 | 
			
		||||
        }
 | 
			
		||||
        return $stmt->execute([$ip]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getWhitelistedIps() {
 | 
			
		||||
        $stmt = $this->db->prepare("SELECT * FROM {$this->whitelistTable} ORDER BY created_at DESC");
 | 
			
		||||
        $stmt->execute();
 | 
			
		||||
 | 
			
		||||
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function attempt($username, $ipAddress) {
 | 
			
		||||
| 
						 | 
				
			
			@ -101,7 +124,7 @@ class RateLimiter {
 | 
			
		|||
        $this->clearOldAttempts();
 | 
			
		||||
 | 
			
		||||
        // Record this attempt
 | 
			
		||||
        $sql = "INSERT INTO {$this->tableName} (ip_address, username) VALUES (:ip, :username)";
 | 
			
		||||
        $sql = "INSERT INTO {$this->ratelimitTable} (ip_address, username) VALUES (:ip, :username)";
 | 
			
		||||
        $stmt = $this->db->prepare($sql);
 | 
			
		||||
        $stmt->execute([
 | 
			
		||||
            ':ip'		=> $ipAddress,
 | 
			
		||||
| 
						 | 
				
			
			@ -114,7 +137,7 @@ class RateLimiter {
 | 
			
		|||
 | 
			
		||||
    public function tooManyAttempts($username, $ipAddress) {
 | 
			
		||||
        $sql = "SELECT COUNT(*) as attempts
 | 
			
		||||
                FROM {$this->tableName}
 | 
			
		||||
                FROM {$this->ratelimitTable}
 | 
			
		||||
                WHERE ip_address = :ip
 | 
			
		||||
                AND username = :username
 | 
			
		||||
                AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +154,7 @@ class RateLimiter {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    public function clearOldAttempts() {
 | 
			
		||||
        $sql = "DELETE FROM {$this->tableName}
 | 
			
		||||
        $sql = "DELETE FROM {$this->ratelimitTable}
 | 
			
		||||
                WHERE attempted_at < datetime('now', '-' || :minutes || ' minutes')";
 | 
			
		||||
 | 
			
		||||
        $stmt = $this->db->prepare($sql);
 | 
			
		||||
| 
						 | 
				
			
			@ -142,7 +165,7 @@ class RateLimiter {
 | 
			
		|||
 | 
			
		||||
    public function getRemainingAttempts($username, $ipAddress) {
 | 
			
		||||
        $sql = "SELECT COUNT(*) as attempts
 | 
			
		||||
                FROM {$this->tableName}
 | 
			
		||||
                FROM {$this->ratelimitTable}
 | 
			
		||||
                WHERE ip_address = :ip
 | 
			
		||||
                AND username = :username
 | 
			
		||||
                AND attempted_at > datetime('now', '-' || :minutes || ' minutes')";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue