frontend: disabled button while processing password reset

This commit is contained in:
Alex
2025-02-28 08:51:35 +01:00
parent 8137f121ed
commit 9c9430ca9c
92 changed files with 37 additions and 17 deletions

View File

@@ -0,0 +1,71 @@
package controllers
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
)
type SQLInjectionTest struct {
name string
email string
password string
expectedStatus int
}
func (sit *SQLInjectionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
loginData := loginInput{
Email: sit.email,
Password: sit.password,
}
jsonData, _ := json.Marshal(loginData)
return GetMockedJSONContext(jsonData, "/login")
}
func (sit *SQLInjectionTest) RunHandler(c *gin.Context, router *gin.Engine) {
router.POST("/login", Uc.LoginHandler)
router.ServeHTTP(c.Writer, c.Request)
}
func (sit *SQLInjectionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if sit.expectedStatus != w.Code {
responseBody, _ := io.ReadAll(w.Body)
return fmt.Errorf("SQL Injection Attempt: Didn't get the expected response code: got: %v; expected: %v. Context: %#v", w.Code, sit.expectedStatus, string(responseBody))
}
return nil
}
func (sit *SQLInjectionTest) ValidateResult() error {
// Add any additional validation if needed
return nil
}
func testSQLInjectionAttempt(t *testing.T) {
tests := []SQLInjectionTest{
{
name: "SQL Injection Attempt in Email",
email: "' OR '1'='1",
password: "password123",
expectedStatus: http.StatusNotFound,
},
{
name: "SQL Injection Attempt in Password",
email: "user@example.com",
password: "' OR '1'='1",
expectedStatus: http.StatusNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
}

View File

@@ -0,0 +1,31 @@
package controllers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func testXSSAttempt(t *testing.T) {
gin.SetMode(gin.TestMode)
router := gin.New()
router.POST("/register", Uc.RegisterUser)
xssPayload := "<script>alert('XSS')</script>"
user := getBaseUser()
user.FirstName = xssPayload
user.Email = "user@xss.hack"
jsonData, _ := json.Marshal(RegistrationData{User: user})
req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
assert.NotContains(t, w.Body.String(), xssPayload)
}

View File

@@ -0,0 +1,50 @@
package controllers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"GoMembership/internal/services"
"GoMembership/pkg/logger"
)
type ContactController struct {
EmailService *services.EmailService
}
type contactData struct {
Email string `form:"REPLY_TO" validate:"required,email"`
Name string `form:"name"`
Message string `form:"message" validate:"required"`
Honeypot string `form:"username" validate:"eq="`
}
func (cc *ContactController) RelayContactRequest(c *gin.Context) {
var msgData contactData
if err := c.ShouldBind(&msgData); err != nil {
// A bot is talking to us
c.JSON(http.StatusNotAcceptable, gin.H{"error": "Not Acceptable"})
return
}
validate := validator.New()
if err := validate.Struct(msgData); err != nil {
logger.Error.Printf("Couldn't validate contact form data: %#v: %v", msgData, err)
c.HTML(http.StatusNotAcceptable, "contactForm_reply.html", gin.H{"Error": "Form validation failed. Please check again."})
// c.JSON(http.StatusNotAcceptable, gin.H{"error": "Couldn't validate contact form data"})
return
}
if err := cc.EmailService.RelayContactFormMessage(msgData.Email, msgData.Name, msgData.Message); err != nil {
logger.Error.Printf("Couldn't send contact message mail: %v", err)
c.HTML(http.StatusInternalServerError, "contactForm_reply.html", gin.H{"Error": "Email submission failed. Please try again."})
// c.JSON(http.StatusInternalServerError, gin.H{"error": "Couldn't send mail"})
return
}
// c.JSON(http.StatusAccepted, "Your message has been sent")
c.HTML(http.StatusAccepted, "contactForm_reply.html", gin.H{"Success": true})
}

View File

@@ -0,0 +1,159 @@
package controllers
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"GoMembership/internal/config"
"GoMembership/internal/constants"
"GoMembership/internal/utils"
"github.com/gin-gonic/gin"
)
type RelayContactRequestTest struct {
Input url.Values
Name string
WantResponse int
Assert bool
}
func testContactController(t *testing.T) {
tests := getContactData()
for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
}
func (rt *RelayContactRequestTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
return GetMockedFormContext(rt.Input, "/contact")
}
func (rt *RelayContactRequestTest) RunHandler(c *gin.Context, router *gin.Engine) {
router.POST("/contact", Cc.RelayContactRequest)
router.ServeHTTP(c.Writer, c.Request)
// Cc.RelayContactRequest(c)
}
func (rt *RelayContactRequestTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != rt.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, rt.WantResponse)
}
return nil
}
func (rt *RelayContactRequestTest) ValidateResult() error {
messages := utils.SMTPGetMessages()
for _, message := range messages {
mail, err := utils.DecodeMail(message.MsgRequest())
if err != nil {
return err
}
if strings.Contains(mail.Subject, constants.MailContactSubject) {
if err := checkContactRequestMail(mail, rt); err != nil {
return err
}
} else {
return fmt.Errorf("Subject not expected: %v", mail.Subject)
}
}
return nil
}
func checkContactRequestMail(mail *utils.Email, rt *RelayContactRequestTest) error {
if !strings.Contains(mail.To, config.Recipients.ContactForm) {
return fmt.Errorf("Contact Information didn't reach the admin! Recipient was: %v instead of %v", mail.To, config.Recipients.ContactForm)
}
if !strings.Contains(mail.From, config.SMTP.User) {
return fmt.Errorf("Contact Information was sent from unexpected address! Sender was: %v instead of %v", mail.From, config.SMTP.User)
}
//Check if all the relevant data has been passed to the mail.
if !strings.Contains(mail.Body, rt.Input.Get("name")) {
return fmt.Errorf("User name(%v) has not been rendered in contact mail.", rt.Input.Get("name"))
}
if !strings.Contains(mail.Body, rt.Input.Get("message")) {
return fmt.Errorf("User message(%v) has not been rendered in contact mail.", rt.Input.Get("message"))
}
return nil
}
func getBaseRequest() *url.Values {
return &url.Values{
"username": {""},
"name": {"My-First and-Last-Name"},
"REPLY_TO": {"name@domain.de"},
"message": {"My message to the world"},
}
}
func customizeRequest(updates map[string]string) *url.Values {
form := getBaseRequest()
for key, value := range updates {
form.Set(key, value)
}
return form
}
func getContactData() []RelayContactRequestTest {
return []RelayContactRequestTest{
{
Name: "mail empty, should fail",
WantResponse: http.StatusNotAcceptable,
Assert: false,
Input: *customizeRequest(
map[string]string{
"REPLY_TO": "",
}),
},
{
Name: "mail invalid, should fail",
WantResponse: http.StatusNotAcceptable,
Assert: false,
Input: *customizeRequest(
map[string]string{
"REPLY_TO": "novalid#email.de",
}),
},
{
Name: "No message should fail",
WantResponse: http.StatusNotAcceptable,
Assert: true,
Input: *customizeRequest(
map[string]string{
"message": "",
}),
},
{
Name: "Honeypot set, should fail",
WantResponse: http.StatusNotAcceptable,
Assert: true,
Input: *customizeRequest(
map[string]string{
"username": "I'm a bot",
}),
},
{
Name: "Correct message, should pass",
WantResponse: http.StatusAccepted,
Assert: true,
Input: *customizeRequest(
map[string]string{}),
},
}
}

