added password reset system
This commit is contained in:
@@ -2,17 +2,18 @@ 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!"
|
||||||
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
MailChangePasswordSubject = "Passwort Änderung angefordert"
|
||||||
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
MailRegistrationSubject = "Neues Mitglied hat sich registriert"
|
||||||
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
MailWelcomeSubject = "Willkommen beim Dörpsmobil Hasloh e.V."
|
||||||
|
MailContactSubject = "Jemand hat das Kontaktformular gefunden"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Roles = struct {
|
var Roles = struct {
|
||||||
@@ -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,
|
||||||
|
|||||||
103
internal/controllers/user_Password.go
Normal file
103
internal/controllers/user_Password.go
Normal 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",
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
10
internal/repositories/user_permissions.go
Normal file
10
internal/repositories/user_permissions.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
57
internal/repositories/user_verification.go
Normal file
57
internal/repositories/user_verification.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
21
internal/services/user_password.go
Normal file
21
internal/services/user_password.go
Normal 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)
|
||||||
|
|
||||||
|
}
|
||||||
5
internal/services/user_permissions.go
Normal file
5
internal/services/user_permissions.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
func (s *UserService) SetUserStatus(id uint, status uint) error {
|
||||||
|
return s.Repo.SetUserStatus(id, status)
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
60
internal/services/user_verification.go
Normal file
60
internal/services/user_verification.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user