From 107178aa97cc300209f59009d88eecfa84253784 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:57:26 -0500 Subject: [PATCH] =?UTF-8?q?feat(users):=20reemplazar=20confirmaci=C3=B3n?= =?UTF-8?q?=20por=20env=C3=ADo=20de=20credenciales=20con=20link=20de=20res?= =?UTF-8?q?et?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- pkg/services/mail_service.go | 34 +++++++++++++++++++++ resources/views/users.html | 45 +++++++++++++--------------- rest/controllers/users_controller.go | 19 ++++++++++-- rest/routes/user.go | 1 + 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/pkg/services/mail_service.go b/pkg/services/mail_service.go index df66efd..83ffc5f 100755 --- a/pkg/services/mail_service.go +++ b/pkg/services/mail_service.go @@ -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(` + +
+
+
U
+

Bienvenido a U-site

+

Panel de administración

+
+

Hola %s,

+

Tu cuenta ha sido creada. Estos son tus datos de acceso:

+
+

Usuario: %s

+

Acceso: %s

+
+

Establece tu contraseña haciendo clic en el siguiente botón:

+ Establecer contraseña +

El enlace es válido por 1 hora.

+

© U-site — Todos los derechos reservados

+
+`, 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) diff --git a/resources/views/users.html b/resources/views/users.html index 184406c..ff79bdf 100755 --- a/resources/views/users.html +++ b/resources/views/users.html @@ -82,16 +82,15 @@ - + + @@ -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; + } } })); }); diff --git a/rest/controllers/users_controller.go b/rest/controllers/users_controller.go index deec73c..9c66577 100755 --- a/rest/controllers/users_controller.go +++ b/rest/controllers/users_controller.go @@ -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}) +} diff --git a/rest/routes/user.go b/rest/routes/user.go index 02cd0b1..11113f1 100755 --- a/rest/routes/user.go +++ b/rest/routes/user.go @@ -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