Skip to main content

Rate limiting

This page describes the limits actually active against abuse: sign-in brute force, mass link requests, assistant cost.

What is active​

ProtectionLimitKeyWhere
Account lockout5 failed sign-ins, then account locked for 30 minutesaccountLoginSecurityService
Magic sign-in link3 requests per 5-minute windowaccountMagicLinkService; 429 response
Bemba assistant (application)40 questions per sliding 10-minute windowuserassistant limiter; 429 response
Public website assistant20 questions per 10 minutes, and 500 per day in totalIP address, then globalassistant_public and assistant_public_global limiters

Limiter counters are stored in the application cache: Redis in production, the file system elsewhere. Sign-in attempts are recorded in the database (IP address, browser, failure reason) and a sign-in from a never-seen address is flagged.

Configuration: backend/config/packages/rate_limiter.yaml.

What is not active​

The configuration file also declares, for production, per-IP limiters on the whole API (login, authentication, api_global, api_write, password_reset, export). The code that applies them, RateLimitingSubscriber, is excluded from the services in backend/config/services.yaml: these limits do not apply. Neither Traefik nor Nginx adds rate limiting.

Before enabling it

The required packages are installed. Enabling the subscriber means removing its exclusion in services.yaml. Two points to check first: the global limit (100 requests per minute per IP) is low for a whole team behind one office address, and the address Symfony sees must be the client's, not Traefik's (trusted_proxies).

Unlock an account​

The lock lifts on its own after 30 minutes. To lift it earlier, an administrator calls POST /api/users/{id}/unlock (the interface has no dedicated button yet), or resets the password with app:reset-user-password, which also lifts the lock. A successful sign-in resets the failure counter.

429 response​

The assistant limiters answer 429 Too Many Requests with a readable message. The interface shows it as is; just wait for the window to end.