fix(auth): flujo de recuperación de contraseña usa email en vez de usuario

- login.html: corregir link roto /password/reset → /request-password-reset
- request-password-reset.html: reescribir — pide email, AJAX, diseño de la app
- RequestPasswordResetPost: buscar por email (GetUserByEmail), no revela si
  el correo existe o no (misma respuesta siempre — seguridad)
- password-reset.html: reescribir — eliminar campo nombre_usuario redundante,
  validación de contraseñas en cliente, diseño consistente con el resto del app
- PasswordResetPost: usar c.Locals("email") del token (ya validado por
  middleware) en vez de nombre_usuario del form
- password_reset.go middleware: ampliar expiración de token de 5 min a 1 hora

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-06 22:17:38 -05:00
co-authored by Claude Sonnet 4.6
parent 37648724f8
commit e937676160
5 changed files with 261 additions and 104 deletions
+1 -1
View File
@@ -71,7 +71,7 @@
<!-- Forgot password link -->
<div class="flex justify-end">
<a href="/password/reset" class="text-xs font-medium hover:underline" style="color:#8eb02f">
<a href="/request-password-reset" class="text-xs font-medium hover:underline" style="color:#8eb02f">
¿Olvidaste tu contraseña?
</a>
</div>
+112 -48
View File
@@ -1,50 +1,114 @@
<div class="min-h-screen flex items-center justify-center bg-gray-100">
<div class="w-full max-w-md bg-white rounded-lg shadow-lg p-6">
<h1 class="text-2xl font-bold text-center text-gray-700 mb-6">¡Restablece tu contraseña!</h1>
<form action="/do/password-reset/{{.Token}}" method="POST" class="space-y-4">
<div>
<label for="email" class="block text-sm font-medium text-gray-700">Codigo de Usuario</label>
<input
id="email"
type="text"
placeholder="Ingresa tu codigo de usuario"
name="nombre_usuario"
required
class="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500 focus:border-blue-500"
>
<div class="w-full max-w-sm" x-data="{showPass: false, showConfirm: false, loading: false, error: ''}">
<!-- Logo -->
<div class="mb-8 text-center">
<div class="inline-flex items-center justify-center w-14 h-14 rounded-2xl mb-4" style="background-color:#1e293b">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"/>
</svg>
</div>
<div>
<label for="password" class="block text-sm font-medium text-gray-700">Nueva contraseña</label>
<input
id="password"
type="password"
placeholder="Ingresa tu nueva contraseña"
name="password"
required
class="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500 focus:border-blue-500"
>
</div>
<div>
<label for="c_password" class="block text-sm font-medium text-gray-700">Confirma tu contraseña</label>
<input
id="c_password"
type="password"
placeholder="Confirma tu nueva contraseña"
name="c_password"
required
class="w-full mt-1 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring focus:ring-blue-500 focus:border-blue-500"
>
</div>
<button
type="submit"
class="w-full bg-blue-500 text-white font-bold py-2 px-4 rounded-md hover:bg-blue-600 focus:outline-none focus:ring focus:ring-blue-300"
>
Restablecer contraseña
</button>
</form>
<h1 class="text-2xl font-bold text-slate-800">Nueva contraseña</h1>
<p class="text-sm text-slate-500 mt-1">Elige una contraseña segura para tu cuenta</p>
</div>
</div>
<!-- Error -->
{{if .error}}
<div class="mb-5 flex items-start gap-3 rounded-xl px-4 py-3" style="background:rgba(239,68,68,0.08); border:1px solid rgba(239,68,68,0.3)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 flex-shrink-0 mt-0.5" style="color:#ef4444" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
</svg>
<span class="text-sm" style="color:#ef4444">{{.error}}</span>
</div>
{{end}}
<form action="/do/password-reset/{{.Token}}" method="POST" class="space-y-4"
@submit.prevent="
const p = $el.querySelector('[name=password]').value;
const c = $el.querySelector('[name=c_password]').value;
if (p.length < 6) { error = 'La contraseña debe tener al menos 6 caracteres.'; return; }
if (p !== c) { error = 'Las contraseñas no coinciden.'; return; }
error = ''; loading = true; $el.submit();
">
<!-- Nueva contraseña -->
<div>
<label class="block text-sm font-medium text-slate-700 mb-1.5" for="password">Nueva contraseña</label>
<div class="relative">
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"/>
</svg>
</div>
<input :type="showPass ? 'text' : 'password'" id="password" name="password"
placeholder="••••••••" required autocomplete="new-password" minlength="6"
class="w-full pl-10 pr-11 py-2.5 border border-slate-200 rounded-xl text-sm outline-none bg-white text-slate-800 focus:border-[#8eb02f] transition-all"
style="box-shadow:none"
onfocus="this.style.boxShadow='0 0 0 3px rgba(142,176,47,0.15)'; this.style.borderColor='#8eb02f'"
onblur="this.style.boxShadow='none'; this.style.borderColor='#e2e8f0'">
<button type="button" @click="showPass = !showPass"
class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors">
<svg x-show="!showPass" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<svg x-show="showPass" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/>
</svg>
</button>
</div>
</div>
<!-- Confirmar contraseña -->
<div>
<label class="block text-sm font-medium text-slate-700 mb-1.5" for="c_password">Confirmar contraseña</label>
<div class="relative">
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
</div>
<input :type="showConfirm ? 'text' : 'password'" id="c_password" name="c_password"
placeholder="••••••••" required autocomplete="new-password"
class="w-full pl-10 pr-11 py-2.5 border border-slate-200 rounded-xl text-sm outline-none bg-white text-slate-800 focus:border-[#8eb02f] transition-all"
style="box-shadow:none"
onfocus="this.style.boxShadow='0 0 0 3px rgba(142,176,47,0.15)'; this.style.borderColor='#8eb02f'"
onblur="this.style.boxShadow='none'; this.style.borderColor='#e2e8f0'">
<button type="button" @click="showConfirm = !showConfirm"
class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 transition-colors">
<svg x-show="!showConfirm" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<svg x-show="showConfirm" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"/>
</svg>
</button>
</div>
</div>
<!-- Error Alpine -->
<div x-show="error" class="flex items-start gap-3 rounded-xl px-4 py-3" style="background:rgba(239,68,68,0.08); border:1px solid rgba(239,68,68,0.3)">
<span class="text-sm" style="color:#ef4444" x-text="error"></span>
</div>
<button type="submit" :disabled="loading"
class="w-full py-2.5 rounded-xl text-sm font-semibold text-white transition-all mt-2"
:class="loading ? 'opacity-70 cursor-not-allowed' : 'hover:opacity-90'"
style="background-color:#8eb02f">
<span x-show="!loading" class="flex items-center justify-center gap-2">
Guardar nueva contraseña
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/>
</svg>
</span>
<span x-show="loading" class="flex items-center justify-center gap-2">
<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
Guardando...
</span>
</button>
<div class="text-center pt-1">
<a href="/login" class="text-xs text-slate-500 hover:underline">← Volver al inicio de sesión</a>
</div>
</form>
</div>
@@ -1,11 +1,100 @@
<h1 class="h1">Reset my password!</h1>
<form action="/do/reset-password" method="POST">
<div class="container">
<label for="nombre_usuario"><b>Codigo de usuario</b></label>
<input id="nombre_usuario" type="text" placeholder="Ingrese su usuario" name="nombre_usuario" required>
<button type="submit">Let me reset password!!</button>
<div class="w-full max-w-sm" x-data="{
email: '',
loading: false,
sent: false,
error: '',
async submit() {
this.error = '';
if (!this.email.trim()) { this.error = 'Ingresa tu correo electrónico.'; return; }
this.loading = true;
try {
const res = await fetch('/do/reset-password', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'email=' + encodeURIComponent(this.email),
});
const d = await res.json();
if (d.success) { this.sent = true; }
else { this.error = d.message || 'No se pudo procesar la solicitud.'; }
} catch (e) {
this.error = 'Error de conexión. Intenta de nuevo.';
}
this.loading = false;
}
}">
<!-- Logo -->
<div class="mb-8 text-center">
<div class="inline-flex items-center justify-center w-14 h-14 rounded-2xl mb-4" style="background-color:#1e293b">
<svg xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 10-2.636 6.364M16.5 12V8.25"/>
</svg>
</div>
<h1 class="text-2xl font-bold text-slate-800">Recuperar contraseña</h1>
<p class="text-sm text-slate-500 mt-1">Te enviaremos un enlace a tu correo</p>
</div>
</form>
{{.Login.Email}}
<!-- Enviado -->
<div x-show="sent" class="text-center space-y-4">
<div class="flex items-center justify-center w-16 h-16 rounded-full bg-green-50 border-2 border-green-200 mx-auto">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
</svg>
</div>
<p class="text-slate-700 font-medium">¡Revisa tu correo!</p>
<p class="text-sm text-slate-500">Si el correo está registrado, recibirás instrucciones para restablecer tu contraseña en los próximos minutos.</p>
<a href="/login" class="inline-block mt-4 text-sm font-medium hover:underline" style="color:#8eb02f">Volver al inicio de sesión</a>
</div>
<!-- Formulario -->
<div x-show="!sent">
<!-- Error -->
<div x-show="error" class="mb-5 flex items-start gap-3 rounded-xl px-4 py-3" style="background:rgba(239,68,68,0.08); border:1px solid rgba(239,68,68,0.3)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 flex-shrink-0 mt-0.5" style="color:#ef4444" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
</svg>
<span class="text-sm" style="color:#ef4444" x-text="error"></span>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-slate-700 mb-1.5" for="email">Correo electrónico</label>
<div class="relative">
<div class="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75"/>
</svg>
</div>
<input type="email" id="email" x-model="email" @keydown.enter="submit()"
placeholder="tu@correo.com" autocomplete="email"
class="w-full pl-10 pr-4 py-2.5 border border-slate-200 rounded-xl text-sm outline-none bg-white text-slate-800 focus:border-[#8eb02f] transition-all"
style="box-shadow:none"
onfocus="this.style.boxShadow='0 0 0 3px rgba(142,176,47,0.15)'; this.style.borderColor='#8eb02f'"
onblur="this.style.boxShadow='none'; this.style.borderColor='#e2e8f0'">
</div>
</div>
<button @click="submit()" :disabled="loading"
class="w-full py-2.5 rounded-xl text-sm font-semibold text-white transition-all mt-2"
:class="loading ? 'opacity-70 cursor-not-allowed' : 'hover:opacity-90'"
style="background-color:#8eb02f">
<span x-show="!loading" class="flex items-center justify-center gap-2">
Enviar enlace de recuperación
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"/>
</svg>
</span>
<span x-show="loading" class="flex items-center justify-center gap-2">
<svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
</svg>
Enviando...
</span>
</button>
<div class="text-center pt-1">
<a href="/login" class="text-xs text-slate-500 hover:underline">← Volver al inicio de sesión</a>
</div>
</div>
</div>
</div>
+49 -45
View File
@@ -80,25 +80,26 @@ func ReenvioEmail(c *fiber.Ctx) error {
// Función para manejar la solicitud de restablecimiento de contraseña
func RequestPasswordResetPost(c *fiber.Ctx) error {
usuario := c.FormValue("nombre_usuario")
fmt.Println("Usuario recibido:", usuario)
user, err := models.GetUserByUsuario(usuario)
if err != nil || user == nil {
email := strings.TrimSpace(c.FormValue("email"))
if email == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"success": false,
"message": "Usuario inactivo o no existente",
"message": "El correo electrónico es requerido.",
})
}
usuario_email := user.Email
log.Println("Sending password reset email to:", usuario_email)
go services.SendPasswordResetEmail(usuario_email, services.GetPublicURL())
// Intentar enviar el correo solo si el usuario existe — mismo mensaje en ambos casos por seguridad
user, err := models.GetUserByEmail(email)
if err == nil && user != nil && user.Email != "" {
log.Println("[Reset] Enviando enlace de recuperación a:", email)
go services.SendPasswordResetEmail(user.Email, services.GetPublicURL())
} else {
log.Println("[Reset] Correo no encontrado (no se revela al cliente):", email)
}
return c.JSON(fiber.Map{
"success": true,
"message": "We've sent an email to reset your password to your registered email address",
"message": "Si el correo está registrado, recibirás instrucciones en los próximos minutos.",
})
}
@@ -207,53 +208,56 @@ func PasswordReset(c *fiber.Ctx) error {
}
func PasswordResetPost(c *fiber.Ctx) error {
// Recuperar el nombre de usuario y la nueva contraseña del formulario
nombreUsuario := c.FormValue("nombre_usuario")
password := c.FormValue("password")
// El email fue validado y extraído del token por el middleware ValidatePasswordResetPost
emailLocal := c.Locals("email")
if emailLocal == nil {
return c.Status(fiber.StatusBadRequest).Render("auth/password-reset", fiber.Map{
"Token": c.Params("token"),
"error": "Token inválido. Solicita un nuevo enlace.",
}, "layouts/landing")
}
email := fmt.Sprintf("%v", emailLocal)
// Validar que ambos campos estén presentes
if nombreUsuario == "" || password == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Los campos nombre_usuario y password son obligatorios.",
"error": true,
})
password := c.FormValue("password")
cPassword := c.FormValue("c_password")
if password == "" || len(password) < 6 {
return c.Render("auth/password-reset", fiber.Map{
"Token": c.Params("token"),
"error": "La contraseña debe tener al menos 6 caracteres.",
}, "layouts/landing")
}
if password != cPassword {
return c.Render("auth/password-reset", fiber.Map{
"Token": c.Params("token"),
"error": "Las contraseñas no coinciden.",
}, "layouts/landing")
}
// Imprimir los datos recibidos (opcional para depuración)
fmt.Printf("Datos recibidos: nombre_usuario=%s, password=%s\n", nombreUsuario, password)
// Buscar al usuario en la base de datos por nombre de usuario
user, err := models.GetUserByUsuario(nombreUsuario)
user, err := models.GetUserByEmail(email)
if err != nil || user == nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"message": "Usuario no encontrado.",
"error": true,
})
return c.Render("auth/password-reset", fiber.Map{
"Token": c.Params("token"),
"error": "No se encontró una cuenta asociada a este enlace.",
}, "layouts/landing")
}
hash := config.Hash{}
// Encripta la nueva contraseña
hashedPassword, err := hash.Create(password)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al encriptar la contraseña",
"error": true,
})
return c.Render("auth/password-reset", fiber.Map{
"Token": c.Params("token"),
"error": "Error al procesar la contraseña. Intenta de nuevo.",
}, "layouts/landing")
}
// Asigna la contraseña encriptada al modelo
password = hashedPassword
// Actualizar la contraseña del usuario
err = models.UpdateUserPassword(user.ID, password)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Error al actualizar la contraseña.",
"error": true,
})
if err := models.UpdateUserPassword(user.ID, hashedPassword); err != nil {
return c.Render("auth/password-reset", fiber.Map{
"Token": c.Params("token"),
"error": "No se pudo actualizar la contraseña. Intenta de nuevo.",
}, "layouts/landing")
}
// Redirigir al usuario al formulario de inicio de sesión después del éxito
return c.Redirect("/login")
}
+1 -1
View File
@@ -47,7 +47,7 @@ func _validatePasswordReset(c *fiber.Ctx, t string) error {
}
now := time.Now().Unix()
diff := now - tokenTS
if diff > (5 * 60) {
if diff > (60 * 60) {
return errors.New("Password Reset Token has expired!")
} else if diff < 0 {
return errors.New("Invalid Password Reset Token")