Overview
The password reset flow consists of two endpoints:
- Forgot Password: Request a password reset token
- 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
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 of the request. Returns “success” for all valid requests
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
The password reset token received from the forgot-password endpoint
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 of the request. Returns “success” when password is reset successfully
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.