feat: add apis for sending email verification, and verifying an email code
This commit is contained in:
parent
25ebb00765
commit
8965395fdf
|
@ -2,9 +2,12 @@ package account
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.lumeweb.com/LumeWeb/portal/mailer"
|
||||||
|
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
|
|
||||||
"git.lumeweb.com/LumeWeb/portal/config"
|
"git.lumeweb.com/LumeWeb/portal/config"
|
||||||
|
@ -24,6 +27,7 @@ type AccountServiceParams struct {
|
||||||
Db *gorm.DB
|
Db *gorm.DB
|
||||||
Config *config.Manager
|
Config *config.Manager
|
||||||
Identity ed25519.PrivateKey
|
Identity ed25519.PrivateKey
|
||||||
|
Mailer *mailer.Mailer
|
||||||
}
|
}
|
||||||
|
|
||||||
var Module = fx.Module("account",
|
var Module = fx.Module("account",
|
||||||
|
@ -36,10 +40,11 @@ type AccountServiceDefault struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
config *config.Manager
|
config *config.Manager
|
||||||
identity ed25519.PrivateKey
|
identity ed25519.PrivateKey
|
||||||
|
mailer *mailer.Mailer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccountService(params AccountServiceParams) *AccountServiceDefault {
|
func NewAccountService(params AccountServiceParams) *AccountServiceDefault {
|
||||||
return &AccountServiceDefault{db: params.Db, config: params.Config, identity: params.Identity}
|
return &AccountServiceDefault{db: params.Db, config: params.Config, identity: params.Identity, mailer: params.Mailer}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AccountServiceDefault) EmailExists(email string) (bool, *models.User, error) {
|
func (s *AccountServiceDefault) EmailExists(email string) (bool, *models.User, error) {
|
||||||
|
@ -96,6 +101,66 @@ func (s *AccountServiceDefault) CreateAccount(email string, password string) (*m
|
||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceDefault) SendEmailVerification(user *models.User) error {
|
||||||
|
token := GenerateSecurityToken()
|
||||||
|
|
||||||
|
var verification models.EmailVerification
|
||||||
|
|
||||||
|
verification.UserID = user.ID
|
||||||
|
verification.Token = token
|
||||||
|
verification.ExpiresAt = time.Now().Add(time.Hour)
|
||||||
|
|
||||||
|
err := s.db.Create(&verification).Error
|
||||||
|
if err != nil {
|
||||||
|
return NewAccountError(ErrKeyDatabaseOperationFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := map[string]interface{}{
|
||||||
|
"FirstName": user.FirstName,
|
||||||
|
"Email": user.Email,
|
||||||
|
"VerificationCode": token,
|
||||||
|
"ExpireTime": verification.ExpiresAt,
|
||||||
|
"PortalName": s.config.Config().Core.PortalName,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.mailer.TemplateSend(mailer.TPL_VERIFY_EMAIL, vars, vars, user.Email)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s AccountServiceDefault) VerifyEmail(email string, token string) error {
|
||||||
|
var verification models.EmailVerification
|
||||||
|
|
||||||
|
verification.Token = token
|
||||||
|
|
||||||
|
result := s.db.Model(&verification).
|
||||||
|
Preload("User").
|
||||||
|
Where(&verification).
|
||||||
|
First(&verification)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||||
|
return NewAccountError(ErrKeyUserNotFound, result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewAccountError(ErrKeyDatabaseOperationFailed, result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verification.ExpiresAt.Before(time.Now()) {
|
||||||
|
return NewAccountError(ErrKeySecurityTokenExpired, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verification.User.Email != email {
|
||||||
|
return NewAccountError(ErrKeySecurityInvalidToken, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.updateAccountInfo(verification.UserID, models.User{Verified: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (s AccountServiceDefault) UpdateAccountName(userId uint, firstName string, lastName string) error {
|
func (s AccountServiceDefault) UpdateAccountName(userId uint, firstName string, lastName string) error {
|
||||||
return s.updateAccountInfo(userId, models.User{FirstName: firstName, LastName: lastName})
|
return s.updateAccountInfo(userId, models.User{FirstName: firstName, LastName: lastName})
|
||||||
}
|
}
|
||||||
|
@ -378,6 +443,19 @@ func (s AccountServiceDefault) DNSLinkExists(hash []byte) (bool, *models.DNSLink
|
||||||
return true, model.(*models.DNSLink), nil
|
return true, model.(*models.DNSLink), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GenerateSecurityToken() string {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
b := make([]byte, 6)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
b[i] = charset[b[i]%byte(len(charset))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
func (s AccountServiceDefault) doLogin(user *models.User, ip string) (string, error) {
|
func (s AccountServiceDefault) doLogin(user *models.User, ip string) (string, error) {
|
||||||
purpose := JWTPurposeLogin
|
purpose := JWTPurposeLogin
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,10 @@ const (
|
||||||
|
|
||||||
// General errors
|
// General errors
|
||||||
ErrKeyDatabaseOperationFailed = "ErrDatabaseOperationFailed"
|
ErrKeyDatabaseOperationFailed = "ErrDatabaseOperationFailed"
|
||||||
|
|
||||||
|
// Security token errors
|
||||||
|
ErrKeySecurityTokenExpired = "ErrSecurityTokenExpired"
|
||||||
|
ErrKeySecurityInvalidToken = "ErrSecurityInvalidToken"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultErrorMessages = map[string]string{
|
var defaultErrorMessages = map[string]string{
|
||||||
|
@ -87,6 +91,10 @@ var defaultErrorMessages = map[string]string{
|
||||||
|
|
||||||
// General errors
|
// General errors
|
||||||
ErrKeyDatabaseOperationFailed: "A database operation failed.",
|
ErrKeyDatabaseOperationFailed: "A database operation failed.",
|
||||||
|
|
||||||
|
// Security token errors
|
||||||
|
ErrKeySecurityTokenExpired: "The security token has expired.",
|
||||||
|
ErrKeySecurityInvalidToken: "The security token is invalid.",
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -130,6 +138,10 @@ var (
|
||||||
// General errors
|
// General errors
|
||||||
ErrKeyDatabaseOperationFailed: http.StatusInternalServerError,
|
ErrKeyDatabaseOperationFailed: http.StatusInternalServerError,
|
||||||
ErrKeyHashingFailed: http.StatusInternalServerError,
|
ErrKeyHashingFailed: http.StatusInternalServerError,
|
||||||
|
|
||||||
|
// Security token errors
|
||||||
|
ErrKeySecurityTokenExpired: http.StatusUnauthorized,
|
||||||
|
ErrKeySecurityInvalidToken: http.StatusUnauthorized,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue