Skip to content

Commit

Permalink
Merge pull request #1 from DEXPRO-Solutions-GmbH/fix/potential-error-…
Browse files Browse the repository at this point in the history
…comparison-bug

Fix potential bug in JWTMiddleware due to type asserting error
  • Loading branch information
fabiante authored Nov 30, 2023
2 parents 5994d61 + dbca877 commit 2f0cf8b
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 9 deletions.
4 changes: 3 additions & 1 deletion http/authn/jwt_mw.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package authn

import (
"errors"
"net/http"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -31,7 +32,8 @@ func (mw *JwtMiddleware) Gin(ctx *gin.Context) {

token, claims, err := mw.parser.ParseToken(tokenStr)
if err != nil {
if _, isValidationErr := err.(*jwt.ValidationError); isValidationErr {
var validationErr *jwt.ValidationError
if errors.As(err, &validationErr) {
http.Error(writer, "auth token validation failed: "+err.Error(), http.StatusUnauthorized)
ctx.Abort()
} else {
Expand Down
81 changes: 73 additions & 8 deletions http/authn/jwt_mw_test.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,101 @@
package authn

import (
"errors"
"fmt"
"net/http/httptest"
"testing"

"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/assert"
)

func TestJwtMiddleware_Gin(t *testing.T) {
gin.SetMode(gin.TestMode)
engine := gin.New()

extractor := newMockExtractor()
parser := newMockParser()

var (
ctx *gin.Context
rec *httptest.ResponseRecorder
)

setup := func() {
var (
extractor *mockExtractor
parser *mockParser
mw *JwtMiddleware
)

reset := func() {
rec = httptest.NewRecorder()
ctx = gin.CreateTestContextOnly(rec, engine)

extractor = &mockExtractor{}
parser = &mockParser{}
mw = NewJwtMiddleware(extractor, parser)
}

t.Run("request with no token", func(t *testing.T) {
t.Run("responds with 401 by default", func(t *testing.T) {
setup()
mw := NewJwtMiddleware(extractor, parser)
t.Run("responds with 401 by default", func(t *testing.T) {
reset()
mw.Gin(ctx)
assert.Equal(t, 401, rec.Code)
})

t.Run("responds with 401 if JWT parsing failed", func(t *testing.T) {
t.Run("with a validation error", func(t *testing.T) {
reset()
extractor.token = "invalid-token"
parser.err = &jwt.ValidationError{}

mw.Gin(ctx)
assert.Equal(t, 401, rec.Code)
})

t.Run("with a wrapped validation error", func(t *testing.T) {
reset()
extractor.token = "invalid-token"
parser.err = fmt.Errorf("wrapped: %w", &jwt.ValidationError{})

mw.Gin(ctx)
assert.Equal(t, 401, rec.Code)
})

t.Run("with some other error", func(t *testing.T) {
reset()
extractor.token = "invalid-token"
parser.err = errors.New("some random error")

mw.Gin(ctx)
assert.Equal(t, 401, rec.Code)
})
})

t.Run("responds with 401 if JWT is invalid", func(t *testing.T) {
reset()
extractor.token = "invalid-token"
parser.token = &jwt.Token{
Valid: false,
}

mw.Gin(ctx)
assert.Equal(t, 401, rec.Code)
})

t.Run("responds with 200 if JWT is valid", func(t *testing.T) {
reset()

// we can mock any values here. the only important thing is that the token's Valid field is true.
extractor.token = "invalid-token"
parser.token = &jwt.Token{
Valid: true,
}

mw.Gin(ctx)
assert.Equal(t, 200, rec.Code)

// assert ctx has the proper jwt object
ctxJwt := GetCtxJwt(ctx)
assert.NotNil(t, ctxJwt, "context has no jwt object")
assert.Equal(t, parser.token, ctxJwt.Token, "jwt token is not the same as the one returned by the parser")
})
}

0 comments on commit 2f0cf8b

Please sign in to comment.