Recuperación Relacional de Acceso
Mañuti reemplaza el mecanismo por defecto de contraseñas olvidadas de Laravel (password_reset_tokens basado en cadenas de correo electrónico planas) por una arquitectura relacional normalizada y auditada centrada en la entidad lost_password.
1. Esquema Relacional de lost_password
CREATE TABLE `lost_password` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`usuario_id` BIGINT UNSIGNED NOT NULL,
`token` VARCHAR(255) NOT NULL,
`cat_status` BIGINT UNSIGNED NOT NULL,
`created_at` TIMESTAMP NULL,
`updated_at` TIMESTAMP NULL,
`deleted_at` TIMESTAMP NULL,
FOREIGN KEY (`usuario_id`) REFERENCES `usuarios`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`cat_status`) REFERENCES `catalogos`(`id`)
);
Ventajas del Esquema Relacional
- Integridad Referencial: Vinculado al
iddel usuario. Si el usuario actualiza su correo corporativo, no se rompe el rastro de recuperación. - Estatus por Catálogo: Los estados del ciclo de vida del token no son cadenas de texto sueltas; apuntan a la tabla de Catálogos (
LP_PENDIENTE,LP_USADO,LP_EXPIRADO). - Caducidad Automática: Se calcula contra
created_atpermitiendo ventanas dinámicas (default: 60 minutos).
2. Flujo Criptográfico Transaccional
flowchart TD
REQ["[Solicitud /forgot-password]"]
STEPS1["1. Valida existencia del correo en tabla 'usuarios'<br/>2. Genera token criptográfico de 64 caracteres: Str::random(64)<br/>3. Obtiene ID de 'LP_PENDIENTE' en 'catalogos'<br/>4. Inserta registro en 'lost_password'<br/>5. Despacha ResetPasswordMail vía SMTP"]
CLICK["[Usuario hace clic en el enlace del correo: /reset-password/{token}]"]
VALIDATE{"Valida simultáneamente:<br/>* Existencia del token en 'lost_password'<br/>* cat_status == LP_PENDIENTE<br/>* created_at < 60 minutos"}
SUCCESS["1. Actualiza password en 'usuarios' (Bcrypt)<br/>2. Actualiza cat_status a LP_USADO<br/>3. Invalida token para futuros accesos"]
FAIL["Retorna error amigable<br/>y audita intento en Mongo"]
REQ --> STEPS1
STEPS1 --> CLICK
CLICK --> VALIDATE
VALIDATE -->|"Válido"| SUCCESS
VALIDATE -->|"Inválido / Expirado"| FAIL
3. Notificación Desacoplada (ResetPasswordMail)
El correo no se renderiza dentro del controlador; utiliza clases Mailable con soporte para encolamiento asíncrono en Redis:
// app/Mail/ResetPasswordMail.php
class ResetPasswordMail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(public string $token, public string $nombres) {}
public function build()
{
return $this->subject('Recuperación de Contraseña')
->view('emails.reset_password');
}
}