Edit Page

Password Reset

RESTHeart Cloud

The password reset flow is split into two endpoints: one to request a reset link and one to apply the new password. The flow is designed to prevent account enumeration: the first endpoint always returns 202 Accepted regardless of whether the email exists.

sequenceDiagram
  participant C as Client
  participant R as RESTHeart
  participant M as MongoDB
  participant E as Email

  Note over C,E: Step 1 — Request reset link
  C->>R: POST /auth/forgot-password {email}
  R->>M: lookup user (result not revealed)
  alt user found and verified
    R->>M: generate + store reset token (TTL 1h)
    R->>E: password reset email
  end
  R-->>C: 202 Accepted (always)

  Note over C,M: Step 2 — Apply new password
  C->>R: PATCH /auth/reset-password {email, token, password}
  Note over R: constant-time token compare + TTL check
  Note over R: zxcvbn password strength check
  R->>M: hash + save password, unset reset token
  M-->>R: OK
  R-->>C: 200 OK

POST /auth/forgot-password — publicly accessible ($unauthenticated).

Request

POST /auth/forgot-password
Content-Type: application/json

{ "email": "alice@example.com" }

Server-side steps

  1. Validate the email format.

  2. Look up the user by email (result is not revealed to the caller).

  3. If the user exists and is verified (roles ≠ ["$unauthenticated"]):

    • Generate a cryptographically random passwordResetToken (256-bit) and record passwordResetCreatedAt.

    • Send a reset email containing a one-time link:

      {frontendUrl}/auth/reset-password?email=alice@example.com&token=<passwordResetToken>
    • TTL: 1 hour.

  4. Always return 202 Accepted — even if the email is unknown or the user is unverified.

Note
Responding 202 unconditionally prevents an attacker from enumerating registered email addresses by observing response differences.

Response

{ "message": "If that address is registered, a reset link has been sent." }

Apply the new password

PATCH /auth/reset-password — publicly accessible ($unauthenticated).

Request

PATCH /auth/reset-password
Content-Type: application/json

{
  "email":              "alice@example.com",
  "passwordResetToken": "<token from email>",
  "password":           "new-correct-horse-battery"
}

Server-side steps

  1. Validate all required fields.

  2. Find user by email.

  3. Compare passwordResetToken using constant-time comparison.

  4. Check passwordResetCreatedAt + TTL (passwordResetTokenTtlHours, default 1 hour).

  5. Enforce password strength (zxcvbn score ≥ minimumPasswordStrength).

  6. Update the user:

    {
      "$set":   { "password": "<bcrypt hash>" },
      "$unset": { "passwordResetToken": "", "passwordResetCreatedAt": "" }
    }
  7. Return 200 OK.

Error responses

Status Reason

400

Missing fields; password too weak; token not found or expired

Note
The token is one-shot: it is $unset on first successful use, so the link cannot be replayed.