47 lines
2.0 KiB
Markdown
47 lines
2.0 KiB
Markdown
# Security Forgot Password Controls
|
|
|
|
## Implemented Controls
|
|
|
|
- Public entry points:
|
|
- `POST /auth/forgot-password/request`
|
|
- `POST /auth/forgot-password/confirm`
|
|
- Enumeration resistance:
|
|
- Request endpoint always returns `200` with a generic success message.
|
|
- No account existence signal for unknown/unverified emails.
|
|
- Verified-account gate:
|
|
- Reset tokens are issued only when `emailVerified=true`.
|
|
- Token security:
|
|
- Reset links contain `uid` and raw `token` in query params.
|
|
- Server stores only `SHA-256(token)`.
|
|
- Token type is `password_reset`.
|
|
- Token must match `uid`, be unused, and be unexpired.
|
|
- Token is consumed once (`usedAt`) and cannot be reused.
|
|
- Session invalidation:
|
|
- Added `User.passwordChangedAt`.
|
|
- JWT auth middleware rejects tokens with `iat <= passwordChangedAt`.
|
|
- Reset and authenticated password-change both set `passwordChangedAt`.
|
|
- Abuse controls:
|
|
- Request endpoint: per-IP + email-fingerprint route keying.
|
|
- Confirm endpoint: per-IP + uid-fingerprint route keying.
|
|
- Endpoint-specific rate limits via env config.
|
|
- Logging hygiene:
|
|
- Structured security events for request/email/confirm outcomes.
|
|
- No plaintext password or raw token in logs.
|
|
- Misconfiguration resilience:
|
|
- Email send failures do not leak through API response shape.
|
|
- Generic response is preserved if SMTP is unavailable.
|
|
|
|
## Environment Settings
|
|
|
|
- `PASSWORD_RESET_TTL_MINUTES` (default `30`)
|
|
- `PASSWORD_RESET_RATE_LIMIT_PER_MINUTE` (default `5`)
|
|
- `PASSWORD_RESET_CONFIRM_RATE_LIMIT_PER_MINUTE` (default `10`)
|
|
|
|
## Operational Verification
|
|
|
|
1. Verify `/auth/forgot-password/request` always returns the same JSON for unknown, unverified, and verified addresses.
|
|
2. Verify only verified users get `EmailToken(type=password_reset)` rows.
|
|
3. Verify `confirm` succeeds once and fails on replay.
|
|
4. Verify pre-reset session cookies fail on protected routes after successful reset.
|
|
5. Verify security logs contain `auth.password_reset.*` events and no raw token values.
|