View File

@@ -0,0 +1,313 @@
package controllers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strconv"
"testing"
"time"
"log"
"github.com/gin-gonic/gin"
"GoMembership/internal/config"
"GoMembership/internal/constants"
"GoMembership/internal/database"
"GoMembership/internal/models"
"GoMembership/internal/repositories"
"GoMembership/internal/services"
"GoMembership/internal/utils"
"GoMembership/internal/validation"
"GoMembership/pkg/logger"
)
type TestCase interface {
SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine)
RunHandler(*gin.Context, *gin.Engine)
ValidateResponse(*httptest.ResponseRecorder) error
ValidateResult() error
}
const (
Host = "127.0.0.1"
Port int = 2525
)
type loginInput struct {
Email string `json:"email"`
Password string `json:"password"`
}
var (
Uc *UserController
Mc *MembershipController
Cc *ContactController
)
func TestSuite(t *testing.T) {
_ = deleteTestDB("test.db")
cwd, err := os.Getwd()
if err != nil {
log.Fatalf("Failed to get current working directory: %v", err)
}
// Build paths relative to the current working directory
configFilePath := filepath.Join(cwd, "..", "..", "configs", "config.json")
templateHTMLPath := filepath.Join(cwd, "..", "..", "templates", "html")
templateMailPath := filepath.Join(cwd, "..", "..", "templates", "email")
if err := os.Setenv("TEMPLATE_MAIL_PATH", templateMailPath); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
if err := os.Setenv("TEMPLATE_HTML_PATH", templateHTMLPath); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
if err := os.Setenv("CONFIG_FILE_PATH", configFilePath); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
if err := os.Setenv("SMTP_HOST", Host); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
if err := os.Setenv("SMTP_PORT", strconv.Itoa(Port)); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
if err := os.Setenv("BASE_URL", "http://"+Host+":2525"); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
if err := os.Setenv("DB_PATH", "test.db"); err != nil {
log.Fatalf("Error setting environment variable: %v", err)
}
config.LoadConfig()
if err := database.Open("test.db", config.Recipients.AdminEmail); err != nil {
log.Fatalf("Failed to create DB: %#v", err)
}
utils.SMTPStart(Host, Port)
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password)
var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
consentService := &services.ConsentService{Repo: consentRepo}
var bankAccountRepo repositories.BankAccountRepositoryInterface = &repositories.BankAccountRepository{}
bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}
var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}
var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}
licenceService := &services.LicenceService{Repo: licenceRepo}
Uc = &UserController{Service: userService, LicenceService: licenceService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
Mc = &MembershipController{UserController: &MockUserController{}, Service: *membershipService}
Cc = &ContactController{EmailService: emailService}
if err := initSubscriptionPlans(); err != nil {
log.Fatalf("Failed to init Subscription plans: %#v", err)
}
if err := initLicenceCategories(); err != nil {
log.Fatalf("Failed to init Categories: %v", err)
}
admin := models.User{
FirstName: "Ad",
LastName: "min",
Email: "admin@example.com",
DateOfBirth: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
Company: "SampleCorp",
Phone: "+123456789",
Address: "123 Main Street",
ZipCode: "12345",
City: "SampleCity",
Status: constants.ActiveStatus,
RoleID: 8,
}
admin.SetPassword("securepassword")
database.DB.Create(&admin)
validation.SetupValidators()
t.Run("userController", func(t *testing.T) {
testUserController(t)
})
t.Run("SQL_Injection", func(t *testing.T) {
testSQLInjectionAttempt(t)
})
t.Run("contactController", func(t *testing.T) {
testContactController(t)
})
t.Run("membershipController", func(t *testing.T) {
testMembershipController(t)
})
t.Run("XSSAttempt", func(t *testing.T) {
testXSSAttempt(t)
})
if err := utils.SMTPStop(); err != nil {
log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err)
}
// if err := deleteTestDB("test.db"); err != nil {
// log.Fatalf("Failed to tear down DB: %#v", err)
// }
}
func initLicenceCategories() error {
categories := []models.Category{
{Name: "AM"},
{Name: "A1"},
{Name: "A2"},
{Name: "A"},
{Name: "B"},
{Name: "C1"},
{Name: "C"},
{Name: "D1"},
{Name: "D"},
{Name: "BE"},
{Name: "C1E"},
{Name: "CE"},
{Name: "D1E"},
{Name: "DE"},
{Name: "T"},
{Name: "L"},
}
for _, category := range categories {
result := database.DB.Create(&category)
if result.Error != nil {
return result.Error
}
}
return nil
}
func initSubscriptionPlans() error {
subscriptions := []models.SubscriptionModel{
{
Name: "Basic",
Details: "Test Plan",
MonthlyFee: 2,
HourlyRate: 3,
},
{
Name: "additional",
Details: "This plan needs another membership id to validate",
RequiredMembershipField: "ParentMembershipID",
MonthlyFee: 2,
HourlyRate: 3,
},
}
for _, subscription := range subscriptions {
result := database.DB.Create(&subscription)
if result.Error != nil {
return result.Error
}
}
return nil
}
func GetMockedJSONContext(jsonStr []byte, url string) (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
router := gin.New()
// Load HTML templates
router.LoadHTMLGlob(config.Templates.HTMLPath + "/*")
var err error
c.Request, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
if err != nil {
log.Fatalf("Failed to create new Request: %#v", err)
}
c.Request.Header.Set("Content-Type", "application/json")
return c, w, router
}
func GetMockedFormContext(formData url.Values, url string) (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
gin.SetMode(gin.TestMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
router := gin.New()
// Load HTML templates
router.LoadHTMLGlob(config.Templates.HTMLPath + "/*")
req, err := http.NewRequest("POST",
url,
bytes.NewBufferString(formData.Encode()))
if err != nil {
log.Fatalf("Failed to create new Request: %#v", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
c.Request = req
return c, w, router
}
func getBaseUser() models.User {
return models.User{
DateOfBirth: time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
FirstName: "John",
LastName: "Doe",
Email: "john.doe@example.com",
Address: "Pablo Escobar Str. 4",
ZipCode: "25474",
City: "Hasloh",
Phone: "01738484993",
BankAccount: models.BankAccount{IBAN: "DE89370400440532013000"},
Membership: models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
Licence: nil,
ProfilePicture: "",
Password: "password123",
Company: "",
RoleID: 8,
}
}
func deleteTestDB(dbPath string) error {
err := os.Remove(dbPath)
if err != nil {
return err
}
return nil
}
func runSingleTest(tc TestCase) error {
c, w, router := tc.SetupContext()
tc.RunHandler(c, router)
if err := tc.ValidateResponse(w); err != nil {
return err
}
return tc.ValidateResult()
}
func GenerateInputJSON(aStruct interface{}) string {
// Marshal the object into JSON
jsonBytes, err := json.Marshal(aStruct)
if err != nil {
logger.Error.Fatalf("Couldn't generate JSON: %#v\nERROR: %#v", aStruct, err)
return ""
}
return string(jsonBytes)
}

View File

@@ -0,0 +1,26 @@
package controllers
import (
"GoMembership/internal/services"
"GoMembership/internal/utils"
"net/http"
"github.com/gin-gonic/gin"
)
type LicenceController struct {
Service services.LicenceService
}
func (lc *LicenceController) GetAllCategories(c *gin.Context) {
categories, err := lc.Service.GetAllCategories()
if err != nil {
utils.RespondWithError(c, err, "Error retrieving licence categories", http.StatusInternalServerError, "general", "server.error.internal_server_error")
return
}
c.JSON(http.StatusOK, gin.H{
"licence_categories": categories,
})
}

View File

@@ -0,0 +1,153 @@
package controllers
import (
"GoMembership/internal/constants"
"GoMembership/internal/models"
"GoMembership/internal/services"
"GoMembership/internal/utils"
"strings"
"net/http"
"github.com/gin-gonic/gin"
"GoMembership/pkg/errors"
"GoMembership/pkg/logger"
)
type MembershipController struct {
Service services.MembershipService
UserController interface {
ExtractUserFromContext(*gin.Context) (*models.User, error)
}
}
type MembershipData struct {
// APIKey string `json:"api_key"`
Subscription models.SubscriptionModel `json:"subscription"`
}
func (mc *MembershipController) RegisterSubscription(c *gin.Context) {
var regData MembershipData
requestUser, err := mc.UserController.ExtractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in subscription registrationHandler", http.StatusBadRequest, "general", "server.validation.invalid_user_data")
return
}
if !utils.HasPrivilige(requestUser, constants.Priviliges.Create) {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to register subscription", http.StatusForbidden, "user.user", "server.error.unauthorized")
return
}
if err := c.ShouldBindJSON(&regData); err != nil {
utils.HandleValidationError(c, err)
return
}
// Register Subscription
logger.Info.Printf("Registering subscription %v", regData.Subscription.Name)
id, err := mc.Service.RegisterSubscription(&regData.Subscription)
if err != nil {
logger.Error.Printf("Couldn't register Membershipmodel: %v", err)
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
c.JSON(http.StatusConflict, "Duplicate subscription name")
return
}
c.JSON(http.StatusNotAcceptable, "Couldn't register Membershipmodel")
return
}
logger.Info.Printf("registering subscription: %+v", regData)
c.JSON(http.StatusCreated, gin.H{
"status": "success",
"id": id,
})
}
func (mc *MembershipController) UpdateHandler(c *gin.Context) {
var regData MembershipData
requestUser, err := mc.UserController.ExtractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in subscription UpdateHandler", http.StatusBadRequest, "general", "server.validation.no_auth_tokenw")
return
}
if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update subscription", http.StatusForbidden, "user.user", "server.error.unauthorized")
return
}
if err := c.ShouldBindJSON(&regData); err != nil {
utils.HandleValidationError(c, err)
return
}
// update Subscription
logger.Info.Printf("Updating subscription %v", regData.Subscription.Name)
id, err := mc.Service.UpdateSubscription(&regData.Subscription)
if err != nil {
logger.Error.Printf("Couldn't update Membershipmodel: %v", err)
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
c.JSON(http.StatusConflict, "Duplicate subscription name")
return
}
c.JSON(http.StatusNotAcceptable, "Couldn't update Membershipmodel")
return
}
logger.Info.Printf("updating subscription: %+v", regData)
c.JSON(http.StatusAccepted, gin.H{
"status": "success",
"id": id,
})
}
func (mc *MembershipController) DeleteSubscription(c *gin.Context) {
type deleteData struct {
Subscription struct {
ID uint `json:"id"`
Name string `json:"name"`
} `json:"subscription"`
}
var data deleteData
requestUser, err := mc.UserController.ExtractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in subscription UpdateHandler", http.StatusBadRequest, "general", "server.validation.no_auth_tokenw")
return
}
if !utils.HasPrivilige(requestUser, constants.Priviliges.Delete) {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update subscription", http.StatusForbidden, "user.user", "server.error.unauthorized")
return
}
if err := c.ShouldBindJSON(&data); err != nil {
utils.HandleValidationError(c, err)
return
}
if err := mc.Service.DeleteSubscription(&data.Subscription.ID, &data.Subscription.Name); err != nil {
utils.RespondWithError(c, err, "Error during subscription Deletion", http.StatusExpectationFailed, "subscription", "server.error.not_possible")
return
}
c.JSON(http.StatusOK, gin.H{"message": "Subscription deleted successfully"})
}
func (mc *MembershipController) GetSubscriptions(c *gin.Context) {
subscriptions, err := mc.Service.GetSubscriptions(nil)
if err != nil {
logger.Error.Printf("Error retrieving subscriptions: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
"field": "general",
"key": "validation.internal_server_error",
}}})
return
}
c.JSON(http.StatusOK, gin.H{
"subscriptions": subscriptions,
})
}

