fix: migrate to go-jwt-middleware v3 API
schemas / check-release (pull_request) Successful in 1m57s
schemas / vulnerabilities (pull_request) Successful in 2m48s
schemas / check (pull_request) Successful in 8m17s
pre-commit / pre-commit (pull_request) Successful in 11m38s
schemas / build (pull_request) Successful in 5m31s
schemas / deploy-prod (pull_request) Has been skipped
schemas / check-release (pull_request) Successful in 1m57s
schemas / vulnerabilities (pull_request) Successful in 2m48s
schemas / check (pull_request) Successful in 8m17s
pre-commit / pre-commit (pull_request) Successful in 11m38s
schemas / build (pull_request) Successful in 5m31s
schemas / deploy-prod (pull_request) Has been skipped
- Use validator and jwks packages for JWT validation - Replace manual JWKS caching with jwks.NewCachingProvider - Add CustomClaims struct for https://unbound.se/roles claim - Rename TokenFromContext to ClaimsFromContext - Update middleware/auth.go to use new claims structure - Update tests to use core.SetClaims and validator.ValidatedClaims Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+73
-71
@@ -6,8 +6,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
mw "github.com/auth0/go-jwt-middleware/v3"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/auth0/go-jwt-middleware/v3/core"
|
||||
"github.com/auth0/go-jwt-middleware/v3/validator"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
@@ -155,9 +155,11 @@ func TestAuthMiddleware_Handler_WithValidJWT(t *testing.T) {
|
||||
mockCache.On("OrganizationByAPIKey", "").Return(nil)
|
||||
|
||||
userID := "user-123"
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"sub": userID,
|
||||
})
|
||||
claims := &validator.ValidatedClaims{
|
||||
RegisteredClaims: validator.RegisteredClaims{
|
||||
Subject: userID,
|
||||
},
|
||||
}
|
||||
|
||||
// Create a test handler that checks the context
|
||||
var capturedUser string
|
||||
@@ -170,9 +172,9 @@ func TestAuthMiddleware_Handler_WithValidJWT(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
// Create request with JWT token in context
|
||||
// Create request with JWT claims in context
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
ctx := context.WithValue(req.Context(), mw.ContextKey{}, token)
|
||||
ctx := core.SetClaims(req.Context(), claims)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
@@ -209,28 +211,35 @@ func TestAuthMiddleware_Handler_APIKeyErrorHandling(t *testing.T) {
|
||||
assert.Contains(t, rec.Body.String(), "Invalid API Key format")
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_Handler_JWTErrorHandling(t *testing.T) {
|
||||
func TestAuthMiddleware_Handler_JWTMissingClaims(t *testing.T) {
|
||||
// Setup
|
||||
mockCache := new(MockCache)
|
||||
authMiddleware := NewAuth(mockCache)
|
||||
|
||||
// The middleware passes the plaintext API key (cache handles hashing)
|
||||
mockCache.On("OrganizationByAPIKey", "").Return(nil)
|
||||
|
||||
// Create a test handler that checks the context
|
||||
var capturedUser string
|
||||
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if user := r.Context().Value(UserKey); user != nil {
|
||||
if u, ok := user.(string); ok {
|
||||
capturedUser = u
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
// Create request with invalid JWT token type in context
|
||||
// Create request without JWT claims - user should not be set
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
ctx := context.WithValue(req.Context(), mw.ContextKey{}, "not-a-token") // Invalid type
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
// Execute
|
||||
authMiddleware.Handler(testHandler).ServeHTTP(rec, req)
|
||||
|
||||
// Assert
|
||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), "Invalid JWT token format")
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Empty(t, capturedUser, "User should not be set when no claims in context")
|
||||
}
|
||||
|
||||
func TestAuthMiddleware_Handler_BothJWTAndAPIKey(t *testing.T) {
|
||||
@@ -249,9 +258,11 @@ func TestAuthMiddleware_Handler_BothJWTAndAPIKey(t *testing.T) {
|
||||
userID := "user-123"
|
||||
apiKey := "test-api-key-123"
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"sub": userID,
|
||||
})
|
||||
claims := &validator.ValidatedClaims{
|
||||
RegisteredClaims: validator.RegisteredClaims{
|
||||
Subject: userID,
|
||||
},
|
||||
}
|
||||
|
||||
// Mock expects plaintext key (cache handles hashing internally)
|
||||
mockCache.On("OrganizationByAPIKey", apiKey).Return(expectedOrg)
|
||||
@@ -273,9 +284,9 @@ func TestAuthMiddleware_Handler_BothJWTAndAPIKey(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
// Create request with both JWT and API key in context
|
||||
// Create request with both JWT claims and API key in context
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
ctx := context.WithValue(req.Context(), mw.ContextKey{}, token)
|
||||
ctx := core.SetClaims(req.Context(), claims)
|
||||
ctx = context.WithValue(ctx, ApiKey, apiKey)
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
@@ -475,13 +486,17 @@ func TestAuthMiddleware_Directive_NoRequirements(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUserHasRole_WithValidRole(t *testing.T) {
|
||||
// Create token with roles claim
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"sub": "user-123",
|
||||
"https://unbound.se/roles": []interface{}{"admin", "user"},
|
||||
})
|
||||
// Create claims with roles
|
||||
claims := &validator.ValidatedClaims{
|
||||
RegisteredClaims: validator.RegisteredClaims{
|
||||
Subject: "user-123",
|
||||
},
|
||||
CustomClaims: &CustomClaims{
|
||||
Roles: []string{"admin", "user"},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
|
||||
ctx := core.SetClaims(context.Background(), claims)
|
||||
|
||||
// Test for existing role
|
||||
hasRole := UserHasRole(ctx, "admin")
|
||||
@@ -492,13 +507,17 @@ func TestUserHasRole_WithValidRole(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUserHasRole_WithoutRole(t *testing.T) {
|
||||
// Create token with roles claim
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"sub": "user-123",
|
||||
"https://unbound.se/roles": []interface{}{"user"},
|
||||
})
|
||||
// Create claims with roles
|
||||
claims := &validator.ValidatedClaims{
|
||||
RegisteredClaims: validator.RegisteredClaims{
|
||||
Subject: "user-123",
|
||||
},
|
||||
CustomClaims: &CustomClaims{
|
||||
Roles: []string{"user"},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
|
||||
ctx := core.SetClaims(context.Background(), claims)
|
||||
|
||||
// Test for non-existing role
|
||||
hasRole := UserHasRole(ctx, "admin")
|
||||
@@ -506,59 +525,42 @@ func TestUserHasRole_WithoutRole(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestUserHasRole_WithoutRolesClaim(t *testing.T) {
|
||||
// Create token without roles claim
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"sub": "user-123",
|
||||
})
|
||||
// Create claims without custom claims
|
||||
claims := &validator.ValidatedClaims{
|
||||
RegisteredClaims: validator.RegisteredClaims{
|
||||
Subject: "user-123",
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
|
||||
ctx := core.SetClaims(context.Background(), claims)
|
||||
|
||||
// Test should return false when roles claim is missing
|
||||
// Test should return false when custom claims is missing
|
||||
hasRole := UserHasRole(ctx, "admin")
|
||||
assert.False(t, hasRole)
|
||||
}
|
||||
|
||||
func TestUserHasRole_WithoutToken(t *testing.T) {
|
||||
func TestUserHasRole_WithoutClaims(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Test should return false when no token in context
|
||||
// Test should return false when no claims in context
|
||||
hasRole := UserHasRole(ctx, "admin")
|
||||
assert.False(t, hasRole)
|
||||
}
|
||||
|
||||
func TestUserHasRole_WithInvalidTokenType(t *testing.T) {
|
||||
// Put invalid token type in context
|
||||
ctx := context.WithValue(context.Background(), mw.ContextKey{}, "not-a-token")
|
||||
func TestUserHasRole_WithEmptyRoles(t *testing.T) {
|
||||
// Create claims with empty roles
|
||||
claims := &validator.ValidatedClaims{
|
||||
RegisteredClaims: validator.RegisteredClaims{
|
||||
Subject: "user-123",
|
||||
},
|
||||
CustomClaims: &CustomClaims{
|
||||
Roles: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
// Test should return false when token type is invalid
|
||||
hasRole := UserHasRole(ctx, "admin")
|
||||
assert.False(t, hasRole)
|
||||
}
|
||||
|
||||
func TestUserHasRole_WithInvalidRolesType(t *testing.T) {
|
||||
// Create token with invalid roles type
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"sub": "user-123",
|
||||
"https://unbound.se/roles": "not-an-array",
|
||||
})
|
||||
|
||||
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
|
||||
|
||||
// Test should return false when roles type is invalid
|
||||
hasRole := UserHasRole(ctx, "admin")
|
||||
assert.False(t, hasRole)
|
||||
}
|
||||
|
||||
func TestUserHasRole_WithInvalidRoleElementType(t *testing.T) {
|
||||
// Create token with invalid role element types
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"sub": "user-123",
|
||||
"https://unbound.se/roles": []interface{}{123, 456}, // Numbers instead of strings
|
||||
})
|
||||
|
||||
ctx := context.WithValue(context.Background(), mw.ContextKey{}, token)
|
||||
|
||||
// Test should return false when role elements are not strings
|
||||
ctx := core.SetClaims(context.Background(), claims)
|
||||
|
||||
// Test should return false when roles array is empty
|
||||
hasRole := UserHasRole(ctx, "admin")
|
||||
assert.False(t, hasRole)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user