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

2.0 KiB

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.