Files
SkyMoney/SECURITY_FORGOT_PASSWORD.md
Ricearoni1245 15e0c0a88a
Some checks failed
Deploy / deploy (push) Successful in 1m28s
Security Tests / security-non-db (push) Failing after 18s
Security Tests / security-db (push) Failing after 22s
feat: implement forgot password, added security updates
2026-03-01 21:47:15 -06:00

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.