diff --git a/php/src/Auth/RateLimiter.php b/php/src/Auth/RateLimiter.php index 57e02de0..fc8fc95c 100644 --- a/php/src/Auth/RateLimiter.php +++ b/php/src/Auth/RateLimiter.php @@ -13,7 +13,7 @@ class RateLimiter { */ public function isLimitReached(string $ip): bool { $attempts = apcu_fetch($this->getKey($ip)); - return $attempts !== false && (int)$attempts >= self::MAX_ATTEMPTS; + return $attempts !== false && is_numeric($attempts) && (int)$attempts >= self::MAX_ATTEMPTS; } /** @@ -23,11 +23,11 @@ class RateLimiter { */ public function recordFailedAttempt(string $ip): void { $key = $this->getKey($ip); - // apcu_add only stores when the key does not yet exist. - // If it already exists (returns false), we increment the existing counter. - if (!apcu_add($key, 1, self::WINDOW_SECONDS)) { - apcu_inc($key); - } + // Initialize the key if it does not yet exist (apcu_add is a no-op when key exists). + // apcu_inc is atomic, so using add-then-increment gives a consistent count even + // under concurrent requests. + apcu_add($key, 0, self::WINDOW_SECONDS); + apcu_inc($key); } /** diff --git a/php/src/Controller/LoginController.php b/php/src/Controller/LoginController.php index e3279579..522306cd 100644 --- a/php/src/Controller/LoginController.php +++ b/php/src/Controller/LoginController.php @@ -53,6 +53,7 @@ readonly class LoginController { $ip = (string)($request->getServerParams()['REMOTE_ADDR'] ?? ''); if ($this->rateLimiter->isLimitReached($ip)) { + $response->getBody()->write("Too many failed login attempts. Please try again later."); return $response ->withHeader('Retry-After', (string)RateLimiter::WINDOW_SECONDS) ->withStatus(429);