92 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			PHP
		
	
		
		
			
		
	
	
			92 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			PHP
		
	
|  | <?php | ||
|  | 
 | ||
|  | class RateLimiter { | ||
|  |     private $db; | ||
|  |     private $maxAttempts = 5;        // Maximum login attempts
 | ||
|  |     private $decayMinutes = 15;      // Time window in minutes
 | ||
|  |     private $tableName = 'login_attempts'; | ||
|  | 
 | ||
|  |     public function __construct($database) { | ||
|  |         $this->db = $database->getConnection(); | ||
|  |         $this->createTableIfNotExists(); | ||
|  |     } | ||
|  | 
 | ||
|  |     private function createTableIfNotExists() { | ||
|  |         $sql = "CREATE TABLE IF NOT EXISTS {$this->tableName} (
 | ||
|  |             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); | ||
|  |     } | ||
|  | 
 | ||
|  |     public function attempt($username, $ipAddress) { | ||
|  |         // Clean old attempts
 | ||
|  |         $this->clearOldAttempts(); | ||
|  | 
 | ||
|  |         // Record this attempt
 | ||
|  |         $sql = "INSERT INTO {$this->tableName} (ip_address, username) VALUES (:ip, :username)"; | ||
|  |         $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) { | ||
|  |         $sql = "SELECT COUNT(*) as attempts 
 | ||
|  |                 FROM {$this->tableName}  | ||
|  |                 WHERE ip_address = :ip  | ||
|  |                 AND username = :username  | ||
|  |                 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() { | ||
|  |         $sql = "DELETE FROM {$this->tableName} 
 | ||
|  |                 WHERE attempted_at < datetime('now', '-' || :minutes || ' minutes')";
 | ||
|  | 
 | ||
|  |         $stmt = $this->db->prepare($sql); | ||
|  |         $stmt->execute([ | ||
|  |             ':minutes'		=> $this->decayMinutes | ||
|  |         ]); | ||
|  |     } | ||
|  | 
 | ||
|  |     public function getRemainingAttempts($username, $ipAddress) { | ||
|  |         $sql = "SELECT COUNT(*) as attempts 
 | ||
|  |                 FROM {$this->tableName}  | ||
|  |                 WHERE ip_address = :ip  | ||
|  |                 AND username = :username  | ||
|  |                 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; | ||
|  |     } | ||
|  | } |