Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/CodexaCP/SERVICIOS-BACK/llms.txt

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

The ServiciosYa API implements a two-step, OTP-based (one-time password) password reset flow. A user who has forgotten their password requests a numeric code to be sent to their registered email address. They then submit that code together with a new password to complete the reset. The raw OTP is never stored — only a SHA-256 hash is persisted — so a database leak cannot expose the code.

Password policy

New passwords set through the reset flow (and through POST /api/auth/change-password) must satisfy the following rules:
  • Minimum 8 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one digit
  • At least one special character
Requests that do not meet this policy are rejected with 400 Bad Request before the OTP is consumed.

Step 1 — Request an OTP

Send the user’s email address and the company identifier to start the reset flow. Endpoint: POST /api/auth/forgot-password
Auth: Public (no token required)
Request body
{
  "email": "user@example.com",
  "companyId": 1
}
FieldTypeRequiredNotes
emailstringThe user’s registered email address
companyIdintegerTenant identifier. Falls back to 1 if <= 0
curl example
curl -X POST https://api.example.com/api/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "companyId": 1}'
Response 200 OK — always returned, regardless of whether the email exists
{
  "message": "Si el correo existe, te enviaremos instrucciones para restablecer tu contrasena."
}
The generic 200 response is intentional and must not be changed. Returning a different status code for unknown emails would allow an attacker to enumerate registered users. Your client application should always display the same message regardless of the response.

What happens server-side

  1. The API generates a cryptographically random OTP using RandomNumberGenerator.GetInt32.
  2. The raw code is SHA-256 hashed with SHA256.HashData; only the hash is written to the database.
  3. The expiry timestamp is set to UtcNow + ExpirationMinutes (clamped to the 10–15 minute range).
  4. If the email exists and the rate-limit window has not been exceeded, PasswordResetNotificationService sends an HTML email containing the raw OTP.

Rate limiting

SettingDefaultDescription
MaxRequestsPerWindow3Maximum OTP requests allowed within the window
RequestWindowMinutes15Rolling window duration in minutes
If a user exceeds MaxRequestsPerWindow requests within RequestWindowMinutes, the stored procedure silently suppresses new OTP creation (the API still returns 200).

Step 2 — Reset the password

Submit the OTP code received by email together with the new password. Endpoint: POST /api/auth/reset-password
Auth: Public (no token required)
Request body
{
  "email": "user@example.com",
  "companyId": 1,
  "otpCode": "847291",
  "newPassword": "NewSecure1!"
}
FieldTypeRequiredNotes
emailstringMust match the address used in Step 1
companyIdintegerSame tenant identifier as Step 1
otpCodestringThe numeric code from the email
newPasswordstringMust satisfy the password policy
curl example
curl -X POST https://api.example.com/api/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "companyId": 1,
    "otpCode": "847291",
    "newPassword": "NewSecure1!"
  }'
Response 200 OK
{
  "message": "Tu contrasena fue actualizada."
}

Failed-attempt limit

SettingDefaultDescription
MaxAttempts5Number of incorrect OTP submissions before the code is invalidated
After MaxAttempts failed verifications the OTP record is marked invalid and the user must request a new code.

OTP properties

SettingDefaultConstraintsDescription
ExpirationMinutes10Clamped to 10–15Minutes until the OTP expires
OtpDigits6Clamped to 6–8Length of the generated OTP code
OTP codes are generated with RandomNumberGenerator.GetInt32 from the System.Security.Cryptography namespace, which produces a cryptographically strong random integer. A 6-digit code has 900,000 possible values; combined with the MaxAttempts limit, brute-force is not feasible within the expiry window.

Configuration

Add the following sections to appsettings.json (or the relevant environment-specific override):
{
  "PasswordReset": {
    "ExpirationMinutes": 10,
    "OtpDigits": 6,
    "MaxAttempts": 5,
    "MaxRequestsPerWindow": 3,
    "RequestWindowMinutes": 15
  },
  "Email": {
    "Enabled": true,
    "Host": "smtp.example.com",
    "Port": 587,
    "Username": "no-reply@example.com",
    "Password": "smtp-password",
    "FromAddress": "no-reply@example.com",
    "FromName": "ServiciosYa",
    "EnableSsl": true
  }
}
Email.Enabled must be true and Email.Host must be set to a reachable SMTP server for OTP emails to be delivered. When Enabled is false or Host is empty, PasswordResetNotificationService logs a warning and returns without sending — the OTP is still created in the database, but the user will never receive the code.

Email configuration fields

FieldDefaultDescription
EnabledfalseMaster switch. Set to true to enable email sending
Host""SMTP server hostname
Port587SMTP port (587 for STARTTLS, 465 for implicit TLS)
Username""SMTP authentication username
Password""SMTP authentication password
FromAddress""Sender email address
FromName"ServiciosYa"Display name shown in the From header
EnableSsltrueWhether to use STARTTLS/SSL for the SMTP connection

Flow summary

1

Client calls POST /api/auth/forgot-password

Provides email and companyId. The API always returns 200.
2

API generates and hashes the OTP

Raw OTP is SHA-256 hashed. Only the hash is stored in the database with an expiry timestamp.
3

Email is sent (if SMTP is configured)

PasswordResetNotificationService sends an HTML email showing the raw OTP code and its expiry time.
4

User submits OTP via POST /api/auth/reset-password

Provides email, companyId, otpCode, and newPassword. The API hashes the submitted code and compares it to the stored hash.
5

Password is updated on success

The user’s PasswordHash is updated, MustChangePassword is set to 0, and the OTP record is invalidated.

Build docs developers (and LLMs) love