added password reset system

This commit is contained in:
Alex
2025-02-26 21:45:16 +01:00
parent 7c01b77445
commit c42adc858f
14 changed files with 350 additions and 154 deletions

View File

@@ -2,14 +2,15 @@ package constants
const ( const (
UnverifiedStatus = iota + 1 UnverifiedStatus = iota + 1
DisabledStatus
VerifiedStatus VerifiedStatus
ActiveStatus ActiveStatus
PassiveStatus PassiveStatus
DisabledStatus
DelayedPaymentStatus DelayedPaymentStatus
SettledPaymentStatus SettledPaymentStatus
AwaitingPaymentStatus AwaitingPaymentStatus
MailVerificationSubject = "Nur noch ein kleiner Schritt!" MailVerificationSubject = "Nur noch ein kleiner Schritt!"
MailChangePasswordSubject = "Passwort Änderung angefordert"
MailRegistrationSubject = "Neues Mitglied hat sich registriert" MailRegistrationSubject = "Neues Mitglied hat sich registriert"
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V." MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
MailContactSubject = "Jemand hat das Kontaktformular gefunden" MailContactSubject = "Jemand hat das Kontaktformular gefunden"
@@ -63,6 +64,14 @@ var Licences = struct {
T: "T", T: "T",
} }
var VerificationTypes = struct {
Email string
Password string
}{
Email: "email",
Password: "password",
}
var Priviliges = struct { var Priviliges = struct {
View int8 View int8
Create int8 Create int8
@@ -75,11 +84,6 @@ var Priviliges = struct {
Delete: 30, Delete: 30,
} }
const PRIV_VIEW = 1
const PRIV_ADD = 2
const PRIV_EDIT = 4
const PRIV_DELETE = 8
var MemberUpdateFields = map[string]bool{ var MemberUpdateFields = map[string]bool{
"Email": true, "Email": true,
"Phone": true, "Phone": true,

View File

@@ -0,0 +1,103 @@
package controllers
import (
"GoMembership/internal/constants"
"GoMembership/internal/utils"
"GoMembership/pkg/errors"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
func (uc *UserController) RequestPasswordChangeHandler(c *gin.Context) {
// Expected data from the user
var input struct {
Email string `json:"email" binding:"required,email"`
}
if err := c.ShouldBindJSON(&input); err != nil {
utils.HandleValidationError(c, err)
return
}
// find user
db_user, err := uc.Service.GetUserByEmail(input.Email)
if err != nil {
utils.RespondWithError(c, err, "couldn't get user by email", http.StatusNotFound, "user.user", "user.email")
return
}
// check if user may change the password
if db_user.Status <= constants.DisabledStatus {
utils.RespondWithError(c, errors.ErrNotAuthorized, "User password change request denied, user is disabled", http.StatusForbidden, errors.Responses.Fields.Login, errors.Responses.Keys.UserDisabled)
return
}
// create token
token, err := uc.Service.HandlePasswordChangeRequest(db_user)
if err != nil {
utils.RespondWithError(c, err, "couldn't handle password change request", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
return
}
// send email
if err := uc.EmailService.SendChangePasswordEmail(db_user, &token); err != nil {
utils.RespondWithError(c, err, "Couldn't send change password email", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
return
}
c.JSON(http.StatusAccepted, gin.H{
"message": "password_change_requested",
})
}
func (uc *UserController) ChangePassword(c *gin.Context) {
// Expected data from the user
var input struct {
Password string `json:"password" binding:"required"`
Token string `json:"token" binding:"required"`
}
userIDint, err := strconv.Atoi(c.Param("id"))
if err != nil {
utils.RespondWithError(c, err, "Invalid user ID", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.InvalidUserID)
return
}
if err := c.ShouldBindJSON(&input); err != nil {
utils.HandleValidationError(c, err)
return
}
verification, err := uc.Service.VerifyUser(&input.Token, &constants.VerificationTypes.Password)
if err != nil || uint(userIDint) != verification.UserID {
if err == errors.ErrAlreadyVerified {
utils.RespondWithError(c, err, "User already verified", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.PasswordAlreadyChanged)
} else {
utils.RespondWithError(c, err, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
}
return
}
user, err := uc.Service.GetUserByID(verification.UserID)
if err != nil {
utils.RespondWithError(c, err, "Couldn't find user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.UserNotFoundWrongPassword)
return
}
user.Status = constants.ActiveStatus
user.Verification = *verification
user.ID = verification.UserID
user.Password = input.Password
_, err = uc.Service.UpdateUser(user)
if err != nil {
utils.RespondWithError(c, err, "Couldn't update user", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
return
}
c.JSON(http.StatusOK, gin.H{
"message": "password_changed",
})
}

View File

@@ -204,7 +204,7 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
} }
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
utils.RespondWithError(c, err, "Error in LoginHandler", http.StatusBadRequest, "general", "server.validation.invalid_json") utils.RespondWithError(c, err, "Invalid JSON or malformed request", http.StatusBadRequest, errors.Responses.Fields.General, errors.Responses.Keys.Invalid)
return return
} }
@@ -216,9 +216,18 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
return return
} }
if user.Status <= constants.DisabledStatus {
utils.RespondWithError(c, fmt.Errorf("User banned from login %v %v", user.FirstName, user.LastName),
"Login Error; user is disabled",
http.StatusNotAcceptable,
errors.Responses.Fields.Login,
errors.Responses.Keys.UserDisabled)
return
}
ok, err := user.PasswordMatches(input.Password) ok, err := user.PasswordMatches(input.Password)
if err != nil { if err != nil {
utils.RespondWithError(c, err, "Login Error; password comparisson failed", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError) utils.RespondWithError(c, err, "Login Error; password comparisson failed", http.StatusInternalServerError, errors.Responses.Fields.Login, errors.Responses.Keys.InternalServerError)
return return
} }
if !ok { if !ok {
@@ -233,7 +242,7 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
logger.Error.Printf("jwtsecret: %v", config.Auth.JWTSecret) logger.Error.Printf("jwtsecret: %v", config.Auth.JWTSecret)
token, err := middlewares.GenerateToken(config.Auth.JWTSecret, user, "") token, err := middlewares.GenerateToken(config.Auth.JWTSecret, user, "")
if err != nil { if err != nil {
utils.RespondWithError(c, err, "Error generating token in LoginHandler", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.JwtGenerationFailed) utils.RespondWithError(c, err, "Error generating token in LoginHandler", http.StatusInternalServerError, errors.Responses.Fields.Login, errors.Responses.Keys.JwtGenerationFailed)
return return
} }
@@ -331,15 +340,26 @@ func (uc *UserController) VerifyMailHandler(c *gin.Context) {
return return
} }
user, err := uc.Service.VerifyUser(&token) verification, err := uc.Service.VerifyUser(&token, &constants.VerificationTypes.Email)
if err != nil { if err != nil {
logger.Error.Printf("Cannot verify user: %v", err) logger.Error.Printf("Cannot verify user: %v", err)
c.HTML(http.StatusUnauthorized, "verification_error.html", gin.H{"ErrorMessage": "Emailadresse wurde schon bestätigt. Sollte dies nicht der Fall sein, wende Dich bitte an info@carsharing-hasloh.de."}) c.HTML(http.StatusUnauthorized, "verification_error.html", gin.H{"ErrorMessage": "Emailadresse wurde schon bestätigt. Sollte dies nicht der Fall sein, wende Dich bitte an info@carsharing-hasloh.de."})
return return
} }
logger.Info.Printf("VerificationMailHandler User: %#v", user.Email)
user, err := uc.Service.GetUserByID(verification.UserID)
if err != nil {
utils.RespondWithError(c, err, "Couldn't find user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.UserNotFoundWrongPassword)
return
}
user.Status = constants.ActiveStatus
user.Verification = *verification
user.ID = verification.UserID
uc.Service.UpdateUser(user)
logger.Info.Printf("Verified User: %#v", user.Email)
uc.EmailService.SendWelcomeEmail(user) uc.EmailService.SendWelcomeEmail(user)
c.HTML(http.StatusOK, "verification_success.html", gin.H{"FirstName": user.FirstName}) c.HTML(http.StatusOK, "verification_success.html", gin.H{"FirstName": user.FirstName})
} }

View File

@@ -13,7 +13,7 @@ func CORSMiddleware() gin.HandlerFunc {
logger.Info.Print("Applying CORS") logger.Info.Print("Applying CORS")
return cors.New(cors.Config{ return cors.New(cors.Config{
AllowOrigins: strings.Split(config.Site.AllowOrigins, ","), AllowOrigins: strings.Split(config.Site.AllowOrigins, ","),
AllowMethods: []string{"GET", "POST", "PATCH", "OPTIONS"}, // "PUT", "PATCH", "DELETE", "OPTIONS"}, AllowMethods: []string{"GET", "POST", "PATCH", "PUT"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "X-CSRF-Token"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "X-CSRF-Token"},
ExposeHeaders: []string{"Content-Length"}, ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true, AllowCredentials: true,

View File

@@ -0,0 +1,10 @@
package repositories
import (
"GoMembership/internal/database"
"GoMembership/internal/models"
)
func (r *UserRepository) SetUserStatus(id uint, status uint) error {
return database.DB.Model(&models.User{}).Where("id = ?", id).Update("status", status).Error
}

View File

@@ -3,7 +3,6 @@ package repositories
import ( import (
"gorm.io/gorm" "gorm.io/gorm"
"GoMembership/internal/constants"
"GoMembership/internal/database" "GoMembership/internal/database"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
@@ -18,10 +17,12 @@ type UserRepositoryInterface interface {
UpdateUser(user *models.User) (*models.User, error) UpdateUser(user *models.User) (*models.User, error)
GetUsers(where map[string]interface{}) (*[]models.User, error) GetUsers(where map[string]interface{}) (*[]models.User, error)
GetUserByEmail(email string) (*models.User, error) GetUserByEmail(email string) (*models.User, error)
SetVerificationToken(verification *models.Verification) (uint, error)
IsVerified(userID *uint) (bool, error) IsVerified(userID *uint) (bool, error)
GetVerificationOfToken(token *string) (*models.Verification, error) GetVerificationOfToken(token *string, verificationType *string) (*models.Verification, error)
SetVerificationToken(verification *models.Verification) (token string, err error)
DeleteVerification(id uint, verificationType string) error
DeleteUser(id uint) error DeleteUser(id uint) error
SetUserStatus(id uint, status uint) error
} }
type UserRepository struct{} type UserRepository struct{}
@@ -156,42 +157,3 @@ func (ur *UserRepository) GetUserByEmail(email string) (*models.User, error) {
} }
return &user, nil return &user, nil
} }
func (ur *UserRepository) IsVerified(userID *uint) (bool, error) {
var user models.User
result := database.DB.Select("status").First(&user, userID)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return false, gorm.ErrRecordNotFound
}
return false, result.Error
}
return user.Status != constants.UnverifiedStatus, nil
}
func (ur *UserRepository) GetVerificationOfToken(token *string) (*models.Verification, error) {
var emailVerification models.Verification
result := database.DB.Where("verification_token = ?", token).First(&emailVerification)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, gorm.ErrRecordNotFound
}
return nil, result.Error
}
return &emailVerification, nil
}
func (ur *UserRepository) SetVerificationToken(verification *models.Verification) (uint, error) {
// Use GORM to insert or update the Verification record
result := database.DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}},
DoUpdates: clause.AssignmentColumns([]string{"verification_token", "created_at"}),
}).Create(&verification)
if result.Error != nil {
return 0, result.Error
}
return verification.ID, nil
}

