Skip to content

fix(backend): rate limit the password-forgot endpoint - #568

Merged
algirdasc merged 1 commit into
mainfrom
fix/password-forgot-throttle
Aug 10, 2026
Merged

fix(backend): rate limit the password-forgot endpoint#568
algirdasc merged 1 commit into
mainfrom
fix/password-forgot-throttle

Conversation

@algirdasc

Copy link
Copy Markdown
Owner

Problem

Security audit finding #6: POST /api/auth/password/forgot was public and unthrottled — anyone could trigger unlimited password-reset emails to a known address (mail bomb / harassment). Login had login_throttling; this endpoint had nothing.

Fix

  • New password_forgot rate limiter (config/packages/rate_limiter.yaml): fixed window, 3 per 15 minutes.
  • PasswordController::forgotPassword consumes the limiter twice — keyed by client IP (generic flooding) and by target email (targeted mail-bombing from rotating IPs) — and throws 429 before any mail is sent. 429 flows through the existing HttpExceptionHandler, so the JSON error shape is unchanged.
  • The admin-triggered reset (Admin\UserManagementController) deliberately bypasses the limiter.
  • when@test raises the limit to keep the test suite hermetic (limiter storage is cache.app, which persists across runs).

Implementation note: this Symfony version registers no RateLimiterFactory $passwordForgotLimiter named alias, so injection is by service id (#[Autowire(service: 'limiter.password_forgot')]).

Tests

  • New PasswordForgotThrottleTest (unit, real RateLimiterFactory + InMemoryStorage): IP-budget exhaustion and email-budget exhaustion each yield 429 and the reset service is never called.
  • New functional testForgotPasswordIsAccepted: legit request still succeeds and the (hashed) reset token is persisted.
  • Full suite: 137 tests / 574 assertions OK; PHPStan clean.

POST /api/auth/password/forgot was unthrottled: anyone could trigger
unlimited reset emails to a known address (mail bomb). Login had
throttling; this endpoint did not.

Adds a fixed_window limiter (3 per 15 min) consumed per client IP and
per target email; exceeding either returns 429 before any mail is sent.
The admin-triggered reset path is deliberately not throttled. Test env
gets a high limit so the suite stays hermetic.
@algirdasc
algirdasc merged commit 8799714 into main Aug 10, 2026
4 checks passed
@algirdasc
algirdasc deleted the fix/password-forgot-throttle branch August 10, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant