Skip to content

Commit

Permalink
jwt test
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Freeman committed Oct 6, 2024
1 parent e767500 commit 6d1d287
Show file tree
Hide file tree
Showing 6 changed files with 433 additions and 96 deletions.
26 changes: 24 additions & 2 deletions pkg/api/middleware/interfaces.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
package middleware

import "github.com/google/uuid"
import (
"context"

//go:generate mockgen -destination=mock_custom_context.go -package=middleware github.com/carverauto/eventrunner/pkg/api/middleware CustomContext
customctx "github.com/carverauto/eventrunner/pkg/context"
"github.com/google/uuid"
"gofr.dev/pkg/gofr"
)

//go:generate mockgen -destination=mock_middleware.go -package=middleware github.com/carverauto/eventrunner/pkg/api/middleware CustomContext,JWTMiddlewareInterface,IDTokenVerifier,Token

// CustomContext interface for mocking CustomContext.
type CustomContext interface {
GetAPIKey() (string, bool)
FindAPIKey(apiKey string) (uuid.UUID, uuid.UUID, error)
SetClaim(key string, value interface{})
}

// JWTMiddlewareInterface interface for mocking JWTMiddleware.
type JWTMiddlewareInterface interface {
Validate(next func(customctx.Context) (interface{}, error)) gofr.Handler
}

// IDTokenVerifier interface for mocking oidc.IDTokenVerifier.
type IDTokenVerifier interface {
Verify(ctx context.Context, rawToken string) (Token, error)
}

// Token interface to abstract oidc.IDToken.
type Token interface {
Claims(v interface{}) error
}
24 changes: 13 additions & 11 deletions pkg/api/middleware/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@ package middleware

import (
"context"
"strings"

"github.com/carverauto/eventrunner/pkg/config"
customctx "github.com/carverauto/eventrunner/pkg/context"
"github.com/carverauto/eventrunner/pkg/eventingest"
"github.com/coreos/go-oidc/v3/oidc"
"gofr.dev/pkg/gofr"
"strings"
)

// JWTMiddleware is a middleware that validates JWT tokens.
type JWTMiddleware struct {
verifier *oidc.IDTokenVerifier
verifier IDTokenVerifier
config *config.OAuthConfig
}

