package api import ( "errors" "net/http" "os" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" "hightube/internal/db" "hightube/internal/model" "hightube/internal/utils" ) type RegisterRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } type LoginRequest struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } type ChangePasswordRequest struct { OldPassword string `json:"old_password" binding:"required"` NewPassword string `json:"new_password" binding:"required"` } func Register(c *gin.Context) { var req RegisterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } req.Username = strings.TrimSpace(req.Username) if strings.EqualFold(req.Username, bootstrapAdminUsername()) { c.JSON(http.StatusForbidden, gin.H{"error": "This username is reserved"}) return } // Check if user exists if _, err := db.LoadUserByUsername(req.Username); err == nil { c.JSON(http.StatusConflict, gin.H{"error": "Username already exists"}) return } else if !errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to validate username"}) return } // Hash password hashedPassword, err := utils.HashPassword(req.Password) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"}) return } // Create user user := model.User{ Username: req.Username, Password: hashedPassword, Role: "user", Enabled: true, } room := model.Room{ Title: user.Username + "'s Live Room", StreamKey: utils.GenerateStreamKey(), IsActive: false, } if err := db.CreateUserAndRoom(&user, &room); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"}) return } c.JSON(http.StatusCreated, gin.H{"message": "User registered successfully", "user_id": user.ID}) } func Login(c *gin.Context) { var req LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } req.Username = strings.TrimSpace(req.Username) user, err := db.LoadUserByUsername(req.Username) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"}) return } if !utils.CheckPasswordHash(req.Password, user.Password) { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid username or password"}) return } if !user.Enabled { c.JSON(http.StatusForbidden, gin.H{"error": "Account is disabled"}) return } token, err := utils.GenerateToken(user.ID, user.Username, user.Role) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"}) return } c.JSON(http.StatusOK, gin.H{ "token": token, "username": user.Username, "role": user.Role, "enabled": user.Enabled, }) } func ChangePassword(c *gin.Context) { userID, _ := c.Get("user_id") var req ChangePasswordRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } user, err := db.LoadUserByID(userID.(uint)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) return } // Verify old password if !utils.CheckPasswordHash(req.OldPassword, user.Password) { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid old password"}) return } // Hash new password hashedPassword, err := utils.HashPassword(req.NewPassword) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"}) return } // Update user if err := db.UpdateUserPassword(user.ID, hashedPassword); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update password"}) return } c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"}) } func bootstrapAdminUsername() string { adminUsername := strings.TrimSpace(os.Getenv("HIGHTUBE_ADMIN_USER")) if adminUsername == "" { return "admin" } return adminUsername }