# 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. 1. **Storage**: Lock state and attempt counts are stored in **Redis** (keys `account_lockout:attempts:{email}` and `account_lockout:locked:{email}`). 2. **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). 3. **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). 4. **While locked**: Any login for that email returns **HTTP 423 Locked** with message *"Account is locked. Please try again later."* 5. **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: ```bash curl -X POST http://localhost:8080/api/v1/admin/auth/unlock-account \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -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: ```bash # 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): ```bash 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 (`IsAccountLocked` returns false). - Not have failed attempts recorded (`RecordFailedAttempt` is a no-op). This is intended for **test / dev accounts** only; avoid exempting real user emails in production.