View File

@@ -0,0 +1,393 @@
package controllers
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"GoMembership/internal/constants"
"GoMembership/internal/database"
"GoMembership/internal/models"
"GoMembership/pkg/logger"
"github.com/gin-gonic/gin"
)
type RegisterSubscriptionTest struct {
WantDBData map[string]interface{}
Input string
Name string
WantResponse int
Assert bool
}
type UpdateSubscriptionTest struct {
WantDBData map[string]interface{}
Input string
Name string
WantResponse int
Assert bool
}
type DeleteSubscriptionTest struct {
WantDBData map[string]interface{}
Input string
Name string
WantResponse int
Assert bool
}
type MockUserController struct {
UserController // Embed the UserController
}
func (m *MockUserController) ExtractUserFromContext(c *gin.Context) (*models.User, error) {
return &models.User{
ID: 1,
FirstName: "Admin",
LastName: "User",
Email: "admin@test.com",
RoleID: constants.Roles.Admin,
}, nil
}
func setupMockAuth() {
// Create and assign the mock controller
mockController := &MockUserController{}
Mc.UserController = mockController
}
func testMembershipController(t *testing.T) {
setupMockAuth()
tests := getSubscriptionRegistrationData()
for _, tt := range tests {
logger.Error.Print("==============================================================")
logger.Error.Printf("MembershipController : %v", tt.Name)
logger.Error.Print("==============================================================")
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
updateTests := getSubscriptionUpdateData()
for _, tt := range updateTests {
logger.Error.Print("==============================================================")
logger.Error.Printf("Update SubscriptionData : %v", tt.Name)
logger.Error.Print("==============================================================")
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
deleteTests := getSubscriptionDeleteData()
for _, tt := range deleteTests {
logger.Error.Print("==============================================================")
logger.Error.Printf("Delete SubscriptionData : %v", tt.Name)
logger.Error.Print("==============================================================")
t.Run(tt.Name, func(t *testing.T) {
if err := runSingleTest(&tt); err != nil {
t.Errorf("Test failed: %v", err.Error())
}
})
}
}
func (rt *RegisterSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
return GetMockedJSONContext([]byte(rt.Input), "api/subscription")
}
func (rt *RegisterSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
Mc.RegisterSubscription(c)
}
func (rt *RegisterSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != rt.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, rt.WantResponse)
}
return nil
}
func (rt *RegisterSubscriptionTest) ValidateResult() error {
return validateSubscription(rt.Assert, rt.WantDBData)
}
func validateSubscription(assert bool, wantDBData map[string]interface{}) error {
subscriptions, err := Mc.Service.GetSubscriptions(wantDBData)
if err != nil {
return fmt.Errorf("Error in database ops: %#v", err)
}
if assert != (len(*subscriptions) != 0) {
return fmt.Errorf("Subscription entry query didn't met expectation: %v != %#v", assert, *subscriptions)
}
return nil
}
func (ut *UpdateSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
return GetMockedJSONContext([]byte(ut.Input), "api/subscription/upsert")
}
func (ut *UpdateSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
Mc.UpdateHandler(c)
}
func (ut *UpdateSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != ut.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, ut.WantResponse)
}
return nil
}
func (ut *UpdateSubscriptionTest) ValidateResult() error {
return validateSubscription(ut.Assert, ut.WantDBData)
}
func (dt *DeleteSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
return GetMockedJSONContext([]byte(dt.Input), "api/subscription/delete")
}
func (dt *DeleteSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
Mc.DeleteSubscription(c)
}
func (dt *DeleteSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
if w.Code != dt.WantResponse {
return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, dt.WantResponse)
}
return nil
}
func (dt *DeleteSubscriptionTest) ValidateResult() error {
return validateSubscription(dt.Assert, dt.WantDBData)
}
func getBaseSubscription() MembershipData {
return MembershipData{
// APIKey: config.Auth.APIKEY,
Subscription: models.SubscriptionModel{
Name: "Premium",
Details: "A subscription detail",
MonthlyFee: 12.0,
HourlyRate: 14.0,
},
}
}
func customizeSubscription(customize func(MembershipData) MembershipData) MembershipData {
subscription := getBaseSubscription()
return customize(subscription)
}
func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
return []RegisterSubscriptionTest{
{
Name: "Missing details should fail",
WantResponse: http.StatusBadRequest,
WantDBData: map[string]interface{}{"name": "Just a Subscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Details = ""
return subscription
})),
},
{
Name: "Missing model name should fail",
WantResponse: http.StatusBadRequest,
WantDBData: map[string]interface{}{"name": ""},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = ""
return subscription
})),
},
{
Name: "Negative monthly fee should fail",
WantResponse: http.StatusBadRequest,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: false,
Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
sub.Subscription.MonthlyFee = -10.0
return sub
})),
},
{
Name: "Negative hourly rate should fail",
WantResponse: http.StatusBadRequest,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: false,
Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
sub.Subscription.HourlyRate = -1.0
return sub
})),
},
{
Name: "correct entry should pass",
WantResponse: http.StatusCreated,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Conditions = "Some Condition"
subscription.Subscription.IncludedPerYear = 0
subscription.Subscription.IncludedPerMonth = 1
return subscription
})),
},
{
Name: "Duplicate subscription name should fail",
WantResponse: http.StatusConflict,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: true, // The original subscription should still exist
Input: GenerateInputJSON(getBaseSubscription()),
},
}
}
func getSubscriptionUpdateData() []UpdateSubscriptionTest {
return []UpdateSubscriptionTest{
{
Name: "Modified Monthly Fee, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium", "monthly_fee": "12"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.MonthlyFee = 123.0
return subscription
})),
},
{
Name: "Missing ID, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.ID = 0
return subscription
})),
},
{
Name: "Modified Hourly Rate, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium", "hourly_rate": "14"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.HourlyRate = 3254.0
return subscription
})),
},
{
Name: "IncludedPerYear changed, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium", "included_per_year": "0"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.IncludedPerYear = 9873.0
return subscription
})),
},
{
Name: "IncludedPerMonth changed, should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "Premium", "included_per_month": "1"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.IncludedPerMonth = 23415.0
return subscription
})),
},
{
Name: "Update non-existent subscription should fail",
WantResponse: http.StatusNotAcceptable,
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = "NonExistentSubscription"
return subscription
})),
},
{
Name: "Correct Update should pass",
WantResponse: http.StatusAccepted,
WantDBData: map[string]interface{}{"name": "Premium", "details": "Altered Details"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Details = "Altered Details"
subscription.Subscription.Conditions = "Some Condition"
subscription.Subscription.IncludedPerYear = 0
subscription.Subscription.IncludedPerMonth = 1
return subscription
})),
},
}
}
func getSubscriptionDeleteData() []DeleteSubscriptionTest {
var premiumSub, basicSub models.SubscriptionModel
database.DB.Where("name = ?", "Premium").First(&premiumSub)
database.DB.Where("name = ?", "Basic").First(&basicSub)
logger.Error.Printf("premiumSub.ID: %v", premiumSub.ID)
logger.Error.Printf("basicSub.ID: %v", basicSub.ID)
return []DeleteSubscriptionTest{
{
Name: "Delete non-existent subscription should fail",
WantResponse: http.StatusExpectationFailed,
WantDBData: map[string]interface{}{"name": "NonExistentSubscription"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = "NonExistentSubscription"
subscription.Subscription.ID = basicSub.ID
return subscription
})),
},
{
Name: "Delete subscription without name should fail",
WantResponse: http.StatusExpectationFailed,
WantDBData: map[string]interface{}{"name": ""},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = ""
subscription.Subscription.ID = basicSub.ID
return subscription
})),
},
{
Name: "Delete subscription with users should fail",
WantResponse: http.StatusExpectationFailed,
WantDBData: map[string]interface{}{"name": "Basic"},
Assert: true,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = "Basic"
subscription.Subscription.ID = basicSub.ID
return subscription
})),
},
{
Name: "Delete valid subscription should succeed",
WantResponse: http.StatusOK,
WantDBData: map[string]interface{}{"name": "Premium"},
Assert: false,
Input: GenerateInputJSON(
customizeSubscription(func(subscription MembershipData) MembershipData {
subscription.Subscription.Name = "Premium"
subscription.Subscription.ID = premiumSub.ID
return subscription
})),
},
}
}

