Skip to main content

Solicitar Restablecimiento de Contraseña

Solicita un enlace de restablecimiento de contraseña que se enviará por correo electrónico. El enlace es válido por 10 minutos.


Información General

Este endpoint permite a los usuarios solicitar un restablecimiento de contraseña cuando la han olvidado. El sistema genera un token único y envía un enlace de restablecimiento al correo electrónico del usuario.

POST/auth/forgot-password

Genera un token de restablecimiento y envía un enlace al email del usuario

📋 Parámetros

emailstringrequerido

Dirección de correo electrónico del usuario registrado

📤 Respuesta

{
"code": 1002,
"message": "Password reset link sent successfully",
"data": {
  "status": "pending"
}
}

Validaciones

Email

  • Debe tener formato válido: usuario@dominio.com
  • Cumplir con regex: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/
  • No puede estar vacío

Respuestas

Respuesta Exitosa

Enlace Enviado Exitosamente

Código 1002 - Enlace de restablecimiento enviado al email.

{
"code": 1002,
"message": "Password reset link sent successfully",
"data": {
"status": "pending"
}
}

Comportamiento del sistema:

  • Se genera un token UUID único
  • Se guarda en Redis: reset:{token} = email (TTL: 10 minutos)
  • Se envía email con plantilla RESET_PASSWORD
  • Enlace generado: https://www.swapbits.site/api/auth/reset-password?token={UUID}
  • Se registra evento de auditoría: password_reset_request

Errores

Email Inválido o Faltante

Código 4006 - Datos faltantes o formato inválido.

{
"code": 4006,
"message": "Missing or invalid data"
}

Causas posibles:

  • Campo email vacío o no enviado
  • Formato de email inválido (no cumple regex)

Proceso Interno del Sistema

Flujo de Operación

sequenceDiagram
participant Client
participant API
participant Redis
participant EmailService

Client->>API: POST /auth/forgot-password
API->>API: Validar formato email

alt Email inválido
API->>Client: 400 (code: 4006)
end

API->>Redis: Generar UUID único
API->>Redis: Guardar reset:{token} = email (TTL: 10 min)
API->>EmailService: Enviar email con link
EmailService-->>Client: Email enviado
API->>Client: 200 (code: 1002)

Note over Client,EmailService: Usuario recibe email con enlace válido por 10 minutos

Pasos del Proceso

  1. Validación de Email: Verifica formato con regex /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  2. Búsqueda de Usuario: Verifica que el email exista en la base de datos
  3. Generación de Token: Crea un UUID v4 único para el proceso de reset
  4. Almacenamiento en Redis:
    • Clave: reset:{token}
    • Valor: email del usuario
    • TTL: 10 minutos (600,000 ms)
  5. Envío de Email: Email con plantilla RESET_PASSWORD que contiene el enlace
  6. Enlace Generado: https://www.swapbits.site/api/auth/reset-password?token={UUID}

Ejemplos de Implementación

JavaScript/TypeScript

async function requestPasswordReset(email) {
try {
const response = await fetch('https://api.swapbits.co/auth/forgot-password', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email })
});

const result = await response.json();

if (result.code === 1002) {
console.log('Enlace de restablecimiento enviado');
// IMPORTANTE: Mostrar mensaje genérico por seguridad
return {
success: true,
message: 'Si el correo está registrado, recibirás un enlace de restablecimiento'
};
} else if (result.code === 4006) {
console.error('Email inválido');
return {
success: false,
error: 'EMAIL_INVALID',
message: 'Por favor ingresa un email válido'
};
}

return { success: false, error: result.message };
} catch (error) {
console.error('Error de red:', error);
return { success: false, error: error.message };
}
}

// Validación en frontend
const validateEmail = (email) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
};

// Uso
const email = 'usuario@ejemplo.com';

if (validateEmail(email)) {
const result = await requestPasswordReset(email);
if (result.success) {
alert(result.message);
} else {
alert(result.message);
}
} else {
alert('Por favor ingresa un email válido');
}

TypeScript con Hook de React

import { useState } from 'react';
import axios from 'axios';

interface ForgotPasswordState {
loading: boolean;
error: string | null;
success: boolean;
}

export function useForgotPassword() {
const [state, setState] = useState<ForgotPasswordState>({
loading: false,
error: null,
success: false
});

const requestReset = async (email: string) => {
// Validar email antes de enviar
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
setState({
loading: false,
error: 'Formato de email inválido',
success: false
});
return false;
}

setState({ loading: true, error: null, success: false });

try {
const response = await axios.post(
'https://api.swapbits.co/auth/forgot-password',
{ email }
);

if (response.data.code === 1002) {
setState({ loading: false, error: null, success: true });
return true;
} else {
throw new Error('Respuesta inesperada del servidor');
}
} catch (error: any) {
const errorMessage = error.response?.data?.message || 'Error al solicitar restablecimiento';
setState({ loading: false, error: errorMessage, success: false });
return false;
}
};

