package controllers import ( "crypto/rand" "errors" "fmt" "log" "math/big" "strings" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/app" "github.com/sujit-baniya/fiber-boilerplate/config" "github.com/sujit-baniya/fiber-boilerplate/pkg/auth" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" "github.com/sujit-baniya/fiber-boilerplate/pkg/services" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) // Función para renderizar la página de registro func RegisterGet(c *fiber.Ctx) error { data := getFlashData(c) data["title"] = "Register | " if err := c.Render("auth/register", data, "layouts/landing"); err != nil { //nolint:wsl panic(err.Error()) } return nil } // Función para manejar la solicitud de registro func RegisterPost(c *fiber.Ctx) error { register := c.Locals("register").(models.RegisterForm) user, err := register.Signup() if err != nil { return app.Http.Flash.WithError(c, fiber.Map{ "error": true, "message": "Error on register request: " + err.Error(), }).Redirect("/register") } store := app.Http.Session.Get(c) // obtener/crear nueva sesión store.Set("user_id", user.ID) // guardar en el almacenamiento _ = store.Save() go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario) return c.Redirect("/") } // Función para verificar el correo electrónico registrado func VerifyRegisteredEmail(c *fiber.Ctx) error { return c.Redirect("/app") } // Función para reenviar el correo de confirmación func ResendConfirmEmail(c *fiber.Ctx) error { user, _ := auth.User(c) go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario) return c.Redirect("/") } func ReenvioEmail(c *fiber.Ctx) error { usuarioID := c.Params("id") // Usa Params para obtener el parámetro de la ruta user, err := models.GetUserById(usuarioID) if err != nil { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "success": false, "message": "Usuario no encontrado: " + err.Error(), }) } // Reenvío del correo electrónico de forma asíncrona go services.SendConfirmationEmail(user.Email, services.GetPublicURL(), user.NombreUsuario) return c.Status(fiber.StatusOK).JSON(fiber.Map{ "success": true, "message": "Email reenviado correctamente", }) } // Función para manejar la solicitud de restablecimiento de contraseña func RequestPasswordResetPost(c *fiber.Ctx) error { email := strings.TrimSpace(c.FormValue("email")) if email == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "El correo electrónico es requerido.", }) } // 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": "Si el correo está registrado, recibirás instrucciones en los próximos minutos.", }) } // todo: Generar contrasenas aleatorias /do/generate-password func generatePassword(length int) (string, error) { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&" var password strings.Builder for i := 0; i < length; i++ { randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) if err != nil { return "", err } password.WriteByte(charset[randomIndex.Int64()]) } return password.String(), nil } func hashPassword(password string) (string, error) { hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", err } return string(hashed), nil } func updatePassword(nombreUsuario, plainPassword string) error { var user models.RegisterForm result := app.Http.Database.DB.Where("nombre_usuario = ?", nombreUsuario).First(&user) if errors.Is(result.Error, gorm.ErrRecordNotFound) { return errors.New("codigo no pertenece a ningun usuario") } if user.Password != "" { return errors.New("este usuario ya cuenta con una contraseña") } hashedPassword, err := hashPassword(plainPassword) if err != nil { return err } user.Password = hashedPassword if err := app.Http.Database.DB.Save(&user).Error; err != nil { return err } return nil } func GeneratePasswordPost(c *fiber.Ctx) error { // Parsear el cuerpo de la solicitud var body struct { NombreUsuario string `json:"nombre_usuario"` } if err := c.BodyParser(&body); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "Error al analizar el cuerpo de la solicitud", }) } if body.NombreUsuario == "" { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "message": "El codigo de usuario es requerido", }) } // Generar la contraseña password, err := generatePassword(12) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": "Error generando contraseña", }) } // Actualizar la contraseña en la base de datos if err := updatePassword(body.NombreUsuario, password); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "message": err.Error(), }) } // Responder con la contraseña generada return c.JSON(fiber.Map{ "success": true, "message": "Contrasena generada exitosamente", "password": password, }) } // Función para renderizar la página de restablecimiento de contraseña func PasswordReset(c *fiber.Ctx) error { token := c.Query("t") if err := c.Render("auth/password-reset", fiber.Map{ "Title": "Password Reset", "Token": token, }, "layouts/landing"); err != nil { panic(err.Error()) } return nil } func PasswordResetPost(c *fiber.Ctx) error { // 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) 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") } user, err := models.GetUserByEmail(email) if err != nil || user == nil { 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{} hashedPassword, err := hash.Create(password) if err != nil { return c.Render("auth/password-reset", fiber.Map{ "Token": c.Params("token"), "error": "Error al procesar la contraseña. Intenta de nuevo.", }, "layouts/landing") } 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") } return c.Redirect("/login") } // Función para renderizar la página de solicitud de restablecimiento de contraseña func RequestPasswordReset(c *fiber.Ctx) error { data := getFlashData(c) data["title"] = "Password Reset | " if err := c.Render("auth/request-password-reset", data, "layouts/landing"); err != nil { //nolint:wsl panic(err.Error()) } return nil } func TokenVencido(c *fiber.Ctx) error { // Renderiza la vista con el layout layouts/main if err := c.Render("token_vencido", nil, "layouts/landing"); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "message": "Error rendering template", "error": err.Error(), }) } return nil }