Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Glemynart/SaaS/llms.txt

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

La Oficina Nítida provides a two-step, token-based password reset flow designed to prevent both account enumeration and token theft. The reset token follows the same security pattern as refresh tokens: the raw secret travels only in the reset link sent to the user’s email, while only the bcrypt hash of that secret is stored in the database. A user can only have one pending reset token at a time — requesting a new one invalidates any previous unredeemed token.

Flow overview

1

Request a reset link

Call POST /auth/forgot-password with the user’s tenantNit and email. If the combination matches an active user, the platform generates a one-time reset token and sends an email containing a link of the form https://app.oficina-nitida.com/reset-password?token={base64Token}. The endpoint always returns 200 OK regardless of whether the combination exists.
2

User clicks the link

The user opens their email and clicks the reset link. The frontend extracts the token query parameter and presents a form to enter a new password.
3

Set the new password

The frontend calls POST /auth/reset-password with the token and the chosen newPassword. On success, the password is updated, all active refresh tokens for the user are revoked (forcing re-login on all devices), and the reset token is permanently consumed.

POST /auth/forgot-password

POST /auth/forgot-password
No authentication required. Always returns 200 OK. Initiates the password reset flow. If the tenantNit + email combination matches an active user in an active tenant, a one-time reset token is generated and an email is dispatched to the user. Any previously pending (unconsumed) reset token for the same user is invalidated before the new one is created.

Request body

tenantNit
string
required
The Colombian NIT of the organization, without the check digit. Combined with email to uniquely identify the user across tenants.
email
string
required
The email address of the account to reset. Must be a valid email format.

Example request

curl -X POST https://api.oficina-nitida.com/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{
    "tenantNit": "900123456",
    "email": "admin@sanjose.edu.co"
  }'

Example response

{
  "message": "Si el correo existe, recibirás instrucciones para restablecer tu contraseña."
}
The response is identical whether or not the tenantNit + email combination matched a real account. See the anti-enumeration note below. Rate limit: 3 requests per hour per IP address. This is an intentionally restrictive limit to prevent abuse of the email-sending infrastructure.

POST /auth/reset-password

POST /auth/reset-password
No authentication required. Returns 200 OK on success. Consumes a one-time reset token and sets a new password. On success, the platform:
  1. Marks the PasswordResetToken row as used (usedAt: now()).
  2. Updates the user’s passwordHash with a fresh bcrypt hash of the new password.
  3. Revokes all active RefreshToken rows for the user, terminating every active session.

Request body

token
string
required
The one-time reset token extracted from the reset link query parameter. Encoded as base64(tokenId:secret). Single-use and expires 1 hour after generation.
newPassword
string
required
The new password to set. Minimum 8 characters, maximum 100 characters. Stored as a bcrypt hash — the plain-text value is never persisted.

Example request

curl -X POST https://api.oficina-nitida.com/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "ZjRkMmE1YjMtNmQ3ZS01ZjhmLTBiMWMtMmQzZTVmNmE3YjhjOnJlc2V0U2VjcmV0",
    "newPassword": "NuevaClave2025!"
  }'

Example response

{
  "message": "Contraseña actualizada exitosamente."
}

Error cases

StatusCause
400 Bad RequestThe token is malformed, has already been used (usedAt is set), or has expired (more than 1 hour has elapsed since generation). Also returned if newPassword is shorter than 8 characters or longer than 100 characters.
400 Bad RequestThe token ID does not exist in the database. The response uses the same generic message as the expiry case to avoid information leakage.
Rate limit: 5 requests per 15 minutes per IP address.

Security details

One-time use. Each PasswordResetToken row has a usedAt column. Once the token is consumed, any further attempt to use the same token returns an error. 1-hour expiry. Tokens are created with an expiresAt timestamp set to 60 minutes in the future. The consumeReset method rejects any token whose expiresAt is in the past, even if it has not yet been used. Hash-only storage. The token sent to the user’s email is base64(tokenId:secret), where secret is a cryptographically random UUID. Only bcrypt(secret) is stored in the password_reset_tokens table — the plaintext secret is never written to the database. An attacker with read access to the database cannot reconstruct a valid token. Pending-token invalidation. When requestReset is called for a user who already has a pending (unused, unexpired) token, the existing token is invalidated before the new one is created. This prevents accumulation of valid tokens and ensures only the most recently requested link works. Session revocation on completion. A successful consumeReset call revokes all rows in refresh_tokens for the affected user. Any client holding an old refresh token will receive 401 on their next /auth/refresh attempt and will need to log in again. Active access tokens remain valid until their natural 15-minute expiry. Multi-tenant safe. The token is validated against the tenantId of the user who owns the PasswordResetToken row. A token generated for a user in tenant A cannot be used to reset a password in tenant B.

Audit events

Both steps of the reset flow produce entries in the AuditLog table:
ActionTriggered by
PASSWORD_RESET_REQUESTEDA successful call to POST /auth/forgot-password that matched a real user
PASSWORD_RESET_COMPLETEDA successful call to POST /auth/reset-password
Both events include tenantId, userId, and metadata with the caller’s IP address and user-agent string.
Anti-enumeration pattern. POST /auth/forgot-password always returns 200 OK with an identical success message, regardless of whether the tenantNit + email combination matched a real account. This prevents attackers from using the endpoint to discover which email addresses are registered in a given tenant. Only the PASSWORD_RESET_REQUESTED audit event (visible to admins) records whether a genuine reset was initiated.

Build docs developers (and LLMs) love