View File

@@ -0,0 +1,57 @@
package repositories
import (
"GoMembership/internal/constants"
"GoMembership/internal/database"
"GoMembership/internal/models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
func (ur *UserRepository) IsVerified(userID *uint) (bool, error) {
var user models.User
result := database.DB.Select("status").First(&user, userID)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return false, gorm.ErrRecordNotFound
}
return false, result.Error
}
return user.Status > constants.DisabledStatus, nil
}
func (ur *UserRepository) GetVerificationOfToken(token *string, verificationType *string) (*models.Verification, error) {
var emailVerification models.Verification
result := database.DB.Where("verification_token = ? AND type = ?", token, verificationType).First(&emailVerification)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
return nil, gorm.ErrRecordNotFound
}
return nil, result.Error
}
return &emailVerification, nil
}
func (ur *UserRepository) SetVerificationToken(verification *models.Verification) (token string, err error) {
result := database.DB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}},
DoUpdates: clause.AssignmentColumns([]string{"verification_token", "created_at", "type"}),
}).Create(&verification)
if result.Error != nil {
return "", result.Error
}
return verification.VerificationToken, nil
}
func (ur *UserRepository) DeleteVerification(id uint, verificationType string) error {
result := database.DB.Where("user_id = ? AND type = ?", id, verificationType).Delete(&models.Verification{})
if result.Error != nil {
return result.Error
}
return nil
}

