Additional micro fixes and cleanups.

This commit is contained in:
2025-06-24 18:48:02 +01:00
parent dbe3455f49
commit c2a81bc2df
10 changed files with 40 additions and 31 deletions
+2
View File
@@ -0,0 +1,2 @@
docker/
.claude/
-5
View File
@@ -1,5 +0,0 @@
### TODO / wishlist
- [] Improve test coverage
- [x] Improve caching mechanism
- [x] Add automatic release and semver generation
+3
View File
@@ -1,3 +1,6 @@
// Package providers implements a universal OIDC provider abstraction system.
// It provides a clean interface for different OIDC providers (Google, Azure, Generic)
// with provider-specific logic encapsulated in separate implementations.
package providers
import (
+3
View File
@@ -62,6 +62,9 @@ type JWKCacheInterface interface {
// Returns:
// - A pointer to the JWKSet containing the keys.
// - An error if fetching fails or the response cannot be decoded.
// NewJWKCache creates a new JWK cache with default configuration.
// It initializes a cache with a 1-hour lifetime and maximum size of 100 entries.
func NewJWKCache() *JWKCache {
cache := &JWKCache{
CacheLifetime: 1 * time.Hour,
+1
View File
@@ -65,6 +65,7 @@ func startReplayCacheCleanup(ctx context.Context, logger *Logger) {
replayCacheMu.RLock()
if replayCache != nil {
replayCache.Cleanup()
}
replayCacheMu.RUnlock()
+4 -1
View File
@@ -1,3 +1,6 @@
// Package traefikoidc provides OIDC authentication middleware for Traefik.
// It supports multiple OIDC providers including Google, Azure AD, and generic OIDC providers
// with features like token refresh, session management, and provider-specific optimizations.
package traefikoidc
import (
@@ -1217,7 +1220,7 @@ func (t *TraefikOidc) processAuthorizedRequest(rw http.ResponseWriter, req *http
// Fields must be exported (uppercase) to be accessible in templates
templateData := map[string]interface{}{
"AccessToken": session.GetAccessToken(),
"IdToken": session.GetIDToken(),
"IDToken": session.GetIDToken(),
"RefreshToken": session.GetRefreshToken(),
"Claims": claims,
}
+1
View File
@@ -183,6 +183,7 @@ func decompressTokenInternal(compressed string) string {
defer func() {
if closeErr := gz.Close(); closeErr != nil {
// Log error but don't fail the operation
_ = closeErr // Explicitly ignore the error
}
}()
+15 -15
View File
@@ -83,9 +83,9 @@ func TestTemplateExecution(t *testing.T) {
},
{
name: "ID Token",
templateText: "{{.IdToken}}",
templateText: "{{.IDToken}}",
data: map[string]interface{}{
"IdToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
"IDToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
},
expectedValue: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.Et9HFtf9R3GEMA0IICOfFMVXY7kkTX1wr4qCyhIf58U",
expectError: false,
@@ -238,10 +238,10 @@ func TestTemplateExecutionContext(t *testing.T) {
}{
{
name: "Access and ID token distinction with map",
templateText: "Access: {{.AccessToken}} ID: {{.IdToken}}",
templateText: "Access: {{.AccessToken}} ID: {{.IDToken}}",
data: map[string]interface{}{
"AccessToken": "access-token-value",
"IdToken": "id-token-value",
"IDToken": "id-token-value",
"Claims": map[string]interface{}{},
"RefreshToken": "refresh-token-value",
},
@@ -252,7 +252,7 @@ func TestTemplateExecutionContext(t *testing.T) {
templateText: "User: {{.Claims.sub}} Token: {{.AccessToken}}",
data: map[string]interface{}{
"AccessToken": "access-token",
"IdToken": "id-token",
"IDToken": "id-token",
"Claims": map[string]interface{}{
"sub": "user123",
},
@@ -265,7 +265,7 @@ func TestTemplateExecutionContext(t *testing.T) {
templateText: "Bearer {{.AccessToken}}",
data: map[string]interface{}{
"AccessToken": "jwt-access-token",
"IdToken": "id-token",
"IDToken": "id-token",
"Claims": map[string]interface{}{},
},
expectedValue: "Bearer jwt-access-token",
@@ -283,7 +283,7 @@ func TestTemplateExecutionContext(t *testing.T) {
templateText: "X-User-Role: {{.Claims.role}}, X-User-Permissions: {{.Claims.permissions}}",
data: map[string]interface{}{
"AccessToken": "access-token-value",
"IdToken": "id-token-value",
"IDToken": "id-token-value",
"Claims": map[string]interface{}{
"email": "[email protected]",
"role": "admin",
@@ -314,7 +314,7 @@ func TestTemplateExecutionContext(t *testing.T) {
templateText: "X-User-Email: {{.Claims.email}}, X-Email-Verified: {{.Claims.email_verified}}",
data: map[string]interface{}{
"AccessToken": "access-token-value",
"IdToken": "id-token-value",
"IDToken": "id-token-value",
"Claims": map[string]interface{}{
"email": "[email protected]",
"email_verified": true,
@@ -327,7 +327,7 @@ func TestTemplateExecutionContext(t *testing.T) {
templateText: "X-User-ID: {{.Claims.sub}}, X-User-Name: {{.Claims.name}}, X-Username: {{.Claims.preferred_username}}",
data: map[string]interface{}{
"AccessToken": "access-token-value",
"IdToken": "id-token-value",
"IDToken": "id-token-value",
"Claims": map[string]interface{}{
"sub": "user123456",
"name": "Jane Doe",
@@ -363,7 +363,7 @@ func TestTemplateExecutionContext(t *testing.T) {
type templateData struct {
Claims map[string]interface{}
AccessToken string
IdToken string
IDToken string
RefreshToken string
}
@@ -376,10 +376,10 @@ func TestTemplateExecutionContext(t *testing.T) {
}{
{
name: "Access and ID token distinction with struct",
templateText: "Access: {{.AccessToken}} ID: {{.IdToken}}",
templateText: "Access: {{.AccessToken}} ID: {{.IDToken}}",
data: templateData{
AccessToken: "access-token-value",
IdToken: "id-token-value", // Now these should be distinct values
IDToken: "id-token-value", // Now these should be distinct values
Claims: map[string]interface{}{},
},
expectedValue: "Access: access-token-value ID: id-token-value",
@@ -389,7 +389,7 @@ func TestTemplateExecutionContext(t *testing.T) {
templateText: "User: {{.Claims.sub}} Token: {{.AccessToken}}",
data: templateData{
AccessToken: "access-token",
IdToken: "access-token",
IDToken: "access-token",
Claims: map[string]interface{}{
"sub": "user123",
},
@@ -401,7 +401,7 @@ func TestTemplateExecutionContext(t *testing.T) {
templateText: "X-Custom: {{.Claims.custom_field}}, X-Group: {{.Claims.group}}",
data: templateData{
AccessToken: "access-token",
IdToken: "id-token",
IDToken: "id-token",
Claims: map[string]interface{}{
"sub": "user123",
"custom_field": "custom-value",
@@ -415,7 +415,7 @@ func TestTemplateExecutionContext(t *testing.T) {
templateText: "X-Email: {{.Claims.email}}, X-Name: {{.Claims.name}}",
data: templateData{
AccessToken: "access-token",
IdToken: "id-token",
IDToken: "id-token",
Claims: map[string]interface{}{
"email": "[email protected]",
"name": "John Smith",
+3 -2
View File
@@ -70,7 +70,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
{
name: "ID Token Header",
headers: []TemplatedHeader{
{Name: "X-ID-Token", Value: "{{.IdToken}}"},
{Name: "X-ID-Token", Value: "{{.IDToken}}"},
},
expectedHeaders: map[string]string{
// We'll update this dynamically after generating the token
@@ -81,7 +81,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
name: "Both Token Types",
headers: []TemplatedHeader{
{Name: "X-Access-Token", Value: "{{.AccessToken}}"},
{Name: "X-ID-Token", Value: "{{.IdToken}}"},
{Name: "X-ID-Token", Value: "{{.IDToken}}"},
},
expectedHeaders: map[string]string{
// We'll update these dynamically after generating the tokens
@@ -389,6 +389,7 @@ func TestTemplatedHeadersIntegration(t *testing.T) {
// The current test expects the literal string "<no value>".
// Let's assume for now that if it's missing, it's an error unless specifically handled.
// The test as written expects "<no value>" to be present.
t.Logf("Header %s not set, but expected '<no value>' for missing claim", name)
}
t.Errorf("Expected header %s was not set", name)
+8 -8
View File
@@ -15,19 +15,19 @@ import (
"golang.org/x/time/rate"
)
// TestTokenTypeDistinction tests that AccessToken and IdToken are correctly distinguished in templates
// TestTokenTypeDistinction tests that AccessToken and IDToken are correctly distinguished in templates
func TestTokenTypeDistinction(t *testing.T) {
// Define test data where AccessToken and IdToken are deliberately different
// Define test data where AccessToken and IDToken are deliberately different
type templateData struct {
Claims map[string]interface{}
AccessToken string
IdToken string
IDToken string
RefreshToken string
}
testData := templateData{
AccessToken: "test-access-token-abc123",
IdToken: "test-id-token-xyz789",
IDToken: "test-id-token-xyz789",
RefreshToken: "test-refresh-token",
Claims: map[string]interface{}{
"sub": "test-subject",
@@ -48,17 +48,17 @@ func TestTokenTypeDistinction(t *testing.T) {
},
{
name: "ID Token Only",
templateText: "ID: {{.IdToken}}",
templateText: "ID: {{.IDToken}}",
expectedValue: "ID: test-id-token-xyz789",
},
{
name: "Both Tokens",
templateText: "Access: {{.AccessToken}} ID: {{.IdToken}}",
templateText: "Access: {{.AccessToken}} ID: {{.IDToken}}",
expectedValue: "Access: test-access-token-abc123 ID: test-id-token-xyz789",
},
{
name: "Both Tokens in Authorization Format",
templateText: "Bearer {{.AccessToken}} and Bearer {{.IdToken}}",
templateText: "Bearer {{.AccessToken}} and Bearer {{.IDToken}}",
expectedValue: "Bearer test-access-token-abc123 and Bearer test-id-token-xyz789",
},
}
@@ -125,7 +125,7 @@ func TestTokenTypeIntegration(t *testing.T) {
// Define test headers that use both token types
headers := []TemplatedHeader{
{Name: "X-ID-Token", Value: "{{.IdToken}}"},
{Name: "X-ID-Token", Value: "{{.IDToken}}"},
{Name: "X-Access-Token", Value: "{{.AccessToken}}"},
{Name: "Authorization", Value: "Bearer {{.AccessToken}}"},
{Name: "X-Email-From-Claims", Value: "{{.Claims.email}}"},