-
Notifications
You must be signed in to change notification settings - Fork 0
feat: adding various middlewares for services #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
9e10073
feat(middleware): implement authentication and context storage
nexus49 01644fd
test: increased unit test coverage
nexus49 613ac7f
chore: address lint warnings
nexus49 77a5ab3
chore(middleware): exclude test support from coverage
nexus49 3dc15df
refactor(middleware): remove deprecated CreateMiddlewares function
nexus49 354b35f
docs(middleware): update JWT parsing documentation
nexus49 174e71a
📝 Add docstrings to `feat/middlewares` (#65)
coderabbitai[bot] 5cb4704
refactor(middleware): simplify CreateAuthMiddleware function
nexus49 4237a62
refactor(middleware): update context usage in StoreWebToken
nexus49 8a29af1
refactor(middleware): unexport the signatureAlgorithms variable
nexus49 48d891a
refactor(middleware): simplify CreateAuthMiddleware function
nexus49 4ca88f7
refactor(test): remove unused imports in authzMiddlewares_test.go
nexus49 382fea6
refactor(middleware): simplify authorization token parsing
nexus49 7bfce74
refactor(middleware): handle nil logger in StoreLoggerMiddleware
nexus49 4a86fd7
refactor(test): replace real looking token to avoid false-positive se…
nexus49 8982072
refactor: Improve auth middleware tests and add build constraints
nexus49 35d71b9
refactor(middleware): simplify auth middleware structure
nexus49 cf55d0c
Update middleware/auth.go
nexus49 ec597d7
Update middleware/requestid_test.go
nexus49 cbe5e72
Update middleware/token_test.go
nexus49 d01fc84
refactor(middleware): clean up auth middleware structure
nexus49 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,4 +4,5 @@ exclude: | |
| - mocks # exclude generated mock files | ||
| - ^test/ | ||
| - ^logger/testlogger | ||
| - middleware/test_support | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/go-http-utils/headers" | ||
|
|
||
| appctx "github.com/platform-mesh/golang-commons/context" | ||
| ) | ||
|
|
||
| // StoreAuthHeader returns HTTP middleware that reads the request's Authorization header and stores it in the request context. | ||
| // The middleware wraps a handler, extracts the Authorization header (using headers.Authorization), calls | ||
| // appctx.AddAuthHeaderToContext with the existing request context and the header value, and invokes the next handler | ||
| // with the request updated to use that context. If the Authorization header is absent or empty, nothing is stored. | ||
| func StoreAuthHeader() func(http.Handler) http.Handler { | ||
| return func(next http.Handler) http.Handler { | ||
| return http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) { | ||
| auth := request.Header.Get(headers.Authorization) | ||
| ctx := request.Context() | ||
| if auth != "" { | ||
| ctx = appctx.AddAuthHeaderToContext(ctx, auth) | ||
| } | ||
| next.ServeHTTP(responseWriter, request.WithContext(ctx)) | ||
| }) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| "github.com/go-http-utils/headers" | ||
| "github.com/platform-mesh/golang-commons/context" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestStoreAuthHeader_WithAuthHeader(t *testing.T) { | ||
| expectedAuth := "Bearer token123" | ||
|
|
||
| nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| // Verify auth header is stored in context | ||
| authFromContext, err := context.GetAuthHeaderFromContext(r.Context()) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, expectedAuth, authFromContext) | ||
|
|
||
| w.WriteHeader(http.StatusOK) | ||
| }) | ||
|
|
||
| middleware := StoreAuthHeader() | ||
| handlerToTest := middleware(nextHandler) | ||
|
|
||
| req := httptest.NewRequest("GET", "http://testing", nil) | ||
| req.Header.Set(headers.Authorization, expectedAuth) | ||
| recorder := httptest.NewRecorder() | ||
|
|
||
| handlerToTest.ServeHTTP(recorder, req) | ||
|
|
||
| assert.Equal(t, http.StatusOK, recorder.Code) | ||
| } | ||
|
|
||
| func TestStoreAuthHeader_WithoutAuthHeader(t *testing.T) { | ||
| nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| // Verify empty auth header returns error when not set | ||
| _, err := context.GetAuthHeaderFromContext(r.Context()) | ||
| assert.Error(t, err) // Should return error when no auth header is set | ||
|
|
||
| w.WriteHeader(http.StatusOK) | ||
| }) | ||
|
|
||
| middleware := StoreAuthHeader() | ||
| handlerToTest := middleware(nextHandler) | ||
|
|
||
| req := httptest.NewRequest("GET", "http://testing", nil) | ||
| // No authorization header set | ||
| recorder := httptest.NewRecorder() | ||
|
|
||
| handlerToTest.ServeHTTP(recorder, req) | ||
|
|
||
| assert.Equal(t, http.StatusOK, recorder.Code) | ||
| } | ||
|
|
||
| func TestStoreAuthHeader_WithEmptyAuthHeader(t *testing.T) { | ||
| nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| // Verify empty auth header returns error when empty | ||
| _, err := context.GetAuthHeaderFromContext(r.Context()) | ||
| assert.Error(t, err) // Should return error when auth header is empty | ||
|
|
||
| w.WriteHeader(http.StatusOK) | ||
| }) | ||
|
|
||
| middleware := StoreAuthHeader() | ||
| handlerToTest := middleware(nextHandler) | ||
|
|
||
| req := httptest.NewRequest("GET", "http://testing", nil) | ||
| req.Header.Set(headers.Authorization, "") | ||
| recorder := httptest.NewRecorder() | ||
|
|
||
| handlerToTest.ServeHTTP(recorder, req) | ||
|
|
||
| assert.Equal(t, http.StatusOK, recorder.Code) | ||
| } | ||
|
|
||
| func TestStoreAuthHeader_MultipleAuthHeaders(t *testing.T) { | ||
| // Test behavior when multiple authorization headers are present | ||
| nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| // Should get the first header value since http.Header.Get returns only the first value | ||
| authFromContext, err := context.GetAuthHeaderFromContext(r.Context()) | ||
| assert.NoError(t, err) | ||
| assert.Equal(t, "Bearer token1", authFromContext) | ||
|
|
||
| w.WriteHeader(http.StatusOK) | ||
| }) | ||
|
|
||
| middleware := StoreAuthHeader() | ||
| handlerToTest := middleware(nextHandler) | ||
|
|
||
| req := httptest.NewRequest("GET", "http://testing", nil) | ||
| req.Header.Add(headers.Authorization, "Bearer token1") | ||
| req.Header.Add(headers.Authorization, "Bearer token2") | ||
| recorder := httptest.NewRecorder() | ||
|
|
||
| handlerToTest.ServeHTTP(recorder, req) | ||
|
|
||
| assert.Equal(t, http.StatusOK, recorder.Code) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
| ) | ||
|
|
||
| // Middleware defines a function that wraps an http.Handler. | ||
| type Middleware func(http.Handler) http.Handler | ||
|
|
||
| // CreateAuthMiddleware returns a slice of Middleware functions for authentication and authorization. | ||
| // The returned middlewares are: StoreWebToken, StoreAuthHeader, and StoreSpiffeHeader. | ||
| func CreateAuthMiddleware() []Middleware { | ||
| return []Middleware{ | ||
| StoreWebToken(), | ||
| StoreAuthHeader(), | ||
| StoreSpiffeHeader(), | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestCreateAuthMiddleware(t *testing.T) { | ||
| middlewares := CreateAuthMiddleware() | ||
|
|
||
| // Expect 3 middlewares: StoreWebToken, StoreAuthHeader, StoreSpiffeHeader | ||
| assert.Len(t, middlewares, 3) | ||
|
|
||
| // Each middleware should not be nil | ||
| for _, mw := range middlewares { | ||
| assert.NotNil(t, mw) | ||
| } | ||
| } | ||
|
|
||
| func TestCreateAuthMiddleware_ReturnsCorrectMiddlewares(t *testing.T) { | ||
| middlewares := CreateAuthMiddleware() | ||
|
|
||
| // Should return exactly 3 middlewares | ||
| assert.Len(t, middlewares, 3) | ||
|
|
||
| // Each middleware should be a valid function | ||
| for _, mw := range middlewares { | ||
| assert.NotNil(t, mw) | ||
| // Signature is implicitly tested by compilation and return type | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/platform-mesh/golang-commons/logger" | ||
| ) | ||
|
|
||
| // StoreLoggerMiddleware returns an HTTP middleware that injects the provided | ||
| // logger into each request's context so downstream handlers can retrieve it. | ||
| func StoreLoggerMiddleware(log *logger.Logger) func(http.Handler) http.Handler { | ||
| if log == nil { | ||
| log = logger.StdLogger | ||
| } | ||
| return func(next http.Handler) http.Handler { | ||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| ctx := logger.SetLoggerInContext(r.Context(), log) | ||
| next.ServeHTTP(w, r.WithContext(ctx)) | ||
| }) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| "github.com/platform-mesh/golang-commons/logger" | ||
| "github.com/platform-mesh/golang-commons/logger/testlogger" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestStoreLoggerMiddleware(t *testing.T) { | ||
| testLog := testlogger.New() | ||
|
|
||
| nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| // Verify logger is stored in context | ||
| logFromContext := logger.LoadLoggerFromContext(r.Context()) | ||
| assert.NotNil(t, logFromContext) | ||
|
|
||
| // The logger should be the same instance we passed | ||
| assert.Equal(t, testLog.Logger, logFromContext) | ||
|
|
||
| w.WriteHeader(http.StatusOK) | ||
| }) | ||
|
|
||
| middleware := StoreLoggerMiddleware(testLog.Logger) | ||
| handlerToTest := middleware(nextHandler) | ||
|
|
||
| req := httptest.NewRequest("GET", "http://testing", nil) | ||
| recorder := httptest.NewRecorder() | ||
|
|
||
| handlerToTest.ServeHTTP(recorder, req) | ||
|
|
||
| assert.Equal(t, http.StatusOK, recorder.Code) | ||
| } | ||
|
|
||
| func TestStoreLoggerMiddleware_NilLogger(t *testing.T) { | ||
| nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| // Even with nil logger, the middleware should not panic | ||
| w.WriteHeader(http.StatusOK) | ||
| }) | ||
|
|
||
| middleware := StoreLoggerMiddleware(nil) | ||
| handlerToTest := middleware(nextHandler) | ||
|
|
||
| req := httptest.NewRequest("GET", "http://testing", nil) | ||
| recorder := httptest.NewRecorder() | ||
|
|
||
| // Should not panic | ||
| assert.NotPanics(t, func() { | ||
| handlerToTest.ServeHTTP(recorder, req) | ||
| }) | ||
|
|
||
| assert.Equal(t, http.StatusOK, recorder.Code) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/platform-mesh/golang-commons/logger" | ||
| ) | ||
|
|
||
| // attaches a request-scoped logger (using the provided logger), assigns a request ID, and propagates that ID into the logger. | ||
| func CreateMiddleware(log *logger.Logger) []func(http.Handler) http.Handler { | ||
| return []func(http.Handler) http.Handler{ | ||
| SetOtelTracingContext(), | ||
| SentryRecoverer, | ||
| StoreLoggerMiddleware(log), | ||
| SetRequestId(), | ||
| SetRequestIdInLogger(), | ||
| } | ||
| } | ||
nexus49 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| "github.com/platform-mesh/golang-commons/logger/testlogger" | ||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestCreateMiddleware(t *testing.T) { | ||
| log := testlogger.New() | ||
| middlewares := CreateMiddleware(log.Logger) | ||
|
|
||
| // Should return 5 middlewares | ||
| assert.Len(t, middlewares, 5) | ||
|
|
||
| // Each middleware should be a valid function | ||
| for _, mw := range middlewares { | ||
| assert.NotNil(t, mw) | ||
| } | ||
|
|
||
| // Test that middlewares can be chained | ||
| handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.WriteHeader(http.StatusOK) | ||
| }) | ||
|
|
||
| // Apply all middlewares | ||
| var finalHandler http.Handler = handler | ||
| for i := len(middlewares) - 1; i >= 0; i-- { | ||
| finalHandler = middlewares[i](finalHandler) | ||
| } | ||
|
|
||
| req := httptest.NewRequest("GET", "http://testing", nil) | ||
| recorder := httptest.NewRecorder() | ||
|
|
||
| finalHandler.ServeHTTP(recorder, req) | ||
|
|
||
| assert.Equal(t, http.StatusOK, recorder.Code) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "go.opentelemetry.io/otel" | ||
| "go.opentelemetry.io/otel/propagation" | ||
| ) | ||
|
|
||
| // SetOtelTracingContext returns an HTTP middleware that extracts OpenTelemetry | ||
| // tracing context from incoming request headers and injects it into the request's | ||
| // context before passing the request to the next handler. | ||
| // | ||
| // The middleware uses the global OpenTelemetry text map propagator and | ||
| // propagation.HeaderCarrier to read trace/span context from the request headers. | ||
| // Any extraction behavior (including failure handling) is delegated to the | ||
| // propagator implementation. | ||
| func SetOtelTracingContext() func(http.Handler) http.Handler { | ||
| return func(next http.Handler) http.Handler { | ||
| return http.HandlerFunc(func(responseWriter http.ResponseWriter, request *http.Request) { | ||
| ctx := otel.GetTextMapPropagator().Extract(request.Context(), propagation.HeaderCarrier(request.Header)) | ||
| next.ServeHTTP(responseWriter, request.WithContext(ctx)) | ||
| }) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.