diff --git a/internal/controllers/membershipController.go b/internal/controllers/membershipController.go index c25b711..e68a095 100644 --- a/internal/controllers/membershipController.go +++ b/internal/controllers/membershipController.go @@ -1,7 +1,6 @@ package controllers import ( - "GoMembership/internal/config" "GoMembership/internal/models" "GoMembership/internal/services" @@ -28,19 +27,6 @@ func (mc *MembershipController) RegisterSubscription(c *gin.Context) { c.JSON(http.StatusBadRequest, gin.H{"error": "Couldn't decode subscription data"}) } - logger.Info.Printf("Using API key: %v", config.Auth.APIKEY) - if regData.APIKey == "" { - logger.Error.Println("API Key is missing") - c.JSON(http.StatusUnauthorized, "API Key is missing") - return - } - - if regData.APIKey != config.Auth.APIKEY { - logger.Error.Printf("API Key not valid: %v", regData.APIKey) - c.JSON(http.StatusUnauthorized, "API Key is missing") - return - } - // Register Subscription id, err := mc.Service.RegisterSubscription(®Data.Model) if err != nil { diff --git a/internal/middlewares/api.go b/internal/middlewares/api.go new file mode 100644 index 0000000..5370efa --- /dev/null +++ b/internal/middlewares/api.go @@ -0,0 +1,31 @@ +package middlewares + +import ( + "crypto/subtle" + "net/http" + + "github.com/gin-gonic/gin" + + "GoMembership/internal/config" +) + +func APIKeyMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + clientAPIKey := c.GetHeader("X-API-Key") + + if clientAPIKey == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "API key is missing"}) + c.Abort() + return + } + + // Using subtle.ConstantTimeCompare to mitigate timing attacks + if subtle.ConstantTimeCompare([]byte(clientAPIKey), []byte(config.Auth.APIKEY)) != 1 { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid API key"}) + c.Abort() + return + } + + c.Next() + } +} diff --git a/internal/middlewares/api_test.go b/internal/middlewares/api_test.go new file mode 100644 index 0000000..79737b2 --- /dev/null +++ b/internal/middlewares/api_test.go @@ -0,0 +1,61 @@ +package middlewares + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + + "GoMembership/internal/config" +) + +func TestAPIKeyMiddleware(t *testing.T) { + // Set up a test API key + testAPIKey := "test-api-key-12345" + config.Auth.APIKEY = testAPIKey + + // Set Gin to Test Mode + gin.SetMode(gin.TestMode) + + // Tests table + tests := []struct { + name string + apiKey string + wantStatus int + }{ + {"Valid API Key", testAPIKey, http.StatusOK}, + {"Missing API Key", "", http.StatusUnauthorized}, + {"Invalid API Key", "wrong-key", http.StatusUnauthorized}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set up a new test router and handler + router := gin.New() + router.Use(APIKeyMiddleware()) + router.GET("/test", func(c *gin.Context) { + c.Status(http.StatusOK) + }) + + // Create a test request + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/test", nil) + if tt.apiKey != "" { + req.Header.Set("X-API-Key", tt.apiKey) + } + + // Serve the request + router.ServeHTTP(w, req) + + // Assert the response + assert.Equal(t, tt.wantStatus, w.Code) + + // Additional assertions for specific cases + if tt.wantStatus == http.StatusUnauthorized { + assert.Contains(t, w.Body.String(), "API key") + } + }) + } +} diff --git a/internal/routes/routes.go b/internal/routes/routes.go index 7aa9640..cdaa203 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -8,11 +8,20 @@ import ( ) func RegisterRoutes(router *gin.Engine, userController *controllers.UserController, membershipcontroller *controllers.MembershipController, contactController *controllers.ContactController) { - router.GET("/backend/verify", userController.VerifyMailHandler) - router.POST("/backend/api/register", userController.RegisterUser) - router.POST("/backend/api/register/subscription", membershipcontroller.RegisterSubscription) - router.POST("/backend/api/contact", contactController.RelayContactRequest) - // router.HandleFunc("/login", userController.LoginUser).Methods("POST") + router.GET("/verify", userController.VerifyMailHandler) + router.POST("/h/register", userController.RegisterUser) + router.POST("/h/contact", contactController.RelayContactRequest) router.POST("/csp-report", middlewares.CSPReportHandling) + + // create subrouter for teh authenticated area /account + // also pthprefix matches everything below /account + // accountRouter := router.PathPrefix("/account").Subrouter() + // accountRouter.Use(middlewares.AuthMiddleware) + //create api key required router + apiRouter := router.Group("/api") + { + router.POST("/subscription", membershipcontroller.RegisterSubscription) + } + apiRouter.Use(middlewares.APIKeyMiddleware()) } diff --git a/internal/server/server.go b/internal/server/server.go index e05bbf2..fe7aa4b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -61,10 +61,6 @@ func Run() { router.Use(middlewares.RateLimitMiddleware(limiter)) routes.RegisterRoutes(router, userController, membershipController, contactController) - // create subrouter for teh authenticated area /account - // also pthprefix matches everything below /account - // accountRouter := router.PathPrefix("/account").Subrouter() - // accountRouter.Use(middlewares.AuthMiddleware) logger.Info.Println("Starting server on :8080") srv = &http.Server{ diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index f133876..f8dc228 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -3,13 +3,16 @@ package errors import "errors" var ( - ErrNotFound = errors.New("not found") - ErrUserNotFound = errors.New("user not found") - ErrInvalidEmail = errors.New("invalid email") - ErrInvalidCredentials = errors.New("invalid credentials: unauthorized") - ErrAlreadyVerified = errors.New("user is already verified") - ErrTokenNotFound = errors.New("verification token not found") - ErrTokenNotSet = errors.New("verification token has not been set") - ErrNoData = errors.New("no data provided") - ErrNoRowsAffected = errors.New("no rows affected") + ErrNotFound = errors.New("not found") + ErrUserNotFound = errors.New("user not found") + ErrInvalidEmail = errors.New("invalid email") + ErrInvalidCredentials = errors.New("invalid credentials: unauthorized") + ErrAlreadyVerified = errors.New("user is already verified") + ErrTokenNotFound = errors.New("verification token not found") + ErrTokenNotSet = errors.New("verification token has not been set") + ErrNoData = errors.New("no data provided") + ErrNoRowsAffected = errors.New("no rows affected") + ErrValueTooLong = errors.New("cookie value too long") + ErrInvalidValue = errors.New("invalid cookie value") + ErrInvalidSigningAlgorithm = errors.New("invalid signing algorithm") ) diff --git a/templates/email/mail_verification.tmpl b/templates/email/mail_verification.tmpl index 17e4706..4b0b452 100644 --- a/templates/email/mail_verification.tmpl +++ b/templates/email/mail_verification.tmpl @@ -8,7 +8,7 @@ noch Ihre Emailadresse indem Sie hier klicken: E-Mail Adresse bestätigen - {{.BASEURL}}/backend/verify?token={{.Token}} + {{.BASEURL}}/verify?token={{.Token}} Nachdem wir Ihre E-Mail Adresse bestätigen konnten, schicken wir Ihnen alle weiteren Informationen zu. Wir freuen uns auf die @@ -94,7 +94,7 @@ Der Vorstand