View File

@@ -3,6 +3,7 @@ package services
import ( import (
"bytes" "bytes"
"html/template" "html/template"
"os"
"gopkg.in/gomail.v2" "gopkg.in/gomail.v2"
@@ -21,7 +22,7 @@ func NewEmailService(host string, port int, username string, password string) *E
return &EmailService{dialer: dialer} return &EmailService{dialer: dialer}
} }
func (s *EmailService) SendEmail(to string, subject string, body string, replyTo string) error { func (s *EmailService) SendEmail(to string, subject string, body string, bodyTXT string, replyTo string) error {
msg := gomail.NewMessage() msg := gomail.NewMessage()
msg.SetHeader("From", s.dialer.Username) msg.SetHeader("From", s.dialer.Username)
msg.SetHeader("To", to) msg.SetHeader("To", to)
@@ -29,7 +30,12 @@ func (s *EmailService) SendEmail(to string, subject string, body string, replyTo
if replyTo != "" { if replyTo != "" {
msg.SetHeader("REPLY_TO", replyTo) msg.SetHeader("REPLY_TO", replyTo)
} }
msg.SetBody("text/html", body) if bodyTXT != "" {
msg.SetBody("text/plain", bodyTXT)
}
msg.AddAlternative("text/html", body)
msg.WriteTo(os.Stdout)
if err := s.dialer.DialAndSend(msg); err != nil { if err := s.dialer.DialAndSend(msg); err != nil {
logger.Error.Printf("Could not send email to %s: %v", to, err) logger.Error.Printf("Could not send email to %s: %v", to, err)
@@ -79,7 +85,10 @@ func (s *EmailService) SendVerificationEmail(user *models.User, token *string) e
logger.Error.Print("Couldn't send verification mail") logger.Error.Print("Couldn't send verification mail")
return err return err
} }
return s.SendEmail(user.Email, subject, body, "") return s.SendEmail(user.Email, subject, body, "", "")
}
func (s *EmailService) SendChangePasswordEmail(user *models.User, token *string) error { func (s *EmailService) SendChangePasswordEmail(user *models.User, token *string) error {
// Prepare data to be injected into the template // Prepare data to be injected into the template
data := struct { data := struct {
@@ -136,12 +145,17 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
} }
subject := constants.MailWelcomeSubject subject := constants.MailWelcomeSubject
body, err := ParseTemplate("mail_welcome.tmpl", data) htmlBody, err := ParseTemplate("mail_welcome.tmpl", data)
if err != nil { if err != nil {
logger.Error.Print("Couldn't send welcome mail") logger.Error.Print("Couldn't send welcome mail")
return err return err
} }
return s.SendEmail(user.Email, subject, body, "") plainBody, err := ParseTemplate("mail_welcome.txt.tmpl", data)
if err != nil {
logger.Error.Print("Couldn't parse password mail")
return err
}
return s.SendEmail(user.Email, subject, htmlBody, plainBody, "")
} }
func (s *EmailService) SendRegistrationNotification(user *models.User) error { func (s *EmailService) SendRegistrationNotification(user *models.User) error {
@@ -185,12 +199,17 @@ func (s *EmailService) SendRegistrationNotification(user *models.User) error {
} }
subject := constants.MailRegistrationSubject subject := constants.MailRegistrationSubject
body, err := ParseTemplate("mail_registration.tmpl", data) htmlBody, err := ParseTemplate("mail_registration.tmpl", data)
if err != nil { if err != nil {
logger.Error.Print("Couldn't send admin notification mail") logger.Error.Print("Couldn't send admin notification mail")
return err return err
} }
return s.SendEmail(config.Recipients.UserRegistration, subject, body, "") plainBody, err := ParseTemplate("mail_registration.txt.tmpl", data)
if err != nil {
logger.Error.Print("Couldn't parse password mail")
return err
}
return s.SendEmail(user.Email, subject, htmlBody, plainBody, "")
} }
func (s *EmailService) RelayContactFormMessage(sender string, name string, message string) error { func (s *EmailService) RelayContactFormMessage(sender string, name string, message string) error {
@@ -208,10 +227,15 @@ func (s *EmailService) RelayContactFormMessage(sender string, name string, messa
WebsiteTitle: config.Site.WebsiteTitle, WebsiteTitle: config.Site.WebsiteTitle,
} }
subject := constants.MailContactSubject subject := constants.MailContactSubject
body, err := ParseTemplate("mail_contact_form.tmpl", data) htmlBody, err := ParseTemplate("mail_contact_form.tmpl", data)
if err != nil { if err != nil {
logger.Error.Print("Couldn't send contact form message mail") logger.Error.Print("Couldn't send contact form message mail")
return err return err
} }
return s.SendEmail(config.Recipients.ContactForm, subject, body, sender) plainBody, err := ParseTemplate("mail_contact_form.txt.tmpl", data)
if err != nil {
logger.Error.Print("Couldn't parse password mail")
return err
}
return s.SendEmail(config.Recipients.ContactForm, subject, htmlBody, plainBody, sender)
} }

