Files
support_backend/specs/013-auth-hardening/quickstart.md
T

38 lines
2.0 KiB
Markdown
Raw Normal View History

# Quickstart: Validating Authentication Hardening
## Scenario 1 — password reset, end to end
1. `POST /auth/password-reset/request` with a real seeded account's email. **Expected**: `200`,
generic message; the server log shows a `password_reset_requested` line with a `resetUrl`
containing the real token.
2. Repeat with an email that doesn't exist. **Expected**: identical `200` response body to
step 1 — diff them to confirm.
3. `POST /auth/password-reset/consume` with the token from step 1's log and a policy-meeting new
password. **Expected**: `200`.
4. Repeat step 3 with the same token. **Expected**: rejected — the token is single-use.
5. `POST /auth/login` with the account's email and the new password from step 3. **Expected**:
`200`. Repeat with the account's old password. **Expected**: `401`.
## Scenario 2 — password strength enforced everywhere
1. `POST /admin/users` (as admin) with a password shorter than `PASSWORD_MIN_LENGTH`.
**Expected**: `400`, naming the actual minimum length.
2. `POST /auth/password-reset/consume` with a valid token and a too-short new password.
**Expected**: the same `400` rejection reason as step 1.
## Scenario 3 — login rate limiting
1. Submit `LOGIN_RATE_LIMIT_MAX_ATTEMPTS` failed login attempts for the same email within
`LOGIN_RATE_LIMIT_WINDOW_SECONDS`. **Expected**: each returns `401` (the existing
identical-failure-response).
2. Submit one more attempt for that same email, still within the window — this time with the
*correct* password. **Expected**: `429`, not `200` — the rate limit is checked before
credentials (FR-007).
3. Submit an attempt for a *different* email within the same window. **Expected**: proceeds
normally (evaluated on its own credentials, not rate-limited).
## What "done" looks like
All three scenarios pass against a real Postgres/Redis, and `POST /admin/users`'s own existing
tests (010-identity-auth) still pass with the added password-strength check in place.