pdo = Database::getInstance()->getPdo(); } /** * Check whether the given IP has exceeded the allowed number of attempts * for an action within the time window. */ public function isLimited(string $ip, string $action, int $maxAttempts, int $windowSeconds): bool { // Clean up old entries first so the table doesn't grow forever $this->purge($action, $windowSeconds); $stmt = $this->pdo->prepare( 'SELECT COUNT(*) FROM rate_limits WHERE ip = :ip AND action = :action AND attempted_at > DATE_SUB(NOW(), INTERVAL :window SECOND)' ); $stmt->execute(['ip' => $ip, 'action' => $action, 'window' => $windowSeconds]); return (int) $stmt->fetchColumn() >= $maxAttempts; } /** * Record an attempt for the given IP and action. */ public function record(string $ip, string $action): void { $stmt = $this->pdo->prepare( 'INSERT INTO rate_limits (ip, action) VALUES (:ip, :action)' ); $stmt->execute(['ip' => $ip, 'action' => $action]); } /** * Remove entries older than the window so the table stays small. */ private function purge(string $action, int $windowSeconds): void { $stmt = $this->pdo->prepare( 'DELETE FROM rate_limits WHERE action = :action AND attempted_at <= DATE_SUB(NOW(), INTERVAL :window SECOND)' ); $stmt->execute(['action' => $action, 'window' => $windowSeconds]); } }