Class PasswordResetService

java.lang.Object
com.ericbouchut.learndev.auth.PasswordResetService

@Service public class PasswordResetService extends Object
The password-reset ("forgot password") flow.

Security properties (see issues #53 and #55):

  • Opaque, high-entropy token: 32 random bytes (256 bits) from SecureRandom, sent as base64url in the email link.
  • Hashed at rest: only the SHA-256 of the token is stored; a database leak yields no usable link.
  • Short-lived and single-use: expires_at enforces the TTL, used_at enforces single use; a new request or a successful reset invalidates all other outstanding tokens.
  • Enumeration-safe: every outcome of a request (unknown email, rate-limited, sent) is invisible to the caller; only the audit trail differs.
  • Rate-limited per user and per IP. The table has no created_at column, so recency is derived from expires_at: created > now - window equals expires_at > now - window + ttl.
  • Constructor Details

  • Method Details

    • requestReset

      @Transactional public void requestReset(String email, String ipAddress, String resetUrlBase)
      Handles a "forgot password" request. Always succeeds from the caller's point of view (enumeration-safe): whether the email exists, is rate-limited, or a mail was actually sent is only visible in the audit trail.
      Parameters:
      email - address typed in the form
      ipAddress - requester IP (rate limiting and audit)
      resetUrlBase - absolute URL of the reset page, without the token, e.g. https://host/auth/reset-password
    • findUsableToken

      @Transactional(readOnly=true) public Optional<PasswordResetToken> findUsableToken(String rawToken)
      Returns the token entity when the raw token is valid (exists, not expired, not used). Read-only: used by the GET page to decide between the form and the invalid-link message.
    • resetPassword

      @Transactional public boolean resetPassword(String rawToken, String newPassword, String ipAddress)
      Consumes the token and sets the new password.
      Returns:
      true on success; false when the token is invalid, expired, or already used (the caller shows the invalid-link message)