getMethod()) !== 'post') { return; } $ipAddress = $request->getIPAddress(); $cache = \Config\Services::cache(); // Use a more specific key for login endpoint $path = $request->getUri()->getPath(); $key = 'throttle_login_' . md5($ipAddress . '_' . $path); $current = $cache->get($key); // Log for debugging log_message('debug', "Throttle check - IP: {$ipAddress}, Path: {$path}, Current: " . ($current ?? 'null') . ", Limit: {$this->getRateLimit()}"); if ($current === null) { // First request - initialize counter $cache->save($key, 1, $this->getWindowSeconds()); log_message('debug', "Throttle initialized for IP: {$ipAddress}"); return; } // Increment counter $newCount = $current + 1; $cache->save($key, $newCount, $this->getWindowSeconds()); log_message('debug', "Throttle incremented - IP: {$ipAddress}, Count: {$newCount}, Limit: {$this->getRateLimit()}"); // Check if limit exceeded (use >= instead of > to be more strict) if ($newCount >= $this->getRateLimit()) { log_message('warning', "Rate limit exceeded for IP: {$ipAddress} on path: {$path} - Count: {$newCount}, Limit: {$this->getRateLimit()}"); // Create response with 429 status $response = service('response'); $response->setStatusCode(429); $response->setBody('Too Many Requests. Please try again later.'); $response->setHeader('Retry-After', (string) $this->getWindowSeconds()); $response->setHeader('X-RateLimit-Limit', (string) $this->getRateLimit()); $response->setHeader('X-RateLimit-Remaining', '0'); $response->setHeader('X-RateLimit-Reset', (string) (time() + $this->getWindowSeconds())); $response->setHeader('Content-Type', 'text/plain; charset=utf-8'); $response->setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); return $response; } // Set rate limit headers for successful requests $remaining = max(0, $this->getRateLimit() - $newCount); $response = service('response'); $response->setHeader('X-RateLimit-Limit', (string) $this->getRateLimit()); $response->setHeader('X-RateLimit-Remaining', (string) $remaining); $response->setHeader('X-RateLimit-Reset', (string) (time() + $this->getWindowSeconds())); } /** * Allows After filters to inspect and modify the response */ public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { // No action needed after request } }