hermes/backend/api/controllers/v1/users/create.go
imterah 538c5b6c51
All checks were successful
Release code / build (push) Successful in 11m41s
chore: Adds "day-one"/v2.0.1 bug fixes.
This fixes database error reporting, as well as majorly fixes users
not being able to authenticate to the API if you used the automated
migration setup, as the password would remain in hex encoding.

We now decode the hexadecimal and then change it to the far more
compact base64 encoding before adding it to the database. This should
fix login, and not cause 500 Interal Server Errors anymore.

Sorry folks!
2024-12-27 00:10:13 -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.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),
})
}