Compare commits
7 Commits
82413499f5
...
f99ff57275
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f99ff57275 | ||
|
|
c36af961f3 | ||
|
|
b3dc134c8c | ||
|
|
066419e546 | ||
|
|
ff7c83671f | ||
|
|
ef5e771998 | ||
|
|
8f3d73af90 |
@@ -18,7 +18,7 @@ func main() {
|
|||||||
|
|
||||||
config.LoadConfig()
|
config.LoadConfig()
|
||||||
|
|
||||||
err := database.Open(config.DB.Path)
|
err := database.Open(config.DB.Path, config.Recipients.AdminEmail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Fatalf("Couldn't init database: %v", err)
|
logger.Error.Fatalf("Couldn't init database: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"site": {
|
||||||
"WebsiteTitle": "My Carsharing Site",
|
"WebsiteTitle": "My Carsharing Site",
|
||||||
"BaseURL": "https://domain.de",
|
"BaseUrl": "https://domain.de",
|
||||||
|
"AllowOrigins": "https://domain.de"
|
||||||
|
},
|
||||||
"Environment": "dev",
|
"Environment": "dev",
|
||||||
"db": {
|
"db": {
|
||||||
"Path": "data/db.sqlite3"
|
"Path": "data/db.sqlite3"
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ type DatabaseConfig struct {
|
|||||||
Path string `json:"Path" default:"data/db.sqlite3" envconfig:"DB_PATH"`
|
Path string `json:"Path" default:"data/db.sqlite3" envconfig:"DB_PATH"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SiteConfig struct {
|
||||||
|
AllowOrigins string `json:"AllowOrigins" envconfig:"ALLOW_ORIGINS"`
|
||||||
|
WebsiteTitle string `json:"WebsiteTitle" envconfig:"WEBSITE_TITLE"`
|
||||||
|
BaseURL string `json:"BaseUrl" envconfig:"BASE_URL"`
|
||||||
|
}
|
||||||
type AuthenticationConfig struct {
|
type AuthenticationConfig struct {
|
||||||
JWTSecret string
|
JWTSecret string
|
||||||
CSRFSecret string
|
CSRFSecret string
|
||||||
@@ -57,11 +62,10 @@ type SecurityConfig struct {
|
|||||||
}
|
}
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Auth AuthenticationConfig `json:"auth"`
|
Auth AuthenticationConfig `json:"auth"`
|
||||||
|
Site SiteConfig `json:"site"`
|
||||||
Templates TemplateConfig `json:"templates"`
|
Templates TemplateConfig `json:"templates"`
|
||||||
Recipients RecipientsConfig `json:"recipients"`
|
Recipients RecipientsConfig `json:"recipients"`
|
||||||
ConfigFilePath string `json:"config_file_path" envconfig:"CONFIG_FILE_PATH"`
|
ConfigFilePath string `json:"config_file_path" envconfig:"CONFIG_FILE_PATH"`
|
||||||
WebsiteTitle string `json:"WebsiteTitle" envconfig:"WEBSITE_TITLE"`
|
|
||||||
BaseURL string `json:"BaseUrl" envconfig:"BASE_URL"`
|
|
||||||
Env string `json:"Environment" default:"development" envconfig:"ENV"`
|
Env string `json:"Environment" default:"development" envconfig:"ENV"`
|
||||||
DB DatabaseConfig `json:"db"`
|
DB DatabaseConfig `json:"db"`
|
||||||
SMTP SMTPConfig `json:"smtp"`
|
SMTP SMTPConfig `json:"smtp"`
|
||||||
@@ -69,8 +73,7 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
BaseURL string
|
Site SiteConfig
|
||||||
WebsiteTitle string
|
|
||||||
CFGPath string
|
CFGPath string
|
||||||
CFG Config
|
CFG Config
|
||||||
Auth AuthenticationConfig
|
Auth AuthenticationConfig
|
||||||
@@ -115,11 +118,10 @@ func LoadConfig() {
|
|||||||
DB = CFG.DB
|
DB = CFG.DB
|
||||||
Templates = CFG.Templates
|
Templates = CFG.Templates
|
||||||
SMTP = CFG.SMTP
|
SMTP = CFG.SMTP
|
||||||
BaseURL = CFG.BaseURL
|
|
||||||
Recipients = CFG.Recipients
|
Recipients = CFG.Recipients
|
||||||
Security = CFG.Security
|
Security = CFG.Security
|
||||||
Env = CFG.Env
|
Env = CFG.Env
|
||||||
WebsiteTitle = CFG.WebsiteTitle
|
Site = CFG.Site
|
||||||
logger.Info.Printf("Config loaded: %#v", CFG)
|
logger.Info.Printf("Config loaded: %#v", CFG)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,9 +44,6 @@ var (
|
|||||||
|
|
||||||
func TestSuite(t *testing.T) {
|
func TestSuite(t *testing.T) {
|
||||||
_ = deleteTestDB("test.db")
|
_ = deleteTestDB("test.db")
|
||||||
if err := database.Open("test.db"); err != nil {
|
|
||||||
log.Fatalf("Failed to create DB: %#v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,6 +74,9 @@ func TestSuite(t *testing.T) {
|
|||||||
log.Fatalf("Error setting environment variable: %v", err)
|
log.Fatalf("Error setting environment variable: %v", err)
|
||||||
}
|
}
|
||||||
config.LoadConfig()
|
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)
|
utils.SMTPStart(Host, Port)
|
||||||
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password)
|
emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password)
|
||||||
var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
|
var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
|
||||||
|
|||||||
@@ -25,16 +25,18 @@ type UserController struct {
|
|||||||
|
|
||||||
type RegistrationData struct {
|
type RegistrationData struct {
|
||||||
User models.User `json:"user"`
|
User models.User `json:"user"`
|
||||||
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserController) CurrentUserHandler(c *gin.Context) {
|
func (uc *UserController) CurrentUserHandler(c *gin.Context) {
|
||||||
userID, err := middlewares.GetUserIDFromContext(c)
|
userIDString, ok := c.Get("user_id")
|
||||||
if err != nil {
|
if !ok || userIDString == nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Failed to authenticate user"})
|
logger.Error.Printf("Error getting user_id from header")
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
user, err := uc.Service.GetUserByID(userID)
|
userID := userIDString.(float64)
|
||||||
|
logger.Error.Printf("UserIDINt64: %v", userID)
|
||||||
|
logger.Error.Printf("c.Get Value: %v", userIDString)
|
||||||
|
user, err := uc.Service.GetUserByID(int64(userID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error.Printf("Error retrieving valid user: %v", err)
|
logger.Error.Printf("Error retrieving valid user: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving user."})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving user."})
|
||||||
@@ -124,6 +126,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
|
|||||||
// logger.Info.Printf("REGISTERING user: %#v", regData.User)
|
// logger.Info.Printf("REGISTERING user: %#v", regData.User)
|
||||||
|
|
||||||
regData.User.RoleID = constants.Roles.Member
|
regData.User.RoleID = constants.Roles.Member
|
||||||
|
regData.User.Password = regData.Password
|
||||||
|
|
||||||
// Register User
|
// Register User
|
||||||
id, token, err := uc.Service.RegisterUser(®Data.User)
|
id, token, err := uc.Service.RegisterUser(®Data.User)
|
||||||
|
|||||||
@@ -18,11 +18,17 @@ import (
|
|||||||
|
|
||||||
"GoMembership/internal/config"
|
"GoMembership/internal/config"
|
||||||
"GoMembership/internal/constants"
|
"GoMembership/internal/constants"
|
||||||
|
"GoMembership/internal/middlewares"
|
||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/internal/utils"
|
"GoMembership/internal/utils"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type loginInput struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
type RegisterUserTest struct {
|
type RegisterUserTest struct {
|
||||||
WantDBData map[string]interface{}
|
WantDBData map[string]interface{}
|
||||||
Name string
|
Name string
|
||||||
@@ -61,11 +67,13 @@ func testUserController(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
testLoginUser(t)
|
testCurrentUserHandler(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoginUser(t *testing.T) {
|
func testLoginUser(t *testing.T) (string, http.Cookie) {
|
||||||
// This test should run after the user registration test
|
// This test should run after the user registration test
|
||||||
|
var loginCookie http.Cookie
|
||||||
|
var loginInput loginInput
|
||||||
t.Run("LoginUser", func(t *testing.T) {
|
t.Run("LoginUser", func(t *testing.T) {
|
||||||
// Test cases
|
// Test cases
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -104,6 +112,9 @@ func testLoginUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
logger.Error.Print("==============================================================")
|
||||||
|
logger.Error.Printf("Testing : %v", tt.name)
|
||||||
|
logger.Error.Print("==============================================================")
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
c, w, _ := GetMockedJSONContext([]byte(tt.input), "/login")
|
c, w, _ := GetMockedJSONContext([]byte(tt.input), "/login")
|
||||||
@@ -122,13 +133,113 @@ func testLoginUser(t *testing.T) {
|
|||||||
logger.Info.Printf("Response: %#v", response)
|
logger.Info.Printf("Response: %#v", response)
|
||||||
assert.Contains(t, response, "set-token")
|
assert.Contains(t, response, "set-token")
|
||||||
assert.NotEmpty(t, response["set-token"])
|
assert.NotEmpty(t, response["set-token"])
|
||||||
|
for _, cookie := range w.Result().Cookies() {
|
||||||
|
if cookie.Name == "jwt" {
|
||||||
|
loginCookie = *cookie
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(tt.input), &loginInput)
|
||||||
|
assert.NoError(t, err, "Failed to unmarshal input JSON")
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotEmpty(t, loginCookie)
|
||||||
} else {
|
} else {
|
||||||
assert.NotContains(t, response, "set-token")
|
assert.NotContains(t, response, "set-token")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return loginInput.Email, loginCookie
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCurrentUserHandler(t *testing.T) {
|
||||||
|
loginEmail, loginCookie := testLoginUser(t)
|
||||||
|
// This test should run after the user login test
|
||||||
|
invalidCookie := http.Cookie{
|
||||||
|
Name: "jwt",
|
||||||
|
Value: "invalid.token.here",
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setupCookie func(*http.Request)
|
||||||
|
expectedUserMail string
|
||||||
|
expectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "With valid cookie",
|
||||||
|
setupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(&loginCookie)
|
||||||
|
},
|
||||||
|
expectedUserMail: loginEmail,
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Without cookie",
|
||||||
|
setupCookie: func(req *http.Request) {},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "With invalid cookie",
|
||||||
|
setupCookie: func(req *http.Request) {
|
||||||
|
req.AddCookie(&invalidCookie)
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
logger.Error.Print("==============================================================")
|
||||||
|
logger.Error.Printf("Testing : %v", tt.name)
|
||||||
|
logger.Error.Print("==============================================================")
|
||||||
|
if tt.expectedStatus == http.StatusOK {
|
||||||
|
time.Sleep(time.Second) // Small delay to ensure different timestamps to get a different JWT token
|
||||||
|
}
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
router := gin.New()
|
||||||
|
router.Use(middlewares.AuthMiddleware())
|
||||||
|
router.GET("/current-user", Uc.CurrentUserHandler)
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/current-user", nil)
|
||||||
|
tt.setupCookie(req)
|
||||||
|
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedStatus, w.Code)
|
||||||
|
|
||||||
|
if tt.expectedStatus == http.StatusOK {
|
||||||
|
var response models.User
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, tt.expectedUserMail, response.Email)
|
||||||
|
var newCookie *http.Cookie
|
||||||
|
for _, cookie := range w.Result().Cookies() {
|
||||||
|
if cookie.Name == "jwt" {
|
||||||
|
newCookie = cookie
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NotNil(t, newCookie, "Cookie should be renewed")
|
||||||
|
assert.NotEqual(t, loginCookie.Value, newCookie.Value, "Cookie value should be different")
|
||||||
|
assert.True(t, newCookie.MaxAge > 0, "New cookie should not be expired")
|
||||||
|
} else {
|
||||||
|
// For unauthorized requests, check for an error message
|
||||||
|
var errorResponse map[string]string
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, errorResponse, "error")
|
||||||
|
assert.NotEmpty(t, errorResponse["error"])
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
func validateUser(assert bool, wantDBData map[string]interface{}) error {
|
||||||
users, err := Uc.Service.GetUsers(wantDBData)
|
users, err := Uc.Service.GetUsers(wantDBData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -195,14 +306,14 @@ func checkWelcomeMail(message *utils.Email, user *models.User) error {
|
|||||||
if !strings.Contains(message.Body, fmt.Sprintf("Mitgliedsnummer</strong>: %v", user.Membership.ID)) {
|
if !strings.Contains(message.Body, fmt.Sprintf("Mitgliedsnummer</strong>: %v", user.Membership.ID)) {
|
||||||
return fmt.Errorf("Users membership Id(%v) has not been rendered in registration mail.", user.Membership.ID)
|
return fmt.Errorf("Users membership Id(%v) has not been rendered in registration mail.", user.Membership.ID)
|
||||||
}
|
}
|
||||||
if !strings.Contains(message.Body, config.BaseURL) {
|
if !strings.Contains(message.Body, config.Site.BaseURL) {
|
||||||
return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.BaseURL)
|
return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.Site.BaseURL)
|
||||||
}
|
}
|
||||||
if !strings.Contains(message.Body, config.BaseURL+config.Templates.LogoURI) {
|
if !strings.Contains(message.Body, config.Site.BaseURL+config.Templates.LogoURI) {
|
||||||
return fmt.Errorf("Logo Url (%v) has not been rendered in registration mail.", config.BaseURL+config.WebsiteTitle)
|
return fmt.Errorf("Logo Url (%v) has not been rendered in registration mail.", config.Site.BaseURL+config.Site.WebsiteTitle)
|
||||||
}
|
}
|
||||||
if !strings.Contains(message.Body, config.WebsiteTitle) {
|
if !strings.Contains(message.Body, config.Site.WebsiteTitle) {
|
||||||
return fmt.Errorf("Website title (%v) has not been rendered in registration mail.", config.WebsiteTitle)
|
return fmt.Errorf("Website title (%v) has not been rendered in registration mail.", config.Site.WebsiteTitle)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -249,8 +360,8 @@ func checkRegistrationMail(message *utils.Email, user *models.User) error {
|
|||||||
if !strings.Contains(message.Body, user.BankAccount.IBAN) {
|
if !strings.Contains(message.Body, user.BankAccount.IBAN) {
|
||||||
return fmt.Errorf("Users IBAN(%v) has not been rendered in registration mail.", user.BankAccount.IBAN)
|
return fmt.Errorf("Users IBAN(%v) has not been rendered in registration mail.", user.BankAccount.IBAN)
|
||||||
}
|
}
|
||||||
if !strings.Contains(message.Body, config.BaseURL) {
|
if !strings.Contains(message.Body, config.Site.BaseURL) {
|
||||||
return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.BaseURL)
|
return fmt.Errorf("Base Url (%v) has not been rendered in registration mail.", config.Site.BaseURL)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -267,8 +378,8 @@ func checkVerificationMail(message *utils.Email, user *models.User) error {
|
|||||||
return fmt.Errorf("Users Verification link token(%v) has not been rendered in email verification mail. %v", user.Verification.VerificationToken, verificationURL)
|
return fmt.Errorf("Users Verification link token(%v) has not been rendered in email verification mail. %v", user.Verification.VerificationToken, verificationURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(message.Body, config.BaseURL) {
|
if !strings.Contains(message.Body, config.Site.BaseURL) {
|
||||||
return fmt.Errorf("Base Url (%v) has not been rendered in email verification mail.", config.BaseURL)
|
return fmt.Errorf("Base Url (%v) has not been rendered in email verification mail.", config.Site.BaseURL)
|
||||||
}
|
}
|
||||||
// open the provided link:
|
// open the provided link:
|
||||||
if err := verifyMail(verificationURL); err != nil {
|
if err := verifyMail(verificationURL); err != nil {
|
||||||
@@ -349,7 +460,7 @@ func getBaseUser() models.User {
|
|||||||
func customizeInput(customize func(models.User) models.User) *RegistrationData {
|
func customizeInput(customize func(models.User) models.User) *RegistrationData {
|
||||||
user := getBaseUser()
|
user := getBaseUser()
|
||||||
user = customize(user) // Apply the customization
|
user = customize(user) // Apply the customization
|
||||||
return &RegistrationData{User: user}
|
return &RegistrationData{User: user, Password: user.Password}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTestUsers() []RegisterUserTest {
|
func getTestUsers() []RegisterUserTest {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/base64"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alexedwards/argon2id"
|
"github.com/alexedwards/argon2id"
|
||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
var DB *gorm.DB
|
var DB *gorm.DB
|
||||||
|
|
||||||
func Open(dbPath string) error {
|
func Open(dbPath string, adminMail string) error {
|
||||||
|
|
||||||
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -34,12 +34,60 @@ func Open(dbPath string) error {
|
|||||||
DB = db
|
DB = db
|
||||||
|
|
||||||
logger.Info.Print("Opened DB")
|
logger.Info.Print("Opened DB")
|
||||||
if err := seedDatabase(); err != nil {
|
|
||||||
|
var count int64
|
||||||
|
db.Model(&models.User{}).Count(&count)
|
||||||
|
if count == 0 {
|
||||||
|
admin, err := seedAdmin(adminMail)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
result := db.Create(&admin)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Landing page to create an admin
|
||||||
|
func seedAdmin(userMail string) (*models.User, error) {
|
||||||
|
passwordBytes := make([]byte, 12)
|
||||||
|
_, err := rand.Read(passwordBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode into a URL-safe base64 string
|
||||||
|
password, err := base64.URLEncoding.EncodeToString(passwordBytes)[:12], nil
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Error.Print("==============================================================")
|
||||||
|
logger.Error.Printf("Admin Email: %v", userMail)
|
||||||
|
logger.Error.Printf("Admin Password: %v", password)
|
||||||
|
logger.Error.Print("==============================================================")
|
||||||
|
|
||||||
|
return &models.User{
|
||||||
|
FirstName: "ad",
|
||||||
|
LastName: "min",
|
||||||
|
DateOfBirth: time.Now(),
|
||||||
|
Password: hash,
|
||||||
|
Address: "Downhill 4",
|
||||||
|
ZipCode: "9999",
|
||||||
|
City: "TechTown",
|
||||||
|
Email: userMail,
|
||||||
|
Status: constants.ActiveStatus,
|
||||||
|
RoleID: constants.Roles.Editor,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func Close() error {
|
func Close() error {
|
||||||
logger.Info.Print("Closing DB")
|
logger.Info.Print("Closing DB")
|
||||||
db, err := DB.DB()
|
db, err := DB.DB()
|
||||||
@@ -48,42 +96,3 @@ func Close() error {
|
|||||||
}
|
}
|
||||||
return db.Close()
|
return db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func seedDatabase() error {
|
|
||||||
var count int64
|
|
||||||
DB.Model(&models.User{}).Count(&count)
|
|
||||||
if count == 0 {
|
|
||||||
bytes := make([]byte, 12)
|
|
||||||
_, err := rand.Read(bytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
password := hex.EncodeToString(bytes)
|
|
||||||
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
admin := models.User{
|
|
||||||
FirstName: "ad",
|
|
||||||
LastName: "min",
|
|
||||||
DateOfBirth: time.Now(),
|
|
||||||
Password: hash,
|
|
||||||
Address: "Downhill 4",
|
|
||||||
ZipCode: "9999",
|
|
||||||
City: "TechTown",
|
|
||||||
Status: constants.ActiveStatus,
|
|
||||||
RoleID: constants.Roles.Editor,
|
|
||||||
}
|
|
||||||
|
|
||||||
result := DB.Create(&admin)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
logger.Error.Print("==============================================================")
|
|
||||||
logger.Error.Printf("Admin Password: %v", password)
|
|
||||||
logger.Error.Print("==============================================================")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ func GenerateToken(userID int64) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func verifyToken(tokenString string) (*jwt.Token, error) {
|
func verifyToken(tokenString string) (*jwt.Token, error) {
|
||||||
|
if tokenString == "" {
|
||||||
|
return nil, fmt.Errorf("Authorization token is required")
|
||||||
|
}
|
||||||
token, err := jwtParser.Parse(tokenString, func(_ *jwt.Token) (interface{}, error) {
|
token, err := jwtParser.Parse(tokenString, func(_ *jwt.Token) (interface{}, error) {
|
||||||
return jwtKey, nil
|
return jwtKey, nil
|
||||||
})
|
})
|
||||||
@@ -36,55 +39,53 @@ func verifyToken(tokenString string) (*jwt.Token, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return token, nil
|
if !token.Valid {
|
||||||
}
|
return nil, fmt.Errorf("invalid token")
|
||||||
func GetUserIDFromContext(c *gin.Context) (int64, error) {
|
|
||||||
|
|
||||||
tokenString, err := c.Cookie("jwt")
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
logger.Error.Printf("Error getting cookie: %v\n", err)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if tokenString == "" {
|
|
||||||
logger.Error.Printf("Token is empty: %v\n", err)
|
|
||||||
return 0, fmt.Errorf("Authorization token is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := verifyToken(tokenString)
|
|
||||||
if err != nil || !token.Valid {
|
|
||||||
|
|
||||||
logger.Error.Printf("Token is invalid: %v\n", err)
|
|
||||||
return 0, fmt.Errorf("Token not valid!")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, ok := token.Claims.(jwt.MapClaims)
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
|
|
||||||
logger.Error.Printf("claims userid: %v", claims["user_id"].(float64))
|
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Error.Printf("Invalid Token claims")
|
return nil, fmt.Errorf("invalid token claims")
|
||||||
return 0, fmt.Errorf("Invalid token claims")
|
|
||||||
}
|
}
|
||||||
userID, ok := claims["user_id"].(float64)
|
|
||||||
|
|
||||||
|
exp, ok := claims["exp"].(float64)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid expiration claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, ok := claims["user_id"].(float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Error.Printf("Invalid user ID: %v", userID)
|
logger.Error.Printf("Invalid user ID: %v", userID)
|
||||||
return 0, fmt.Errorf("Invalid user ID")
|
return nil, fmt.Errorf("Invalid user ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
return int64(userID), nil
|
if time.Now().Unix() > int64(exp) {
|
||||||
|
return nil, fmt.Errorf("token expired")
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthMiddleware() gin.HandlerFunc {
|
func AuthMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
tokenString, err := c.Cookie("jwt")
|
||||||
userID, err := GetUserIDFromContext(c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
logger.Error.Printf("No Auth token: %v\n", err)
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "No Auth token"})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token, err := verifyToken(tokenString)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Printf("Token is invalid: %v\n", err)
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Auth token invalid"})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
claims, _ := token.Claims.(jwt.MapClaims)
|
||||||
|
userID, _ := claims["user_id"].(float64)
|
||||||
|
|
||||||
// Generate a new token
|
// Generate a new token
|
||||||
newToken, err := GenerateToken(int64(userID))
|
newToken, err := GenerateToken(int64(userID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ func TestAuthMiddleware(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
logger.Error.Print("==============================================================")
|
||||||
|
logger.Error.Printf("Testing : %v", tt.name)
|
||||||
|
logger.Error.Print("==============================================================")
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Setup
|
// Setup
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
func CORSMiddleware() gin.HandlerFunc {
|
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: []string{config.BaseURL, "http://localhost:8080"}, // Add your frontend URL(s)
|
AllowOrigins: []string{config.Site.AllowOrigins},
|
||||||
AllowMethods: []string{"GET", "POST"}, // "PUT", "PATCH", "DELETE", "OPTIONS"},
|
AllowMethods: []string{"GET", "POST"}, // "PUT", "PATCH", "DELETE", "OPTIONS"},
|
||||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With"},
|
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With"},
|
||||||
// ExposeHeaders: []string{"Content-Length"},
|
// ExposeHeaders: []string{"Content-Length"},
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ func TestCORSMiddleware(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Allowed origin",
|
name: "Allowed origin",
|
||||||
origin: config.BaseURL,
|
origin: config.Site.AllowOrigins,
|
||||||
expectedStatus: http.StatusOK,
|
expectedStatus: http.StatusOK,
|
||||||
expectedHeaders: map[string]string{
|
expectedHeaders: map[string]string{
|
||||||
"Access-Control-Allow-Origin": config.BaseURL,
|
"Access-Control-Allow-Origin": config.Site.AllowOrigins,
|
||||||
"Content-Type": "text/plain; charset=utf-8",
|
"Content-Type": "text/plain; charset=utf-8",
|
||||||
"Access-Control-Allow-Credentials": "true",
|
"Access-Control-Allow-Credentials": "true",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type User struct {
|
|||||||
Phone string `json:"phone" validate:"omitempty,omitnil"`
|
Phone string `json:"phone" validate:"omitempty,omitnil"`
|
||||||
Notes *string `json:"notes"`
|
Notes *string `json:"notes"`
|
||||||
FirstName string `gorm:"not null" json:"first_name" validate:"required"`
|
FirstName string `gorm:"not null" json:"first_name" validate:"required"`
|
||||||
Password string `json:"password" required_unless=RoleID 0`
|
Password string `json:"-" required_unless=RoleID 0`
|
||||||
Email string `gorm:"unique;not null" json:"email" validate:"required,email"`
|
Email string `gorm:"unique;not null" json:"email" validate:"required,email"`
|
||||||
LastName string `gorm:"not null" json:"last_name" validate:"required"`
|
LastName string `gorm:"not null" json:"last_name" validate:"required"`
|
||||||
ProfilePicture string `json:"profile_picture" validate:"omitempty,omitnil,image"`
|
ProfilePicture string `json:"profile_picture" validate:"omitempty,omitnil,image"`
|
||||||
@@ -27,7 +27,7 @@ type User struct {
|
|||||||
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
|
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
|
||||||
Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||||
Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"`
|
Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"`
|
||||||
ID int64 `gorm:"primaryKey"`
|
ID int64 `gorm:"primaryKey" json:"id"`
|
||||||
PaymentStatus int8 `json:"payment_status"`
|
PaymentStatus int8 `json:"payment_status"`
|
||||||
Status int8 `json:"status"`
|
Status int8 `json:"status"`
|
||||||
RoleID int8 `json:"role_id"`
|
RoleID int8 `json:"role_id"`
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll
|
|||||||
router.POST("/users/register", userController.RegisterUser)
|
router.POST("/users/register", userController.RegisterUser)
|
||||||
router.POST("/users/contact", contactController.RelayContactRequest)
|
router.POST("/users/contact", contactController.RelayContactRequest)
|
||||||
|
|
||||||
|
router.POST("/users/login", userController.LoginUser)
|
||||||
router.POST("/csp-report", middlewares.CSPReportHandling)
|
router.POST("/csp-report", middlewares.CSPReportHandling)
|
||||||
|
|
||||||
// create subrouter for teh authenticated area /account
|
// create subrouter for teh authenticated area /account
|
||||||
@@ -26,8 +27,8 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll
|
|||||||
apiRouter.Use(middlewares.APIKeyMiddleware())
|
apiRouter.Use(middlewares.APIKeyMiddleware())
|
||||||
|
|
||||||
authRouter := router.Group("/users/backend")
|
authRouter := router.Group("/users/backend")
|
||||||
{
|
|
||||||
router.POST("/currentUser", userController.CurrentUserHandler)
|
|
||||||
}
|
|
||||||
authRouter.Use(middlewares.AuthMiddleware())
|
authRouter.Use(middlewares.AuthMiddleware())
|
||||||
|
{
|
||||||
|
authRouter.POST("/currentUser", userController.CurrentUserHandler)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func (s *EmailService) SendVerificationEmail(user *models.User, token *string) e
|
|||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
Token: *token,
|
Token: *token,
|
||||||
BASEURL: config.BaseURL,
|
BASEURL: config.Site.BaseURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
subject := constants.MailVerificationSubject
|
subject := constants.MailVerificationSubject
|
||||||
@@ -102,8 +102,8 @@ func (s *EmailService) SendWelcomeEmail(user *models.User) error {
|
|||||||
MembershipID: user.Membership.ID,
|
MembershipID: user.Membership.ID,
|
||||||
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
|
MembershipFee: float32(user.Membership.SubscriptionModel.MonthlyFee),
|
||||||
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
|
RentalFee: float32(user.Membership.SubscriptionModel.HourlyRate),
|
||||||
BASEURL: config.BaseURL,
|
BASEURL: config.Site.BaseURL,
|
||||||
WebsiteTitle: config.WebsiteTitle,
|
WebsiteTitle: config.Site.WebsiteTitle,
|
||||||
Logo: config.Templates.LogoURI,
|
Logo: config.Templates.LogoURI,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +151,9 @@ func (s *EmailService) SendRegistrationNotification(user *models.User) error {
|
|||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Phone: user.Phone,
|
Phone: user.Phone,
|
||||||
IBAN: user.BankAccount.IBAN,
|
IBAN: user.BankAccount.IBAN,
|
||||||
BASEURL: config.BaseURL,
|
BASEURL: config.Site.BaseURL,
|
||||||
Logo: config.Templates.LogoURI,
|
Logo: config.Templates.LogoURI,
|
||||||
WebsiteTitle: config.WebsiteTitle,
|
WebsiteTitle: config.Site.WebsiteTitle,
|
||||||
}
|
}
|
||||||
|
|
||||||
subject := constants.MailRegistrationSubject
|
subject := constants.MailRegistrationSubject
|
||||||
@@ -175,9 +175,9 @@ func (s *EmailService) RelayContactFormMessage(sender string, name string, messa
|
|||||||
}{
|
}{
|
||||||
Message: message,
|
Message: message,
|
||||||
Name: name,
|
Name: name,
|
||||||
BASEURL: config.BaseURL,
|
BASEURL: config.Site.BaseURL,
|
||||||
Logo: config.Templates.LogoURI,
|
Logo: config.Templates.LogoURI,
|
||||||
WebsiteTitle: config.WebsiteTitle,
|
WebsiteTitle: config.Site.WebsiteTitle,
|
||||||
}
|
}
|
||||||
subject := constants.MailContactSubject
|
subject := constants.MailContactSubject
|
||||||
body, err := ParseTemplate("mail_contact_form.tmpl", data)
|
body, err := ParseTemplate("mail_contact_form.tmpl", data)
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import (
|
|||||||
"GoMembership/internal/database"
|
"GoMembership/internal/database"
|
||||||
"GoMembership/internal/models"
|
"GoMembership/internal/models"
|
||||||
"GoMembership/pkg/logger"
|
"GoMembership/pkg/logger"
|
||||||
"github.com/go-playground/validator/v10"
|
|
||||||
"github.com/jbub/banking/iban"
|
|
||||||
"github.com/jbub/banking/swift"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/jbub/banking/iban"
|
||||||
|
"github.com/jbub/banking/swift"
|
||||||
)
|
)
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user