Skip to main content
Woku detects and blocks brute force attacks against login to protect user accounts.

How it works

  1. Every login attempt (successful or failed) is recorded in a LoginAttempt table with the identifier used (lowercased), the source IP, and the result.
  2. If an account accumulates 5 failed attempts in the last 15 minutes, the system:
    • Sets the user’s lockedUntil field to 15 minutes in the future.
    • Records an auth.lockout entry in the audit log with failedAttempts, lockedUntil, and the IP of the last attempt.
    • Sends an email to the user notifying them of the lockout (see below).
  3. During the lockout, any login attempt (even with the correct password) responds with 403 Forbidden.
  4. Once the 15-minute window passes, the lockout is lifted automatically on the next attempt, no manual action is required.

Notification email

When the lockout is triggered, the system sends the user an email through AWS SES with:
  • Subject: “Temporary lock on your woku account”.
  • Body: explains that we detected several failed attempts, when the lockout is lifted, from which IP, and a CTA to reset the password.
The email is fire-and-forget: if SES is down, the lockout still happens and the email failure is logged internally; there is no impact on security.

Administrative unlock

If the user needs to log in before the window passes, an internal administrator can unlock them from the backoffice. The action is recorded as auth.unlock in the audit log with the IP of the request.

Limitations

  • Detection is per-user, not per-IP. A distributed attacker probing many different users from multiple IPs does not trigger the per-user lockout, but is still exposed to the global rate limit (10 req/s, 50 req/10s, 100 req/min per IP, managed by @nestjs/throttler).
  • If the attacker uses an identifier that does not match any user, there is no lockout (there is nothing to lock), but the rate limit per IP still applies.

Status check

A user’s lockout status is not exposed publicly to the front end. The UI shows the same generic message (“Invalid credentials or locked account”) to avoid revealing whether an account exists, this reduces the risk of user enumeration.