2.4 KiB
2.4 KiB
Account lockout (BE-SEC-007)
How it works
After too many failed login attempts, the account is temporarily locked to slow down brute-force attacks.
- Storage: Lock state and attempt counts are stored in Redis (keys
account_lockout:attempts:{email}andaccount_lockout:locked:{email}). - Failed attempt: Each failed login (wrong password, user not found, or email not verified) calls
RecordFailedAttempt(email). The attempt counter is incremented; it expires after a window (default 15 minutes). - Lock: When the number of failed attempts in the window reaches max attempts (default 5), the account is locked for a lockout duration (default 30 minutes).
- While locked: Any login for that email returns HTTP 423 Locked with message "Account is locked. Please try again later."
- Unlock: The lock key has a TTL; when it expires, the account is automatically unlocked. A successful login also clears the lock and the attempt counter.
Defaults (see internal/services/account_lockout_service.go):
- Max attempts: 5
- Window: 15 minutes
- Lockout duration: 30 minutes
If Redis is unavailable, lockout is disabled (no locking, no recording).
Unlock an account
Option 1: Admin API (recommended)
As an admin user, send:
curl -X POST http://localhost:8080/api/v1/admin/auth/unlock-account \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <ADMIN_ACCESS_TOKEN>" \
-d '{"email":"user@example.com"}'
Response: 200 OK with {"message":"account unlocked","email":"user@example.com"}.
Option 2: Redis CLI
If you have access to Redis:
# Replace with the locked user's email
EMAIL="user@example.com"
redis-cli DEL "account_lockout:locked:${EMAIL}" "account_lockout:attempts:${EMAIL}"
Disable lockout for specific accounts
Use exempt emails so those accounts are never locked and failed attempts are not recorded.
Environment variable (comma-separated list):
ACCOUNT_LOCKOUT_EXEMPT_EMAILS=testuser@example.com,admin@test.com
Example in .env or .env.development:
ACCOUNT_LOCKOUT_EXEMPT_EMAILS=testuser@example.com
After restarting the API, that email will:
- Never be considered locked (
IsAccountLockedreturns false). - Not have failed attempts recorded (
RecordFailedAttemptis a no-op).
This is intended for test / dev accounts only; avoid exempting real user emails in production.