From 7e0c1e71def50301b695f51ef12db6719641cf8a Mon Sep 17 00:00:00 2001 From: nquidox Date: Mon, 7 Jul 2025 17:46:31 +0300 Subject: [PATCH] added: auth middleware --- internal/router/handler.go | 29 ++++++++++++++-- internal/router/helper.go | 64 +++++++++++++++++++++++++++++++++++ internal/router/middleware.go | 57 +++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 3 deletions(-) create mode 100644 internal/router/helper.go create mode 100644 internal/router/middleware.go diff --git a/internal/router/handler.go b/internal/router/handler.go index b916349..09f18b2 100644 --- a/internal/router/handler.go +++ b/internal/router/handler.go @@ -6,18 +6,22 @@ import ( swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "merch-parser-api/internal/interfaces" + "merch-parser-api/internal/shared" "net/http" ) type router struct { - apiPrefix string - engine *gin.Engine - ginMode string + apiPrefix string + engine *gin.Engine + ginMode string + excludeRoutes map[string]shared.ExcludeRoute + tokenProv interfaces.JWTProvider } type Deps struct { ApiPrefix string GinMode string + TokenProv interfaces.JWTProvider } func NewRouter(deps Deps) interfaces.Router { @@ -35,6 +39,7 @@ func NewRouter(deps Deps) interfaces.Router { return &router{ apiPrefix: deps.ApiPrefix, engine: engine, + tokenProv: deps.TokenProv, } } @@ -49,5 +54,23 @@ func (r *router) Set() *gin.Engine { //swagger r.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + r.engine.Use(authMiddleware(mwDeps{ + prefix: r.apiPrefix, + excludeRoutes: &r.excludeRoutes, + tokenProv: r.tokenProv, + })) + return r.engine } + +func (r *router) ExcludeRoutes(routes []shared.ExcludeRoute) { + log.Debug("Excluded routes:") + excludedRoutes := make(map[string]shared.ExcludeRoute, len(routes)) + + for _, route := range routes { + log.Debugf("%s %s", route.Method, route.Route) + ex := joinPaths(r.apiPrefix, route.Route) + excludedRoutes[ex] = shared.ExcludeRoute{Route: ex, Method: route.Method} + } + r.excludeRoutes = excludedRoutes +} diff --git a/internal/router/helper.go b/internal/router/helper.go new file mode 100644 index 0000000..64fae83 --- /dev/null +++ b/internal/router/helper.go @@ -0,0 +1,64 @@ +package router + +import ( + "fmt" + "merch-parser-api/internal/shared" + "net/http" + "strings" +) + +func excluded(prefix, path, method string, excludedRoutes *map[string]shared.ExcludeRoute) bool { + if excludedRoutes == nil { + return false + } + + nPath := normalizePath(path) + + switch { + case nPath == "/"+prefix && method == http.MethodGet: //ignore api base path for GET method + return true + case nPath == "/swagger/*any": + return true + case nPath == "/version": + return true + } + + if value, ok := (*excludedRoutes)[nPath]; ok { + fmt.Println(value, ok) + if isSameRoute(nPath, value.Route) && value.Method == method { + return true + } + } + + return false +} + +func normalizePath(path string) string { + return strings.TrimRight(path, "/") +} + +func joinPaths(paths ...string) string { + result := "" + for _, p := range paths { + if result == "" { + result = strings.TrimRight(p, "/") + } else { + result = result + "/" + strings.Trim(p, "/") + } + } + return "/" + strings.TrimLeft(result, "/") +} + +func isSameRoute(path, excluded string) bool { + path = strings.Split(path, "?")[0] + path = strings.TrimSuffix(path, "/") + excluded = strings.TrimSuffix(excluded, "/") + + if strings.HasSuffix(excluded, "/*any") || strings.HasSuffix(excluded, "/*") { + base := strings.TrimSuffix(excluded, "/*any") + base = strings.TrimSuffix(base, "/*") + return strings.HasPrefix(path, base) + } + + return path == excluded +} diff --git a/internal/router/middleware.go b/internal/router/middleware.go new file mode 100644 index 0000000..f5c088f --- /dev/null +++ b/internal/router/middleware.go @@ -0,0 +1,57 @@ +package router + +import ( + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + "merch-parser-api/internal/interfaces" + "merch-parser-api/internal/shared" + "merch-parser-api/pkg/responses" + "net/http" +) + +type mwDeps struct { + prefix string + excludeRoutes *map[string]shared.ExcludeRoute + tokenProv interfaces.JWTProvider +} + +func authMiddleware(deps mwDeps) gin.HandlerFunc { + return func(c *gin.Context) { + if excluded(deps.prefix, c.FullPath(), c.Request.Method, deps.excludeRoutes) { + log.WithField("msg", "route excluded from auth check").Info("MW | Authorization") + c.Next() + return + } + + token := c.GetHeader("Authorization") + if token == "" { + c.JSON(http.StatusUnauthorized, responses.ErrorResponse401{Error: "Authorization token is required"}) + log.WithField("msg", "Authorization token is required").Error("MW | Authorization") + c.Abort() + return + } + + userUuid, refreshUuid, err := deps.tokenProv.Parse(token) + if err != nil { + c.JSON(http.StatusUnauthorized, responses.ErrorResponse401{Error: err.Error()}) + log.WithField("msg", "error parsing jwt").Error("MW | Authorization") + c.Abort() + return + } + + c.Set("userUuid", userUuid) + if refreshUuid != "" { + c.Set("refreshUuid", refreshUuid) + } + + log.WithFields(log.Fields{ + "userUuid": userUuid, + "refreshUuid": refreshUuid, + }).Debug("MW | Parsed uuids") + + if !c.IsAborted() { + log.WithField("msg", "context aborted").Info("MW | Authorization") + c.Next() + } + } +}