test(auth): password-reset redeem coverage (PMS-659) #458

Merged
nrupard merged 1 commit from test/PMS-659-password-reset-coverage into main 2026-07-22 18:17:15 +02:00
Owner

What

Adds automated coverage for the legacy password-reset redeem flow (PMS-659 verification). Five integration tests in tests/auth.rs.

Why

PMS-659 verifies the reset flow and its protections. The redeem half (redemption, expiry, single-use, session revocation, no-enumeration) had almost no coverage. The emailed token is {user_id}.{secret} with only the Argon2 hash of secret stored, so the secret cannot be read back from the DB and request_password_reset returns nothing; the tests mint a token row with a known secret (craft_reset_token) to drive the redeem endpoint.

Tests

  • reset_password_changes_password_and_revokes_sessions - a valid reset changes the password (old fails, new logs in), stamps the token used_at, and revokes all user_sessions (logout_all).
  • reset_password_rejects_expired_token - the expires_at > NOW() guard.
  • reset_password_token_is_single_use - a second redeem of the same token fails.
  • reset_password_rejects_malformed_and_wrong_secret - a bad token shape and a wrong secret both 400.
  • forgot_password_unknown_email_issues_no_token - 2xx and zero tokens (no user enumeration).

Full cargo test --test auth passes (27/27) against Postgres.

Verification findings (PMS-659)

The reset flow itself is sound: user-bound token, Argon2-hashed at rest, 24h expiry, single-use, all pending tokens invalidated on redeem, sessions revoked (logout_all). Login is rate-limited (IP 20/min + email 5/min). Two gaps found, filed as linked issues:

  • PMS-680: /auth/forgot-password is not rate-limited (login is), so reset-email bombing is unguarded.
  • PMS-681: a reset revokes refresh but not already-issued access tokens (the access path is stateless; 1h TTL).

Note: the legacy password login has no persistent lockout; it relies on the per-(IP, email) rate limit instead (hard lockout enables victim-lockout DoS). MFA has its own lockout (mfa_failed_codes_lock_account).

#PMS-659

## What Adds automated coverage for the legacy password-reset **redeem** flow (PMS-659 verification). Five integration tests in `tests/auth.rs`. ## Why PMS-659 verifies the reset flow and its protections. The redeem half (redemption, expiry, single-use, session revocation, no-enumeration) had almost no coverage. The emailed token is `{user_id}.{secret}` with only the Argon2 hash of `secret` stored, so the secret cannot be read back from the DB and `request_password_reset` returns nothing; the tests mint a token row with a known secret (`craft_reset_token`) to drive the redeem endpoint. ## Tests - `reset_password_changes_password_and_revokes_sessions` - a valid reset changes the password (old fails, new logs in), stamps the token `used_at`, and revokes all `user_sessions` (logout_all). - `reset_password_rejects_expired_token` - the `expires_at > NOW()` guard. - `reset_password_token_is_single_use` - a second redeem of the same token fails. - `reset_password_rejects_malformed_and_wrong_secret` - a bad token shape and a wrong secret both 400. - `forgot_password_unknown_email_issues_no_token` - 2xx and zero tokens (no user enumeration). Full `cargo test --test auth` passes (27/27) against Postgres. ## Verification findings (PMS-659) The reset flow itself is sound: user-bound token, Argon2-hashed at rest, 24h expiry, single-use, all pending tokens invalidated on redeem, sessions revoked (`logout_all`). Login is rate-limited (IP 20/min + email 5/min). Two gaps found, filed as linked issues: - **PMS-680**: `/auth/forgot-password` is not rate-limited (login is), so reset-email bombing is unguarded. - **PMS-681**: a reset revokes refresh but not already-issued access tokens (the access path is stateless; 1h TTL). Note: the legacy password login has no persistent lockout; it relies on the per-(IP, email) rate limit instead (hard lockout enables victim-lockout DoS). MFA has its own lockout (`mfa_failed_codes_lock_account`). #PMS-659
test(auth): cover the password-reset redeem flow (PMS-659)
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 58s
Check / fmt + clippy + build + tests (pull_request) Successful in 2m11s
Integration / integration tests (pull_request) Successful in 9m14s
Create release / Gate (release-branch merges only) (pull_request) Successful in 2s
Create release / Create release from merged PR (pull_request) Has been skipped
066e3f7502
PMS-659 verification found the reset redeem path (redemption, expiry, single-use, session revocation, no-enumeration) had almost no automated coverage. Add five integration tests to tests/auth.rs. Because the emailed token is `{user_id}.{secret}` and only the Argon2 hash of the secret is stored, the tests mint a token row with a known secret (`craft_reset_token`) to exercise the redeem half that the request-side test cannot reach.

Coverage: a valid reset changes the password and revokes the user's sessions (logout_all), rejects an expired token, enforces single-use, rejects malformed and wrong-secret tokens, and confirms forgot-password issues no token for an unknown email (no enumeration).

Two defects found during verification are filed separately: forgot-password is not rate-limited (PMS-680) and a reset does not invalidate already-issued access tokens within their 1h TTL (PMS-681).

#PMS-659

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch test/PMS-659-password-reset-coverage 2026-07-22 18:17:15 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!458
No description provided.