hermes/backend/api/controllers/v1/users/create.go
2024-12-22 13:58:18 -05:00

163 lines
3.7 KiB
Go

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),
})
}