Password Reset
RESTHeart CloudThe 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
Request a reset link
POST /auth/forgot-password — publicly accessible ($unauthenticated).
Request
POST /auth/forgot-password
Content-Type: application/json
{ "email": "alice@example.com" }
Server-side steps
-
Validate the email format.
-
Look up the user by email (result is not revealed to the caller).
-
If the user exists and is verified (roles â‰
["$unauthenticated"]):-
Generate a cryptographically random
passwordResetToken(256-bit) and recordpasswordResetCreatedAt. -
Send a reset email containing a one-time link:
{frontendUrl}/auth/reset-password?email=alice@example.com&token=<passwordResetToken> -
TTL: 1 hour.
-
-
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
-
Validate all required fields.
-
Find user by
email. -
Compare
passwordResetTokenusing constant-time comparison. -
Check
passwordResetCreatedAt+ TTL (passwordResetTokenTtlHours, default 1 hour). -
Enforce password strength (zxcvbn score ≥
minimumPasswordStrength). -
Update the user:
{ "$set": { "password": "<bcrypt hash>" }, "$unset": { "passwordResetToken": "", "passwordResetCreatedAt": "" } } -
Return
200 OK.
Error responses
| Status | Reason |
|---|---|
|
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.
|