// NewJWTMiddleware creates a new JWTMiddleware.
func NewJWTMiddleware(ctx context.Context, config *config.OAuthConfig) (*JWTMiddleware, error) {
provider, err := oidc.NewProvider(ctx, config.KeycloakURL)
func NewJWTMiddleware(ctx context.Context, cfg *config.OAuthConfig) (*JWTMiddleware, error) {
provider, err := oidc.NewProvider(ctx, cfg.KeycloakURL)
if err != nil {
return nil, eventingest.NewInternalError("Failed to get provider")
}

verifier := provider.Verifier(&oidc.Config{ClientID: config.ClientID})
oidcVerifier := provider.Verifier(&oidc.Config{ClientID: cfg.ClientID})
verifier := NewOIDCVerifier(oidcVerifier)

return &JWTMiddleware{
verifier: verifier,
config: config,
config: cfg,
}, nil
}

Expand All @@ -37,14 +37,16 @@ func (m *JWTMiddleware) Validate(next func(customctx.Context) (interface{}, erro
return func(c *gofr.Context) (interface{}, error) {
cc := customctx.NewCustomContext(c)

authHeader := c.Request.Context().Value("Authorization").(string)
if authHeader == "" {
return nil, eventingest.NewAuthError("Missing authorization header")
// Safely retrieve Authorization header from context
authHeaderValue := c.Request.Context().Value("Authorization")
authHeader, ok := authHeaderValue.(string)
if !ok || authHeader == "" {
return nil, eventingest.NewAuthError("Missing or invalid authorization header")
}

bearerToken := strings.TrimPrefix(authHeader, "Bearer ")
if bearerToken == authHeader {
return nil, eventingest.NewAuthError("Invalid authorization header")
return nil, eventingest.NewAuthError("Invalid authorization header format")
}

token, err := m.verifier.Verify(context.Background(), bearerToken)
Expand Down
35 changes: 35 additions & 0 deletions pkg/api/middleware/jwt_oidc_wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package middleware

import (
"context"
"github.com/coreos/go-oidc/v3/oidc"
)

// OIDCVerifier is a wrapper around oidc.IDTokenVerifier
type OIDCVerifier struct {
verifier *oidc.IDTokenVerifier
}

// NewOIDCVerifier returns an instance of OIDCVerifier
func NewOIDCVerifier(verifier *oidc.IDTokenVerifier) *OIDCVerifier {
return &OIDCVerifier{verifier: verifier}
}

// Verify method to conform to the IDTokenVerifier interface
func (o *OIDCVerifier) Verify(ctx context.Context, rawToken string) (Token, error) {
idToken, err := o.verifier.Verify(ctx, rawToken)
if err != nil {
return nil, err
}
return &OIDCToken{idToken: idToken}, nil
}

// OIDCToken is a wrapper around oidc.IDToken to implement the Token interface
type OIDCToken struct {
idToken *oidc.IDToken
}

// Claims wraps the oidc.IDToken.Claims method to conform to the Token interface
func (o *OIDCToken) Claims(v interface{}) error {
return o.idToken.Claims(v)
}
163 changes: 163 additions & 0 deletions pkg/api/middleware/jwt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package middleware

import (
"context"
"encoding/json"
"net/http"
"testing"

"github.com/carverauto/eventrunner/pkg/config"
customctx "github.com/carverauto/eventrunner/pkg/context"
"github.com/carverauto/eventrunner/pkg/eventingest"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"gofr.dev/pkg/gofr"
"gofr.dev/pkg/gofr/container"
)

// MockRequest for use in testing, similar to what you used in another test file.
type MockRequest struct {
ctx context.Context
params map[string][]string // Adjusted to hold slices of strings
body []byte
header http.Header
}

func (r *MockRequest) Context() context.Context {
return r.ctx
}

func (r *MockRequest) Param(key string) string {
if vals, ok := r.params[key]; ok && len(vals) > 0 {
return vals[0]
}

return ""
}

func (r *MockRequest) Params(key string) []string {
return r.params[key]
}

func (r *MockRequest) PathParam(key string) string {
if vals, ok := r.params[key]; ok && len(vals) > 0 {
return vals[0]
}

return ""
}

func (r *MockRequest) Bind(i interface{}) error {
return json.Unmarshal(r.body, i)
}

func (r *MockRequest) HostName() string {
return "localhost"
}

func (r *MockRequest) Header() http.Header {
return r.header
}

func TestJWTMiddleware_Validate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockVerifier := NewMockIDTokenVerifier(ctrl)
mockToken := NewMockToken(ctrl)

jwtMiddleware := &JWTMiddleware{
verifier: mockVerifier,
config: &config.OAuthConfig{},
}

tests := []struct {
name string
setupRequest func(*MockRequest)
setupMocks func()
expectedResult interface{}
expectedError error
}{
{
name: "Valid Token",
setupRequest: func(req *MockRequest) {
req.ctx = context.WithValue(context.Background(), "Authorization", "Bearer valid_token")
},
setupMocks: func() {
mockVerifier.EXPECT().Verify(gomock.Any(), "valid_token").Return(mockToken, nil).Times(1)
mockToken.EXPECT().Claims(gomock.Any()).DoAndReturn(func(v interface{}) error {
claims := struct {
TenantID string `json:"tenant_id"`
CustomerID string `json:"customer_id"`
}{
TenantID: "test-tenant-id",
CustomerID: "test-customer-id",
}
claimsBytes, _ := json.Marshal(claims)
return json.Unmarshal(claimsBytes, v)
}).Times(1)
},
expectedResult: "success",
expectedError: nil,
},
{
name: "Missing Authorization Header",
setupRequest: func(req *MockRequest) {}, // No Authorization set in context
setupMocks: func() {},
expectedResult: nil,
expectedError: eventingest.NewAuthError("Missing or invalid authorization header"),
},
{
name: "Invalid Authorization Header",
setupRequest: func(req *MockRequest) {
req.ctx = context.WithValue(context.Background(), "Authorization", "InvalidHeader")
},
setupMocks: func() {},
expectedResult: nil,
expectedError: eventingest.NewAuthError("Invalid authorization header format"),
},
{
name: "Invalid Token",
setupRequest: func(req *MockRequest) {
req.ctx = context.WithValue(context.Background(), "Authorization", "Bearer invalid_token")
},
setupMocks: func() {
mockVerifier.EXPECT().Verify(gomock.Any(), "invalid_token").Return(nil, eventingest.NewAuthError("Invalid token")).Times(1)
},
expectedResult: nil,
expectedError: eventingest.NewAuthError("Invalid token"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockRequest := &MockRequest{
ctx: context.Background(),
params: make(map[string][]string),
header: http.Header{},
}
tt.setupRequest(mockRequest)

// Create a new GoFr container
c, _ := container.NewMockContainer(t)

// Create the GoFr context
gofrCtx := &gofr.Context{
Context: mockRequest.Context(),
Request: mockRequest,
Container: c,
}

tt.setupMocks()

handler := jwtMiddleware.Validate(func(cc customctx.Context) (interface{}, error) {
return "success", nil
})

result, err := handler(gofrCtx)

assert.Equal(t, tt.expectedResult, result)
assert.Equal(t, tt.expectedError, err)
})
}
}
83 changes: 0 additions & 83 deletions pkg/api/middleware/mock_custom_context.go

This file was deleted.

Loading

0 comments on commit 6d1d287

Please sign in to comment.