View File

@@ -0,0 +1,105 @@
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 changed password", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.PasswordAlreadyChanged)
} else if err.Error() == "record not found" {
utils.RespondWithError(c, err, "Couldn't find verification. This is most probably a outdated token.", http.StatusGone, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
} 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

@@ -0,0 +1,366 @@
package controllers
import (
"GoMembership/internal/config"
"GoMembership/internal/constants"
"GoMembership/internal/middlewares"
"GoMembership/internal/models"
"GoMembership/internal/services"
"GoMembership/internal/utils"
"GoMembership/internal/validation"
"fmt"
"strings"
"net/http"
"github.com/gin-gonic/gin"
"GoMembership/pkg/errors"
"GoMembership/pkg/logger"
)
type UserController struct {
Service services.UserServiceInterface
EmailService *services.EmailService
ConsentService services.ConsentServiceInterface
BankAccountService services.BankAccountServiceInterface
MembershipService services.MembershipServiceInterface
LicenceService services.LicenceInterface
}
type RegistrationData struct {
User models.User `json:"user"`
}
func (uc *UserController) CurrentUserHandler(c *gin.Context) {
requestUser, err := uc.ExtractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in CurrentUserHandler", http.StatusBadRequest, "general", "server.error.internal_server_error")
return
}
c.JSON(http.StatusOK, gin.H{
"user": requestUser.Safe(),
})
}
func (uc *UserController) GetAllUsers(c *gin.Context) {
requestUser, err := uc.ExtractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, "general", "server.validation.no_auth_tokenw")
return
}
if requestUser.RoleID == constants.Roles.Member {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update user", http.StatusForbidden, "user.user", "server.error.unauthorized")
return
}
users, err := uc.Service.GetUsers(nil)
if err != nil {
utils.RespondWithError(c, err, "Error getting users in GetAllUsers", http.StatusInternalServerError, "user.user", "server.error.internal_server_error")
return
}
// Create a slice to hold the safe user representations
safeUsers := make([]map[string]interface{}, len(*users))
// Convert each user to its safe representation
for i, user := range *users {
safeUsers[i] = user.Safe()
}
c.JSON(http.StatusOK, gin.H{
"users": users,
})
}
func (uc *UserController) UpdateHandler(c *gin.Context) {
// 1. Extract and validate the user ID from the route
requestUser, err := uc.ExtractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, "general", "server.validation.no_auth_tokenw")
return
}
var user models.User
var updateData RegistrationData
if err := c.ShouldBindJSON(&updateData); err != nil {
utils.HandleValidationError(c, err)
return
}
user = updateData.User
if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) && user.ID != requestUser.ID {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update user", http.StatusUnauthorized, "user.user", "server.error.unauthorized")
return
}
existingUser, err := uc.Service.GetUserByID(user.ID)
if err != nil {
utils.RespondWithError(c, err, "Error finding an existing user", http.StatusNotFound, "user.user", "server.error.not_found")
return
}
// user.Membership.ID = existingUser.Membership.ID
// user.MembershipID = existingUser.MembershipID
// if existingUser.Licence != nil {
// user.Licence.ID = existingUser.Licence.ID
// }
// user.LicenceID = existingUser.LicenceID
// user.BankAccount.ID = existingUser.BankAccount.ID
// user.BankAccountID = existingUser.BankAccountID
if requestUser.RoleID <= constants.Priviliges.View {
existingUser.Password = ""
if err := utils.FilterAllowedStructFields(&user, existingUser, constants.MemberUpdateFields, ""); err != nil {
if err.Error() == "Not authorized" {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Trying to update unauthorized fields", http.StatusUnauthorized, "user.user", "server.error.unauthorized")
return
}
utils.RespondWithError(c, err, "Error filtering users input fields", http.StatusInternalServerError, "user.user", "server.error.internal_server_error")
return
}
}
updatedUser, err := uc.Service.UpdateUser(&user)
if err != nil {
utils.HandleUserUpdateError(c, err)
return
}
logger.Info.Printf("User %d updated successfully by user %d", updatedUser.ID, requestUser.ID)
c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser.Safe()})
}
func (uc *UserController) DeleteUser(c *gin.Context) {
requestUser, err := uc.ExtractUserFromContext(c)
if err != nil {
utils.RespondWithError(c, err, "Error extracting user from context in DeleteUser", http.StatusBadRequest, "general", "server.validation.no_auth_tokenw")
return
}
type deleteData struct {
User struct {
ID uint `json:"id"`
LastName string `json:"last_name"`
} `json:"user"`
}
var data deleteData
if err := c.ShouldBindJSON(&data); err != nil {
utils.HandleValidationError(c, err)
return
}
if !utils.HasPrivilige(requestUser, constants.Priviliges.Delete) && data.User.ID != requestUser.ID {
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to delete user", http.StatusForbidden, "user.user", "server.error.unauthorized")
return
}
logger.Error.Printf("Deleting user: %v", data.User)
if err := uc.Service.DeleteUser(data.User.LastName, data.User.ID); err != nil {
utils.RespondWithError(c, err, "Error during user deletion", http.StatusInternalServerError, "user.user", "server.error.internal_server_error")
return
}
c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
}
func (uc *UserController) ExtractUserFromContext(c *gin.Context) (*models.User, error) {
tokenString, err := c.Cookie("jwt")
if err != nil {
return nil, err
}
_, claims, err := middlewares.ExtractContentFrom(tokenString)
if err != nil {
return nil, err
}
jwtUserID := uint((*claims)["user_id"].(float64))
user, err := uc.Service.GetUserByID(jwtUserID)
if err != nil {
return nil, err
}
return user, nil
}
func (uc *UserController) LogoutHandler(c *gin.Context) {
tokenString, err := c.Cookie("jwt")
if err != nil {
logger.Error.Printf("unable to get token from cookie: %#v", err)
}
middlewares.InvalidateSession(tokenString)
c.SetCookie("jwt", "", -1, "/", "", true, true)
c.JSON(http.StatusOK, gin.H{"message": "Logged out successfully"})
}
func (uc *UserController) LoginHandler(c *gin.Context) {
var input struct {
Email string `json:"email"`
Password string `json:"password"`
}
if err := c.ShouldBindJSON(&input); err != nil {
utils.RespondWithError(c, err, "Invalid JSON or malformed request", http.StatusBadRequest, errors.Responses.Fields.General, errors.Responses.Keys.Invalid)
return
}
user, err := uc.Service.GetUserByEmail(input.Email)
if err != nil {
utils.RespondWithError(c, err, "Login Error; user not found", http.StatusNotFound,
errors.Responses.Fields.Login,
errors.Responses.Keys.UserNotFoundWrongPassword)
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)
if err != nil {
utils.RespondWithError(c, err, "Login Error; password comparisson failed", http.StatusInternalServerError, errors.Responses.Fields.Login, errors.Responses.Keys.InternalServerError)
return
}
if !ok {
utils.RespondWithError(c, fmt.Errorf("%v %v(%v)", user.FirstName, user.LastName, user.Email),
"Login Error; wrong password",
http.StatusNotAcceptable,
errors.Responses.Fields.Login,
errors.Responses.Keys.UserNotFoundWrongPassword)
return
}
logger.Error.Printf("jwtsecret: %v", config.Auth.JWTSecret)
token, err := middlewares.GenerateToken(config.Auth.JWTSecret, user, "")
if err != nil {
utils.RespondWithError(c, err, "Error generating token in LoginHandler", http.StatusInternalServerError, errors.Responses.Fields.Login, errors.Responses.Keys.JwtGenerationFailed)
return
}
utils.SetCookie(c, token)
c.JSON(http.StatusOK, gin.H{
"message": "Login successful",
})
}
func (uc *UserController) RegisterUser(c *gin.Context) {
var regData RegistrationData
logger.Error.Printf("registering user...")
if err := c.ShouldBindJSON(&regData); err != nil {
utils.HandleValidationError(c, err)
return
}
logger.Info.Printf("Registering user %v", regData.User.Email)
selectedModel, err := uc.MembershipService.GetSubscriptionByName(&regData.User.Membership.SubscriptionModel.Name)
if err != nil {
utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, "subscription_model", "server.validation.subscription_model_not_found")
return
}
regData.User.Membership.SubscriptionModel = *selectedModel
if selectedModel.RequiredMembershipField != "" {
if err := validation.CheckParentMembershipID(regData.User.Membership); err != nil {
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't check parent membership id", http.StatusBadRequest, "parent_membership_id", "server.validation.parent_membership_id_not_found")
return
}
}
regData.User.RoleID = constants.Roles.Member
// Register User
id, token, err := uc.Service.RegisterUser(&regData.User)
if err != nil {
logger.Error.Printf("Couldn't register User(%v): %v", regData.User.Email, err)
if strings.Contains(err.Error(), "UNIQUE constraint failed: users.email") {
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, "email", "server.validation.email_already_exists")
} else {
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, "general", "server.error.internal_server_error")
}
return
}
regData.User.ID = id
// Register Consents
var consents = [2]models.Consent{
{
FirstName: regData.User.FirstName,
LastName: regData.User.LastName,
Email: regData.User.Email,
ConsentType: "TermsOfService",
},
{
FirstName: regData.User.FirstName,
LastName: regData.User.LastName,
Email: regData.User.Email,
ConsentType: "Privacy",
},
}
for _, consent := range consents {
_, err = uc.ConsentService.RegisterConsent(&consent)
if err != nil {
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register consent", http.StatusInternalServerError, "general", "server.error.internal_server_error")
return
}
}
// Send notifications
if err := uc.EmailService.SendVerificationEmail(&regData.User, &token); err != nil {
logger.Error.Printf("Failed to send email verification email to user(%v): %v", regData.User.Email, err)
// Proceed without returning error since user registration is successful
// TODO Notify Admin
}
// Notify admin of new user registration
if err := uc.EmailService.SendRegistrationNotification(&regData.User); err != nil {
logger.Error.Printf("Failed to notify admin of new user(%v) registration: %v", regData.User.Email, err)
// Proceed without returning error since user registration is successful
// TODO Notify Admin
}
c.JSON(http.StatusCreated, gin.H{
"message": "Registration successuful",
"id": regData.User.ID,
})
}
func (uc *UserController) VerifyMailHandler(c *gin.Context) {
token := c.Query("token")
if token == "" {
logger.Error.Println("Missing token to verify mail")
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Missing token"})
return
}
verification, err := uc.Service.VerifyUser(&token, &constants.VerificationTypes.Email)
if err != nil {
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."})
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.VerifiedStatus
user.Verification = *verification
user.ID = verification.UserID
user.Password = ""
uc.Service.UpdateUser(user)
logger.Info.Printf("Verified User: %#v", user.Email)
uc.EmailService.SendWelcomeEmail(user)
c.HTML(http.StatusOK, "verification_success.html", gin.H{"FirstName": user.FirstName})
}

File diff suppressed because it is too large Load Diff