portal/controller/account.go

146 lines
4.3 KiB
Go
Raw Normal View History

package controller
2023-04-29 17:38:21 +00:00
import (
"crypto/ed25519"
"encoding/hex"
2023-04-29 17:38:21 +00:00
"errors"
2023-06-07 03:16:34 +00:00
"fmt"
2023-04-29 17:38:21 +00:00
"git.lumeweb.com/LumeWeb/portal/db"
"git.lumeweb.com/LumeWeb/portal/logger"
2023-04-29 17:38:21 +00:00
"git.lumeweb.com/LumeWeb/portal/model"
2023-06-07 03:16:34 +00:00
"github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
2023-04-29 17:38:21 +00:00
"github.com/kataras/iris/v12"
2023-05-19 13:04:47 +00:00
"go.uber.org/zap"
2023-04-29 17:38:21 +00:00
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"strings"
2023-04-29 17:38:21 +00:00
)
type AccountController struct {
2023-04-30 07:29:24 +00:00
Ctx iris.Context
2023-04-29 17:38:21 +00:00
}
type RegisterRequest struct {
2023-06-07 03:16:34 +00:00
Email string `json:"email"`
2023-04-29 17:38:21 +00:00
Password string `json:"password"`
Pubkey string `json:"pubkey"`
2023-04-29 17:38:21 +00:00
}
func CheckPubkeyValidator(value interface{}) error {
2023-06-07 03:16:34 +00:00
p, _ := value.(string)
pubkeyBytes, err := hex.DecodeString(p)
if err != nil {
return err
}
2023-06-07 03:16:34 +00:00
if len(pubkeyBytes) != ed25519.PublicKeySize {
return errors.New(fmt.Sprintf("pubkey must be %d bytes in hexadecimal format", ed25519.PublicKeySize))
}
2023-06-07 03:16:34 +00:00
return nil
}
2023-06-07 03:16:34 +00:00
func (r RegisterRequest) Validate() error {
return validation.ValidateStruct(&r,
validation.Field(&r.Email, validation.Required, is.EmailFormat),
validation.Field(&r.Pubkey, validation.When(len(r.Password) == 0, validation.Required, validation.By(CheckPubkeyValidator))),
2023-06-07 03:16:34 +00:00
validation.Field(&r.Password, validation.When(len(r.Pubkey) == 0, validation.Required)),
)
}
2023-04-29 17:38:21 +00:00
2023-06-07 03:16:34 +00:00
func hashPassword(password string) (string, error) {
2023-04-29 17:38:21 +00:00
// Generate a new bcrypt hash from the provided password.
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
logger.Get().Error("failed to hash password", zap.Error(err))
2023-04-29 17:38:21 +00:00
return "", err
}
// Convert the hashed password to a string and return it.
return string(hashedPassword), nil
}
func (a *AccountController) PostRegister() {
2023-04-29 17:38:21 +00:00
var r RegisterRequest
2023-06-07 12:49:07 +00:00
if !tryParseRequest(r, a.Ctx) {
2023-04-29 17:38:21 +00:00
return
}
// Check if an account with the same email address already exists.
existingAccount := model.Account{}
err := db.Get().Where("email = ?", r.Email).First(&existingAccount).Error
2023-04-29 17:38:21 +00:00
if err == nil {
logger.Get().Debug("account with email already exists", zap.Error(err), zap.String("email", r.Email))
2023-04-29 17:38:21 +00:00
// An account with the same email address already exists.
// Return an error response to the client.
2023-04-30 07:29:24 +00:00
a.Ctx.StopWithError(iris.StatusConflict, errors.New("an account with this email address already exists"))
2023-04-29 17:38:21 +00:00
return
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
logger.Get().Error("error querying accounts", zap.Error(err), zap.String("email", r.Email))
2023-04-29 17:38:21 +00:00
// An unexpected error occurred while querying the database.
// Return an error response to the client.
2023-04-30 07:29:24 +00:00
a.Ctx.StopWithError(iris.StatusInternalServerError, err)
2023-04-29 17:38:21 +00:00
return
}
2023-06-07 12:49:14 +00:00
if len(r.Pubkey) > 0 {
r.Pubkey = strings.ToLower(r.Pubkey)
var count int64
err := db.Get().Model(&model.Key{}).Where("pubkey = ?", r.Pubkey).Count(&count).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
logger.Get().Error("error querying accounts", zap.Error(err), zap.String("pubkey", r.Pubkey))
a.Ctx.StopWithError(iris.StatusInternalServerError, err)
}
if count > 0 {
logger.Get().Debug("account with pubkey already exists", zap.Error(err), zap.String("pubkey", r.Pubkey))
// An account with the same pubkey already exists.
// Return an error response to the client.
a.Ctx.StopWithError(iris.StatusConflict, errors.New("an account with this pubkey already exists"))
return
}
}
2023-04-29 17:38:21 +00:00
// Create a new Account model with the provided email and hashed password.
account := model.Account{
Email: r.Email,
}
// Hash the password before saving it to the database.
if len(r.Password) > 0 {
hashedPassword, err := hashPassword(r.Password)
if err != nil {
a.Ctx.StopWithError(iris.StatusInternalServerError, err)
return
}
account.Password = &hashedPassword
2023-04-29 17:38:21 +00:00
}
err = db.Get().Transaction(func(tx *gorm.DB) error {
// do some database operations in the transaction (use 'tx' from this point, not 'db')
if err := tx.Create(&account).Error; err != nil {
return err
}
if len(r.Pubkey) > 0 {
if err := tx.Create(&model.Key{Account: account, Pubkey: strings.ToLower(r.Pubkey)}).Error; err != nil {
return err
}
}
// return nil will commit the whole transaction
return nil
})
2023-04-29 17:38:21 +00:00
if err != nil {
logger.Get().Error("failed to create account", zap.Error(err))
2023-04-30 07:29:24 +00:00
a.Ctx.StopWithError(iris.StatusInternalServerError, err)
2023-04-29 17:38:21 +00:00
return
}
// Return a success response to the client.
2023-04-30 07:29:24 +00:00
a.Ctx.StatusCode(iris.StatusCreated)
2023-04-29 17:38:21 +00:00
}