View File

@@ -0,0 +1,21 @@
package services
import (
"GoMembership/internal/constants"
"GoMembership/internal/models"
)
func (s *UserService) HandlePasswordChangeRequest(user *models.User) (token string, err error) {
// Deactivate user and reset Verification
if err := s.SetUserStatus(user.ID, constants.DisabledStatus); err != nil {
return "", err
}
if err := s.RevokeVerification(&user.ID, constants.VerificationTypes.Password); err != nil {
return "", err
}
// Generate a token
return s.SetVerificationToken(&user.ID, &constants.VerificationTypes.Password)
}

View File

@@ -0,0 +1,5 @@
package services
func (s *UserService) SetUserStatus(id uint, status uint) error {
return s.Repo.SetUserStatus(id, status)
}

View File

@@ -1,15 +1,12 @@
package services package services
import ( import (
"net/http"
"strings" "strings"
"GoMembership/internal/constants" "GoMembership/internal/constants"
"GoMembership/internal/models" "GoMembership/internal/models"
"GoMembership/internal/repositories" "GoMembership/internal/repositories"
"GoMembership/internal/utils"
"GoMembership/pkg/errors" "GoMembership/pkg/errors"
"GoMembership/pkg/logger"
"github.com/alexedwards/argon2id" "github.com/alexedwards/argon2id"
"gorm.io/gorm" "gorm.io/gorm"
@@ -18,13 +15,17 @@ import (
) )
type UserServiceInterface interface { type UserServiceInterface interface {
RegisterUser(user *models.User) (uint, string, error) RegisterUser(user *models.User) (id uint, token string, err error)
GetUserByEmail(email string) (*models.User, error) GetUserByEmail(email string) (*models.User, error)
GetUserByID(id uint) (*models.User, error) GetUserByID(id uint) (*models.User, error)
GetUsers(where map[string]interface{}) (*[]models.User, error) GetUsers(where map[string]interface{}) (*[]models.User, error)
VerifyUser(token *string) (*models.User, error)
UpdateUser(user *models.User) (*models.User, error) UpdateUser(user *models.User) (*models.User, error)
DeleteUser(lastname string, id uint) error DeleteUser(lastname string, id uint) error
SetUserStatus(id uint, status uint) error
VerifyUser(token *string, verificationType *string) (*models.Verification, error)
SetVerificationToken(id *uint, verificationType *string) (string, error)
RevokeVerification(id *uint, verificationType string) error
HandlePasswordChangeRequest(user *models.User) (token string, err error)
} }
type UserService struct { type UserService struct {
@@ -81,7 +82,7 @@ func (service *UserService) UpdateUser(user *models.User) (*models.User, error)
return updatedUser, nil return updatedUser, nil
} }
func (service *UserService) RegisterUser(user *models.User) (uint, string, error) { func (service *UserService) RegisterUser(user *models.User) (id uint, token string, err error) {
setPassword(user.Password, user) setPassword(user.Password, user)
@@ -89,49 +90,20 @@ func (service *UserService) RegisterUser(user *models.User) (uint, string, error
user.CreatedAt = time.Now() user.CreatedAt = time.Now()
user.UpdatedAt = time.Now() user.UpdatedAt = time.Now()
user.PaymentStatus = constants.AwaitingPaymentStatus user.PaymentStatus = constants.AwaitingPaymentStatus
// if user.Licence == nil {
// user.Licence = &models.Licence{Status: constants.UnverifiedStatus}
// }
user.BankAccount.MandateDateSigned = time.Now() user.BankAccount.MandateDateSigned = time.Now()
id, err := service.Repo.CreateUser(user) id, err = service.Repo.CreateUser(user)
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
user.ID = id token, err = service.SetVerificationToken(&id, &constants.VerificationTypes.Email)
token, err := utils.GenerateVerificationToken()
if err != nil { if err != nil {
return 0, "", err return 0, "", err
} }
logger.Info.Printf("TOKEN: %v", token)
// Check if user is already verified
verified, err := service.Repo.IsVerified(&user.ID)
if err != nil {
return 0, "", err
}
if verified {
return 0, "", errors.ErrAlreadyVerified
}
// Prepare the Verification record
verification := models.Verification{
UserID: user.ID,
VerificationToken: token,
}
if _, err = service.Repo.SetVerificationToken(&verification); err != nil {
return http.StatusInternalServerError, "", err
}
return id, token, nil return id, token, nil
} }
func (service *UserService) GetUserByID(id uint) (*models.User, error) { func (service *UserService) GetUserByID(id uint) (*models.User, error) {
return repositories.GetUserByID(&id) return repositories.GetUserByID(&id)
} }
@@ -146,35 +118,6 @@ func (service *UserService) GetUsers(where map[string]interface{}) (*[]models.Us
return service.Repo.GetUsers(where) return service.Repo.GetUsers(where)
} }
func (service *UserService) VerifyUser(token *string) (*models.User, error) {
verification, err := service.Repo.GetVerificationOfToken(token)
if err != nil {
return nil, err
}
// Check if the user is already verified
verified, err := service.Repo.IsVerified(&verification.UserID)
if err != nil {
return nil, err
}
user, err := repositories.GetUserByID(&verification.UserID)
if err != nil {
return nil, err
}
if verified {
return user, errors.ErrAlreadyVerified
}
// Update user status to active
t := time.Now()
verification.EmailVerifiedAt = &t
user.Status = constants.VerifiedStatus
user.Verification = *verification
user.ID = verification.UserID
service.Repo.UpdateUser(user)
return user, nil
}
func setPassword(plaintextPassword string, u *models.User) error { func setPassword(plaintextPassword string, u *models.User) error {
hash, err := argon2id.CreateHash(plaintextPassword, argon2id.DefaultParams) hash, err := argon2id.CreateHash(plaintextPassword, argon2id.DefaultParams)
if err != nil { if err != nil {

View File

@@ -0,0 +1,60 @@
package services
import (
"GoMembership/internal/models"
"GoMembership/internal/utils"
"GoMembership/pkg/errors"
"time"
)
func (s *UserService) SetVerificationToken(id *uint, verificationType *string) (string, error) {
token, err := utils.GenerateVerificationToken()
if err != nil {
return "", err
}
// Check if user is already verified
verified, err := s.Repo.IsVerified(id)
if err != nil {
return "", err
}
if verified {
return "", errors.ErrAlreadyVerified
}
// Prepare the Verification record
verification := models.Verification{
UserID: *id,
VerificationToken: token,
Type: *verificationType,
}
return s.Repo.SetVerificationToken(&verification)
}
func (s *UserService) RevokeVerification(id *uint, verificationType string) error {
return s.Repo.DeleteVerification(*id, verificationType)
}
func (service *UserService) VerifyUser(token *string, verificationType *string) (*models.Verification, error) {
verification, err := service.Repo.GetVerificationOfToken(token, verificationType)
if err != nil {
return nil, err
}
// Check if the user is already verified
verified, err := service.Repo.IsVerified(&verification.UserID)
if err != nil {
return nil, err
}
if verified {
return nil, errors.ErrAlreadyVerified
}
t := time.Now()
verification.VerifiedAt = &t
// Update user status to active
return verification, nil
}

View File

@@ -81,21 +81,6 @@ func FilterAllowedStructFields(input interface{}, existing interface{}, allowedF
} else { } else {
originField.Set(fieldValue) originField.Set(fieldValue)
} }
// If the slice contains structs, recursively filter each element
// if fieldValue.Type().Elem().Kind() == reflect.Struct {
// for j := 0; j < fieldValue.Len(); j++ {
// err := FilterAllowedStructFields(
// fieldValue.Index(j).Addr().Interface(),
// originField.Index(j).Addr().Interface(),
// allowedFields,
// fullKey,
// )
// if err != nil {
// return err
// }
// }
// }
continue continue
} }

View File

@@ -12,13 +12,14 @@ type ValidationKeys struct {
JwtGenerationFailed string JwtGenerationFailed string
Duplicate string Duplicate string
InvalidUserID string InvalidUserID string
PasswordAlreadyChanged string
UserDisabled string
} }
type ValidationFields struct { type ValidationFields struct {
General string General string
ParentMemberShipID string ParentMemberShipID string
SubscriptionModel string SubscriptionModel string
UserID string
Login string Login string
Email string Email string
User string User string
@@ -60,14 +61,15 @@ var Responses = struct {
UserNotFoundWrongPassword: "server.validation.user_not_found_or_wrong_password", UserNotFoundWrongPassword: "server.validation.user_not_found_or_wrong_password",
JwtGenerationFailed: "server.error.jwt_generation_failed", JwtGenerationFailed: "server.error.jwt_generation_failed",
Duplicate: "server.validation.duplicate", Duplicate: "server.validation.duplicate",
UserDisabled: "server.validation.user_disabled",
PasswordAlreadyChanged: "server.validation.password_already_changed",
}, },
Fields: ValidationFields{ Fields: ValidationFields{
General: "general", General: "general",
ParentMemberShipID: "parent_membership_id", ParentMemberShipID: "parent_membership_id",
SubscriptionModel: "subscription_model", SubscriptionModel: "subscription_model",
UserID: "user_id", Login: "user.login",
Login: "login", Email: "user.email",
Email: "email", User: "user.user",
User: "user",
}, },
} }