package handlers import ( "fmt" "log" "net/http" "strconv" "time" "github.com/gin-gonic/gin" "github.com/starfleetcptn/gomft/components" "github.com/starfleetcptn/gomft/internal/db" "golang.org/x/crypto/bcrypt" ) // HandleUsers handles the GET /admin/users route func (h *Handlers) HandleUsers(c *gin.Context) { var users []db.User if err := h.DB.Find(&users).Error; err != nil { c.String(http.StatusInternalServerError, "Failed to retrieve users") return } data := components.UsersData{ Users: users, } components.Users(components.CreateTemplateContext(c), data).Render(c, c.Writer) } // HandleNewUser handles the GET /admin/users/new route func (h *Handlers) HandleNewUser(c *gin.Context) { data := components.UserFormData{ IsNew: true, ErrorMessage: "", } components.UserForm(components.CreateTemplateContext(c), data).Render(c, c.Writer) } // HandleCreateUser handles the POST /admin/users/new route func (h *Handlers) HandleCreateUser(c *gin.Context) { email := c.PostForm("email") password := c.PostForm("password") isAdmin := c.PostForm("is_admin") == "on" // Check if email already exists var existingUser db.User if err := h.DB.Where("email = ?", email).First(&existingUser).Error; err == nil { c.String(http.StatusBadRequest, "Email already exists") return } // Hash the password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { c.String(http.StatusInternalServerError, "Failed to hash password") return } // Create the user user := db.User{ Email: email, PasswordHash: string(hashedPassword), IsAdmin: isAdmin, LastPasswordChange: time.Now(), } if err := h.DB.Create(&user).Error; err != nil { c.String(http.StatusInternalServerError, "Failed to create user") return } c.Redirect(http.StatusSeeOther, "/admin/users") } // HandleDeleteUser handles the POST /admin/users/delete route func (h *Handlers) HandleDeleteUser(c *gin.Context) { userID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.String(http.StatusBadRequest, "Invalid user ID") return } // Don't allow deleting the current user currentUserID := c.GetUint("userID") if uint(userID) == currentUserID { c.String(http.StatusBadRequest, "Cannot delete your own account") return } // Delete the user if err := h.DB.Delete(&db.User{}, userID).Error; err != nil { c.String(http.StatusInternalServerError, "Failed to delete user") return } c.Redirect(http.StatusSeeOther, "/admin/users") } // HandleRegisterPage handles the GET /register route func (h *Handlers) HandleRegisterPage(c *gin.Context) { // Check if any users exist var count int64 h.DB.Model(&db.User{}).Count(&count) // If users exist, don't allow registration if count > 0 { c.Redirect(http.StatusSeeOther, "/") return } components.Register(c.Request.Context(), "").Render(c, c.Writer) } // HandleRegister handles the POST /register route func (h *Handlers) HandleRegister(c *gin.Context) { // Check if any users exist var count int64 h.DB.Model(&db.User{}).Count(&count) // If users exist, don't allow registration if count > 0 { c.Redirect(http.StatusSeeOther, "/") return } email := c.PostForm("email") password := c.PostForm("password") // Hash the password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { c.String(http.StatusInternalServerError, "Failed to hash password") return } // Create the admin user user := db.User{ Email: email, PasswordHash: string(hashedPassword), IsAdmin: true, LastPasswordChange: time.Now(), } if err := h.DB.Create(&user).Error; err != nil { c.String(http.StatusInternalServerError, "Failed to create user") return } // Generate JWT token, err := h.GenerateJWT(user.ID, user.Email, user.IsAdmin) if err != nil { c.String(http.StatusInternalServerError, "Failed to generate token") return } // Set cookie c.SetCookie("jwt", token, 60*60*24, "/", "", false, true) c.Redirect(http.StatusSeeOther, "/dashboard") } // HandleEditUser handles the edit user page request func (h *Handlers) HandleEditUser(c *gin.Context) { // Only admin users can access this page isAdmin, exists := c.Get("isAdmin") if !exists || isAdmin != true { c.Redirect(http.StatusFound, "/dashboard") return } id := c.Param("id") var user db.User if err := h.DB.First(&user, id).Error; err != nil { c.Redirect(http.StatusFound, "/users") return } data := components.UserFormData{ IsNew: false, ErrorMessage: "", } components.UserForm(c.Request.Context(), data).Render(c, c.Writer) } // HandleUpdateUser handles the update user form submission func (h *Handlers) HandleUpdateUser(c *gin.Context) { // Only admin users can update users isAdmin, exists := c.Get("isAdmin") if !exists || isAdmin != true { c.String(http.StatusForbidden, "Only administrators can update users") return } id := c.Param("id") var user db.User if err := h.DB.First(&user, id).Error; err != nil { log.Printf("Error finding user: %v", err) c.String(http.StatusNotFound, "User not found") return } // Get the old user values for comparison oldUser := user // Bind form data to user if err := c.ShouldBind(&user); err != nil { log.Printf("Error binding user form: %v", err) c.String(http.StatusBadRequest, fmt.Sprintf("Invalid form data: %v", err)) return } // Check if email already exists for a different user var existingUser db.User if user.Email != oldUser.Email { if err := h.DB.Where("email = ? AND id != ?", user.Email, user.ID).First(&existingUser).Error; err == nil { c.String(http.StatusBadRequest, "Email already in use") return } } // Get password from form password := c.PostForm("password") // Only update password if provided if password != "" { // Validate password complexity if !h.validatePasswordComplexity(password) { c.String(http.StatusBadRequest, "Password does not meet complexity requirements") return } // Hash password hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { log.Printf("Error hashing password: %v", err) c.String(http.StatusInternalServerError, "Failed to hash password") return } user.PasswordHash = string(hashedPassword) user.LastPasswordChange = time.Now() } else { // Preserve the old password if not updating user.PasswordHash = oldUser.PasswordHash user.LastPasswordChange = oldUser.LastPasswordChange } // Preserve fields that shouldn't be updated user.CreatedAt = oldUser.CreatedAt user.FailedLoginAttempts = oldUser.FailedLoginAttempts user.AccountLocked = oldUser.AccountLocked user.LockoutUntil = oldUser.LockoutUntil // Update admin status user.IsAdmin = c.PostForm("is_admin") == "on" if err := h.DB.Save(&user).Error; err != nil { log.Printf("Error updating user: %v", err) c.String(http.StatusInternalServerError, fmt.Sprintf("Failed to update user: %v", err)) return } c.Redirect(http.StatusFound, "/users") } // HandleUnlockUser handles the unlock user request func (h *Handlers) HandleUnlockUser(c *gin.Context) { // Only admin users can unlock users isAdmin, exists := c.Get("isAdmin") if !exists || isAdmin != true { c.JSON(http.StatusForbidden, gin.H{"error": "Only administrators can unlock users"}) return } id := c.Param("id") var user db.User if err := h.DB.First(&user, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } // Unlock user user.AccountLocked = false user.FailedLoginAttempts = 0 user.LockoutUntil = nil if err := h.DB.Save(&user).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to unlock user: %v", err)}) return } c.JSON(http.StatusOK, gin.H{"message": "User unlocked successfully"}) } // validatePasswordComplexity validates that a password meets complexity requirements func (h *Handlers) validatePasswordComplexity(password string) bool { // Password must be at least 8 characters long if len(password) < 8 { return false } // Check for at least one uppercase letter, one lowercase letter, and one number hasUpper := false hasLower := false hasNumber := false for _, char := range password { if char >= 'A' && char <= 'Z' { hasUpper = true } else if char >= 'a' && char <= 'z' { hasLower = true } else if char >= '0' && char <= '9' { hasNumber = true } } return hasUpper && hasLower && hasNumber }