package users import ( "crypto/rand" "encoding/base64" "fmt" "net/http" "strings" "github.com/go-playground/validator/v10" "git.terah.dev/imterah/hermes/api/dbcore" "git.terah.dev/imterah/hermes/api/jwtcore" permissionHelper "git.terah.dev/imterah/hermes/api/permissions" "github.com/charmbracelet/log" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) type UserCreationRequest struct { Name string `validate:"required"` Email string `validate:"required"` Password string `validate:"required"` Username string `validate:"required"` // TODO: implement support ExistingUserToken string `json:"token"` IsBot bool } func CreateUser(c *gin.Context) { if !signupEnabled && !unsafeSignup { c.JSON(http.StatusForbidden, gin.H{ "error": "Signing up is not enabled at this time.", }) return } var req UserCreationRequest if err := c.BindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": fmt.Sprintf("Failed to parse body: %s", err.Error()), }) return } if err := validator.New().Struct(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": fmt.Sprintf("Failed to validate body: %s", err.Error()), }) return } var user *dbcore.User userRequest := dbcore.DB.Where("email = ? OR username = ?", req.Email, req.Username).Find(&user) if userRequest.Error != nil { log.Warnf("failed to find if user exists or not: %s", userRequest.Error) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to find if user exists", }) return } userExists := userRequest.RowsAffected > 0 if userExists { c.JSON(http.StatusBadRequest, gin.H{ "error": "User already exists", }) return } passwordHashed, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { log.Warnf("Failed to generate password for client upon signup: %s", err.Error()) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to generate password hash", }) return } permissions := []dbcore.Permission{} for _, permission := range permissionHelper.DefaultPermissionNodes { permissionEnabledState := false if unsafeSignup || strings.HasPrefix(permission, "routes.") || permission == "permissions.see" { permissionEnabledState = true } permissions = append(permissions, dbcore.Permission{ PermissionNode: permission, HasPermission: permissionEnabledState, }) } tokenRandomData := make([]byte, 80) if _, err := rand.Read(tokenRandomData); err != nil { log.Warnf("Failed to read random data to use as token: %s", err.Error()) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to generate refresh token", }) return } user = &dbcore.User{ Email: req.Email, Username: req.Username, Name: req.Name, IsBot: &req.IsBot, Password: base64.StdEncoding.EncodeToString(passwordHashed), Permissions: permissions, Tokens: []dbcore.Token{ { Token: base64.StdEncoding.EncodeToString(tokenRandomData), DisableExpiry: forceNoExpiryTokens, CreationIPAddr: c.ClientIP(), }, }, } if result := dbcore.DB.Create(&user); result.Error != nil { log.Warnf("Failed to create user: %s", result.Error.Error()) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to add user into database", }) return } jwt, err := jwtcore.Generate(user.ID) if err != nil { log.Warnf("Failed to generate JWT: %s", err.Error()) c.JSON(http.StatusInternalServerError, gin.H{ "error": "Failed to generate refresh token", }) return } c.JSON(http.StatusOK, gin.H{ "success": true, "token": jwt, "refreshToken": base64.StdEncoding.EncodeToString(tokenRandomData), }) }