const reset = () => {
setState({ loading: false, error: null, success: false });
};

return { ...state, requestReset, reset };
}

// Componente de ejemplo con cooldown
function ForgotPasswordForm() {
const [email, setEmail] = useState('');
const [cooldown, setCooldown] = useState(0);
const { loading, error, success, requestReset } = useForgotPassword();

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

if (cooldown > 0) return;

const isSuccess = await requestReset(email);

if (isSuccess) {
// Iniciar cooldown de 60 segundos
setCooldown(60);
const interval = setInterval(() => {
setCooldown(prev => {
if (prev <= 1) {
clearInterval(interval);
return 0;
}
return prev - 1;
});
}, 1000);
}
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="correo@ejemplo.com"
required
/>
<button type="submit" disabled={loading || cooldown > 0}>
{loading ? 'Enviando...' : cooldown > 0 ? `Espera ${cooldown}s` : 'Restablecer Contraseña'}
</button>
{success && (
<p className="success">
Si el correo está registrado, recibirás un enlace de restablecimiento
</p>
)}
{error && <p className="error">Error: {error}</p>}
</form>
);
}

Python

import requests
import re
from typing import Dict, Any

def validate_email(email: str) -> bool:
"""Validar formato de email"""
email_regex = r'^[^\s@]+@[^\s@]+\.[^\s@]+$'
return bool(re.match(email_regex, email))

def request_password_reset(email: str) -> Dict[str, Any]:
"""
Solicita restablecimiento de contraseña

Args:
email: Dirección de correo electrónico del usuario

Returns:
Respuesta del servidor con código y mensaje

Raises:
ValueError: Si el email es inválido
requests.RequestException: Si hay error de red
"""
# Validar formato de email
if not validate_email(email):
return {
'success': False,
'error': 'EMAIL_INVALID',
'message': 'Formato de email inválido'
}

url = 'https://api.swapbits.co/auth/forgot-password'
data = {'email': email}

try:
response = requests.post(url, json=data, timeout=10)
result = response.json()

if result['code'] == 1002:
print("Enlace de restablecimiento enviado")
return {
'success': True,
'message': 'Si el correo está registrado, recibirás un enlace de restablecimiento'
}
elif result['code'] == 4006:
return {
'success': False,
'error': 'EMAIL_INVALID',
'message': 'Email inválido o faltante'
}
else:
return {
'success': False,
'error': 'UNKNOWN',
'message': result.get('message', 'Error desconocido')
}

except requests.exceptions.Timeout:
return {
'success': False,
'error': 'TIMEOUT',
'message': 'Tiempo de espera agotado'
}
except requests.exceptions.RequestException as e:
return {
'success': False,
'error': 'NETWORK_ERROR',
'message': str(e)
}

# Ejemplo de uso con interfaz CLI
def main():
email = input("Ingresa tu email: ")

print(f"\nSolicitando restablecimiento para: {email}")
result = request_password_reset(email)

if result['success']:
print(f"\nÉxito: {result['message']}")
print("Revisa tu bandeja de entrada y carpeta de spam")
else:
print(f"\nError: {result['message']}")

if __name__ == '__main__':
main()

cURL

# Solicitud básica
curl -X POST \
'https://api.swapbits.co/auth/forgot-password' \
-H 'Content-Type: application/json' \
-d '{
"email": "usuario@ejemplo.com"
}'
# Script bash con validación y manejo de respuesta
#!/bin/bash

EMAIL="usuario@ejemplo.com"

# Validar formato de email
if ! echo "$EMAIL" | grep -qE '^[^\s@]+@[^\s@]+\.[^\s@]+$'; then
echo "Error: Formato de email inválido"
exit 1
fi

# Hacer request
response=$(curl -s -X POST \
'https://api.swapbits.co/auth/forgot-password' \
-H 'Content-Type: application/json' \
-d "{\"email\": \"$EMAIL\"}")

code=$(echo $response | jq -r '.code')
message=$(echo $response | jq -r '.message')

case $code in
1002)
echo "Éxito: Enlace de restablecimiento enviado"
echo "Revisa tu email: $EMAIL"
;;
4006)
echo "Error: Email inválido o faltante"
;;
*)
echo "Error: $message"
;;
esac

Consideraciones de Seguridad

Protección Contra Enumeración de Usuarios

Comportamiento de seguridad crítico:

El endpoint implementa una protección importante: siempre devuelve la misma respuesta exitosa (código 1002), incluso si el email no está registrado en el sistema.

Comportamiento:

  • Email registrado: Envía enlace real + respuesta 1002
  • Email NO registrado: NO envía email + respuesta 1002 (misma respuesta)

Propósito: Prevenir que atacantes determinen qué emails están registrados en el sistema mediante prueba y error.

Implementación Correcta en Frontend

Buena práctica - Siempre mostrar mensaje genérico:

async function handleForgotPassword(email) {
const result = await requestPasswordReset(email);

// SIEMPRE mostrar este mensaje, sin importar si el email existe
showMessage('Si el correo está registrado, recibirás un enlace de restablecimiento');

// ❌ NO mostrar mensajes como:
// - "Email no encontrado"
// - "Usuario no existe"
// - "Email enviado" (demasiado específico)
}

Prevención de Abuso

Recomendaciones para implementar en frontend:

  1. Rate Limiting Local: Limitar solicitudes por IP/sesión (máximo 3 por hora)
  2. Cooldown Timer: Deshabilitar botón por 60 segundos después de cada envío
  3. CAPTCHA: Implementar después de 2-3 intentos fallidos
  4. Validación Previa: Validar formato de email antes de enviar request
// Ejemplo de cooldown
const [cooldown, setCooldown] = useState(0);
const [requestCount, setRequestCount] = useState(0);

const handleRequest = async (email) => {
if (cooldown > 0) {
alert(`Espera ${cooldown} segundos`);
return;
}

// Verificar límite de intentos
if (requestCount >= 3) {
alert('Has alcanzado el límite de intentos. Intenta más tarde.');
return;
}

await requestPasswordReset(email);
setRequestCount(prev => prev + 1);

// Iniciar cooldown de 60 segundos
setCooldown(60);
const interval = setInterval(() => {
setCooldown(prev => {
if (prev <= 1) {
clearInterval(interval);
return 0;
}
return prev - 1;
});
}, 1000);
};

Características del Sistema

Token de Restablecimiento

Especificaciones técnicas:

  • Tipo: UUID v4 único
  • Almacenamiento: Redis con clave reset:{token}
  • Valor almacenado: Email del usuario
  • TTL: 10 minutos (600,000 ms)
  • Uso: Un solo uso por token
  • Enlace generado: https://www.swapbits.site/api/auth/reset-password?token={UUID}

Expiración y Límites

Límites de tiempo:

  • Token válido por 10 minutos exactos
  • Después de 10 minutos, el token se elimina automáticamente de Redis
  • Si el token expira, el usuario debe solicitar un nuevo enlace
  • Cada solicitud genera un nuevo token único

Email de Restablecimiento

Detalles del email:

  • Plantilla: RESET_PASSWORD (configurada en EmailService)
  • Contenido: Enlace con token incluido
  • Remitente: Sistema SwapBits
  • Evento de auditoría: password_reset_request
  • Log: Se registra cada solicitud para auditoría

Flujo Completo del Usuario

graph TD
A[Usuario olvida contraseña] --> B[Ir a Forgot Password]
B --> C[Ingresar email]
C --> D{Email válido?}
D -->|No| E[Error 4006 - Email inválido]
D -->|Sí| F[POST /auth/forgot-password]
F --> G[Sistema genera UUID]
G --> H[Guardar en Redis - TTL 10min]
H --> I[Enviar email con enlace]
I --> J[Respuesta 1002]
J --> K[Usuario recibe email]
K --> L[Clic en enlace]
L --> M[GET /auth/reset-password?token=xxx]
M --> N[Formulario de nueva contraseña]
N --> O[POST /auth/reset-password]
O --> P[Contraseña actualizada]

Notas Importantes

Información Clave

Puntos importantes a recordar:

  1. Expiración del Token: El enlace es válido por exactamente 10 minutos
  2. Token de Uso Único: Cada token solo puede ser usado una vez
  3. Respuesta Unificada: Siempre retorna código 1002 por seguridad
  4. Email Template: Plantilla RESET_PASSWORD del EmailService
  5. URL del Enlace: https://www.swapbits.site/api/auth/reset-password?token={UUID}
  6. Redis Storage: Clave reset:{token} con email como valor
  7. Auditoría: Evento password_reset_request registrado en logs
  8. Sin Autenticación: No requiere estar autenticado para usar este endpoint

Códigos de Respuesta

CódigoHTTP StatusDescripción
1002200Enlace de restablecimiento enviado correctamente
4006400Email faltante o formato inválido

Probar esta API

¿Quieres probar este endpoint de forma interactiva con tus propios datos?


Enlaces Relacionados