Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/desarrolladorandres2026-gif/Native-tailwind/llms.txt

Use this file to discover all available pages before exploring further.

Debuta’s password recovery flow is a three-step process: request a code, verify it, then submit the new password. A random 6-digit code is generated, bcrypt-hashed before storage, and delivered to the user’s inbox via Nodemailer. The code expires after 15 minutes. All three endpoints are public — no JWT is required.
For security, POST /api/password/forgot always returns the same response message regardless of whether the email exists in the database. This prevents account enumeration attacks.
Social auth accounts (Google / Facebook) don’t have a local password. Directing those users to this flow will silently fail at the forgot step (the neutral response is returned). Direct them to their provider’s account recovery instead.

The three-step flow

1

Send a verification code — POST /api/password/forgot

Generates a 6-digit numeric code, bcrypt-hashes it, stores it on the user document with a 15-minute expiry, and sends the plaintext code to the user’s email via Nodemailer.Auth required: No

Request body

{
  "correo": "ana@example.com"
}
correo
string
required
The email address associated with the account. Whitespace is trimmed and the value is normalised to lowercase before the lookup.

Validation

  • correo must pass a basic email format check (/^[^\s@]+@[^\s@]+\.[^\s@]+$/). Sends 400 if invalid.
  • If the email is not found, the endpoint still returns 200 with the neutral message below.

Response — 200 OK

{
  "message": "Si ese correo está registrado, recibirás un código en breve."
}
This response is returned whether or not the email exists in the system.

Storage details

The following fields are updated on the Usuario document:
FieldValue
resetPasswordCodebcrypt hash (cost 10) of the 6-digit code. Field is select: false and never returned in responses.
resetPasswordExpiresDate.now() + 15 × 60 × 1000 — 15 minutes from request time

Error responses

StatusCondition
400correo is missing or fails email format validation
500Internal server error or Nodemailer failure

curl example

curl -X POST https://api.debuta.app/api/password/forgot \
  -H "Content-Type: application/json" \
  -d '{ "correo": "ana@example.com" }'
2

Validate the code — POST /api/password/verify

Checks the submitted code against the stored hash without changing the password. This is the intermediate validation step that unlocks the new-password form in the UI. The code and expiry are not cleared at this stage — they remain valid until POST /api/password/reset is called.Auth required: No

Request body

{
  "correo": "ana@example.com",
  "code": "483921"
}
correo
string
required
The account’s email address.
code
string
required
The 6-digit code the user received by email. Leading/trailing whitespace is trimmed before comparison.

Validation

The endpoint looks up a user whose correo matches and whose resetPasswordExpires is still in the future ($gt: new Date()). If the code has expired, the user document won’t be found and a 400 is returned.

Response — 200 OK

{
  "message": "Código verificado correctamente",
  "valido": true
}
valido
boolean
Always true on a 200 response. The app uses this flag to enable the new-password input.

Error responses

StatusCondition
400correo or code missing
400Code has expired (more than 15 minutes since forgot was called)
400Code does not match the stored hash
500Internal server error

curl example

curl -X POST https://api.debuta.app/api/password/verify \
  -H "Content-Type: application/json" \
  -d '{
    "correo": "ana@example.com",
    "code": "483921"
  }'
3

Reset the password — POST /api/password/reset

Performs a final code validation, sets the new password (the pre-save hook bcrypt-hashes it automatically), then clears resetPasswordCode and resetPasswordExpires from the document so the code cannot be reused.Auth required: No

Request body

{
  "correo": "ana@example.com",
  "code": "483921",
  "nuevaPassword": "nuevaContraseña456"
}
correo
string
required
The account’s email address.
code
string
required
The same 6-digit code from the email. Re-validated here against the stored hash.
nuevaPassword
string
required
The new plaintext password. Minimum 6 characters. The bcrypt pre-save hook on the Usuario model hashes it before it is persisted.

Response — 200 OK

{
  "message": "Contraseña actualizada correctamente"
}

Post-reset state

FieldAfter reset
passwordNew bcrypt hash
resetPasswordCodenull
resetPasswordExpiresnull

Error responses

StatusCondition
400Any of correo, code, or nuevaPassword is missing
400nuevaPassword is shorter than 6 characters
400Code has expired
400Code does not match the stored hash
500Internal server error

curl example

curl -X POST https://api.debuta.app/api/password/reset \
  -H "Content-Type: application/json" \
  -d '{
    "correo": "ana@example.com",
    "code": "483921",
    "nuevaPassword": "nuevaContraseña456"
  }'

Flow diagram

Client                             Server
  │                                  │
  ├─── POST /api/password/forgot ───►│  Generate 6-digit code
  │    { correo }                    │  Hash & store with 15-min expiry
  │◄── { message } ─────────────────┤  Send code via Nodemailer
  │                                  │
  │  [user reads email, enters code] │
  │                                  │
  ├─── POST /api/password/verify ───►│  Compare code against hash
  │    { correo, code }              │  Check expiry
  │◄── { valido: true } ────────────┤  (code & expiry preserved)
  │                                  │
  │  [user enters new password]      │
  │                                  │
  ├─── POST /api/password/reset ────►│  Re-validate code + expiry
  │    { correo, code,               │  Set new password (pre-save hashes it)
  │      nuevaPassword }             │  Clear resetPasswordCode & Expires
  │◄── { message } ─────────────────┤
If the user requests a new code before the first one expires, calling POST /api/password/forgot again overwrites resetPasswordCode and resetPasswordExpires, effectively invalidating the previous code.

Build docs developers (and LLMs) love