feat(users): reemplazar confirmación por envío de credenciales con link de reset
- Eliminar envío automático de confirmación al crear usuario - Nuevo botón en cada fila: enviar credenciales (usuario + link establecer contraseña) - POST /app/users/:id/credenciales → SendUserCredencialesEmail - Email incluye username, link al login y link para establecer contraseña Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
bf3cbd3606
commit
107178aa97
@@ -50,6 +50,40 @@ func SendConfirmationEmail(email string, baseURL string, nombreUsuario string) {
|
||||
}
|
||||
}
|
||||
|
||||
// SendUserCredencialesEmail envía al usuario admin su nombre de usuario y un link para establecer contraseña.
|
||||
func SendUserCredencialesEmail(email, nombre, nombreUsuario string) {
|
||||
resetEmail := fmt.Sprintf("%s-reset-%d", email, time.Now().Unix())
|
||||
resetLink := GeneratePasswordResetURL(resetEmail, GetPublicURL())
|
||||
loginURL := absAppURL("/login")
|
||||
|
||||
htmlBody := fmt.Sprintf(`<!DOCTYPE html>
|
||||
<html><body style="font-family:Inter,sans-serif;background:#f1f5f9;padding:32px">
|
||||
<div style="max-width:480px;margin:0 auto;background:#fff;border-radius:16px;padding:32px;border:1px solid #e2e8f0">
|
||||
<div style="text-align:center;margin-bottom:24px">
|
||||
<div style="width:48px;height:48px;border-radius:50%%;background:#8eb02f;display:inline-flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:20px">U</div>
|
||||
<h2 style="margin:12px 0 4px;color:#1e293b">Bienvenido a U-site</h2>
|
||||
<p style="color:#64748b;font-size:14px;margin:0">Panel de administración</p>
|
||||
</div>
|
||||
<p style="color:#334155;font-size:15px">Hola <strong>%s</strong>,</p>
|
||||
<p style="color:#334155;font-size:14px">Tu cuenta ha sido creada. Estos son tus datos de acceso:</p>
|
||||
<div style="background:#f8fafc;border-radius:10px;padding:16px;margin:20px 0;border:1px solid #e2e8f0">
|
||||
<p style="margin:0 0 8px;font-size:13px;color:#64748b"><strong>Usuario:</strong> %s</p>
|
||||
<p style="margin:0;font-size:13px;color:#64748b"><strong>Acceso:</strong> <a href="%s" style="color:#8eb02f">%s</a></p>
|
||||
</div>
|
||||
<p style="color:#334155;font-size:14px">Establece tu contraseña haciendo clic en el siguiente botón:</p>
|
||||
<a href="%s" style="display:block;text-align:center;background:#8eb02f;color:#fff;padding:12px 24px;border-radius:10px;font-weight:600;text-decoration:none;font-size:15px;margin:20px 0">Establecer contraseña</a>
|
||||
<p style="color:#64748b;font-size:13px">El enlace es válido por <strong>1 hora</strong>.</p>
|
||||
<p style="font-size:12px;color:#94a3b8;margin-top:24px;text-align:center">© U-site — Todos los derechos reservados</p>
|
||||
</div>
|
||||
</body></html>`, nombre, nombreUsuario, loginURL, loginURL, resetLink)
|
||||
|
||||
if err := app.Http.Mail.Send(email, "Bienvenido a U-site — Tus credenciales de acceso", htmlBody); err != nil {
|
||||
log.Printf("[MAIL] Error enviando credenciales a %s: %v", email, err)
|
||||
} else {
|
||||
log.Printf("[MAIL] Credenciales enviadas a %s", email)
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateConfirmURL(nombreUsuario string, baseURL string) string {
|
||||
token := utils.Encrypt(nombreUsuario, app.Http.Server.Key)
|
||||
uri := fmt.Sprintf("%s/do/verify-email?t=%s", baseURL, token)
|
||||
|
||||
+20
-25
@@ -82,16 +82,15 @@
|
||||
<!-- Círculo verde -->
|
||||
|
||||
</template>
|
||||
<template x-if="!user.email_verified">
|
||||
<!-- Botón Enviar verificación -->
|
||||
<button @click="enviarVerificación(user.ID)" title="Reenviar validación">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" class="w-5 text-orange-400">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
<!-- Botón Enviar credenciales -->
|
||||
<button @click="enviarCredenciales(user.ID, user.email)" title="Enviar credenciales por email"
|
||||
x-show="user.email">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" class="w-5 text-blue-500">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M21.75 6.75v10.5a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25m19.5 0v.243a2.25 2.25 0 0 1-1.07 1.916l-7.5 4.615a2.25 2.25 0 0 1-2.36 0L3.32 8.91a2.25 2.25 0 0 1-1.07-1.916V6.75" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
<td class="py-3 text-xs px-4 border-b flex justify-between space-x-1">
|
||||
<!-- Botón de Ver -->
|
||||
@@ -547,22 +546,18 @@
|
||||
});
|
||||
},
|
||||
|
||||
enviarVerificación($id) {
|
||||
this.Id = $id;
|
||||
async enviarCredenciales($id, $email) {
|
||||
if (!confirm(`¿Enviar credenciales de acceso a ${$email}?`)) return;
|
||||
this.loading = true;
|
||||
fetch(`/app/verificacion/${this.Id}`, {
|
||||
method: 'GET',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
})
|
||||
.then(() => {
|
||||
alert('Se ha enviado un correo de verificación');
|
||||
this.closeModal();
|
||||
this.loadRegister();
|
||||
})
|
||||
.catch(error => console.error('Error:', error))
|
||||
.finally(() => {
|
||||
this.loading = false; // Ocultar el GIF de carga
|
||||
});
|
||||
try {
|
||||
const r = await fetch(`/app/users/${$id}/credenciales`, { method: 'POST' });
|
||||
const data = await r.json();
|
||||
alert(data.message || 'Credenciales enviadas');
|
||||
} catch (e) {
|
||||
alert('Error al enviar credenciales');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -486,9 +486,6 @@ func CreateUserGas(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// Envía un correo de confirmación
|
||||
go services.SendConfirmationEmail(m.Email, services.GetPublicURL(), m.NombreUsuario)
|
||||
|
||||
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
|
||||
"message": "Usuario creado con éxito",
|
||||
"error": false,
|
||||
@@ -555,3 +552,19 @@ func createPassword(c *fiber.Ctx, UserId uint) error {
|
||||
"error": false,
|
||||
})
|
||||
}
|
||||
|
||||
func EnviarCredencialesUsuario(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 32)
|
||||
if err != nil {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "ID inválido"})
|
||||
}
|
||||
user, err := models.FindUserByID(uint(id))
|
||||
if err != nil || user == nil {
|
||||
return c.Status(404).JSON(fiber.Map{"error": "Usuario no encontrado"})
|
||||
}
|
||||
if user.Email == "" {
|
||||
return c.Status(400).JSON(fiber.Map{"error": "El usuario no tiene email registrado"})
|
||||
}
|
||||
go services.SendUserCredencialesEmail(user.Email, user.Name, user.NombreUsuario)
|
||||
return c.JSON(fiber.Map{"ok": true, "message": "Credenciales enviadas a " + user.Email})
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ func UserRoutes(app fiber.Router) {
|
||||
protected.Put("/password/:id", controllers.UpdatePassword) // Actualizar
|
||||
protected.Delete("/users/:id", controllers.DeleteUser) // Eliminar
|
||||
protected.Get("/user/:id", controllers.GetUser) // Buscar un usuario
|
||||
protected.Post("/users/:id/credenciales", controllers.EnviarCredencialesUsuario) // Enviar credenciales
|
||||
|
||||
// Rutas de conexiones ssh
|
||||
protected.Get("/conexion_ssh", middlewares.MenuMiddleware, controllers.ConxSsh) // Renderizar la vista
|
||||
|
||||
Reference in New Issue
Block a user