Skip to main content

Overview

The password reset flow consists of two endpoints:
  1. Forgot Password: Request a password reset token
  2. Reset Password: Use the token to set a new password
This implements a secure password recovery mechanism that prevents username enumeration attacks.

Forgot Password

Initiates the password reset process by generating a reset token for the user. For security reasons, this endpoint always returns a success response, regardless of whether the username exists.

Request

username
string
required
The username of the account requesting password reset

Example Request

curl -X POST https://api.example.com/api/v1/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{
    "username": "john_doe"
  }'

Response

status
string
Status of the request. Returns “success” for all valid requests
message
string
Generic message that doesn’t reveal whether the user exists

Example Response

{
  "status": "success",
  "message": "Si el usuario existe, se enviará un enlace de recuperación."
}
In development mode, the reset link is logged to the server console. In production, this would be sent via email.

Error Responses

400 Bad Request

{
  "status": "error",
  "message": "Body requerido."
}
{
  "status": "error",
  "message": "El nombre de usuario es requerido."
}

500 Internal Server Error

{
  "status": "error",
  "message": "Error interno del servidor"
}

Implementation Reference

From backend/Auth/Adapters/auth_controller.py:48-82:
@router.route('/forgot-password', methods=['POST'])
def forgot_password():
    """Endpoint para solicitar el restablecimiento de contraseña."""
    try:
        data = request.get_json()
        if not data:
            return jsonify({"status": "error", "message": "Body requerido."}), 400

        username = data.get('username')
        if not username:
            return jsonify({"status": "error", "message": "El nombre de usuario es requerido."}), 400

        db = next(get_db())
        service = _get_service(db)
        
        token = service.generate_reset_token(username)
        
        if token:
            reset_link = f"http://localhost:5173/reset-password?token={token}"
            logger.info("SIMULANDO ENVÍO DE CORREO A: %s", username)
            logger.info("Enlace de restablecimiento: %s", reset_link)

        # Siempre devolvemos un 200 genérico para evitar filtración de usuarios
        return jsonify({
            "status": "success",
            "message": "Si el usuario existe, se enviará un enlace de recuperación."
        }), 200

    except ValueError as ve:
        return jsonify({"status": "error", "message": str(ve)}), 400
    except Exception as e:
        logger.error("Error inesperado en forgot_password: %s", e)
        return jsonify({"status": "error", "message": "Error interno del servidor"}), 500

Reset Password

Completes the password reset process by validating the token and setting a new password.

Request

token
string
required
The password reset token received from the forgot-password endpoint
new_password
string
required
The new password to set for the account

Example Request

curl -X POST https://api.example.com/api/v1/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "new_password": "NewSecurePassword456"
  }'

Response

status
string
Status of the request. Returns “success” when password is reset successfully
message
string
Confirmation message

Example Response

{
  "status": "success",
  "message": "Contraseña restablecida exitosamente."
}

Error Responses

400 Bad Request

Returned when required fields are missing or the token is invalid:
{
  "status": "error",
  "message": "Body requerido."
}
{
  "status": "error",
  "message": "Token y nueva contraseña son requeridos."
}
{
  "status": "error",
  "message": "Token inválido o expirado"
}

500 Internal Server Error

{
  "status": "error",
  "message": "Error interno del servidor"
}

Implementation Reference

From backend/Auth/Adapters/auth_controller.py:84-112:
@router.route('/reset-password', methods=['POST'])
def reset_password():
    """Endpoint para enviar la nueva contraseña con el token."""
    try:
        data = request.get_json()
        if not data:
            return jsonify({"status": "error", "message": "Body requerido."}), 400

        token = data.get('token')
        new_password = data.get('new_password')

        if not token or not new_password:
            return jsonify({"status": "error", "message": "Token y nueva contraseña son requeridos."}), 400

        db = next(get_db())
        service = _get_service(db)
        
        service.reset_password_with_token(token, new_password)
        
        return jsonify({
            "status": "success",
            "message": "Contraseña restablecida exitosamente."
        }), 200

    except ValueError as ve:
        return jsonify({"status": "error", "message": str(ve)}), 400
    except Exception as e:
        logger.error("Error inesperado en reset_password: %s", e)
        return jsonify({"status": "error", "message": "Error interno del servidor"}), 500

Security Considerations

The forgot-password endpoint intentionally returns the same response for both existing and non-existing users to prevent username enumeration attacks.
Reset tokens should have a limited validity period. Ensure proper token expiration is implemented in the AuthService.

Build docs developers (and LLMs) love