Compare commits

...

3 Commits

Author SHA1 Message Date
Yuval Bar-On 249dcad1b3 fix: prevent deadlock in SessionData.Clear method (#114)
Move mutex unlock before calling Save() to prevent potential deadlock
when Save() method needs to acquire the same mutex.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-16 15:02:33 +00:00
lukaszraczylo de4b4d7258 fix(cache): remove sync.Pool for Yaegi compatibility (#121)
- [x] Remove sync.Pool implementation that causes reflection panics
- [x] Replace pool-based NewRESPWriter with direct instantiation
- [x] Replace pool-based NewRESPReader with direct instantiation
- [x] Convert Release() methods to no-ops for API compatibility
- [x] Add documentation explaining sync.Pool removal for Yaegi
- [x] Remove "sync" import

Resolves #120
2026-01-19 17:52:31 +00:00
lukaszraczylo 9d52f1b018 feat(core): refactor linters config and improve code quality (#119)
- [x] Reorganize golangci-lint configuration with documented disable reasons
- [x] Simplify errcheck and revive linter rules with targeted exclusions
- [x] Pre-compile regex patterns in input_validation.go for performance
- [x] Fix type assertions in memory_shard.go and resp.go with safety checks
- [x] Replace string comparison with EqualFold for case-insensitive matching
- [x] Fix loop variable captures in jwk.go and logout.go
- [x] Change high goroutine log level from Info to Debug in autocleanup.go
- [x] Replace deprecated "cancelled" spelling with "canceled" throughout
- [x] Add nolint annotations for intentional unused parameters
- [x] Improve comment formatting for deprecated functions
- [x] Fix comment spelling: "marshalling" → "marshaling"
- [x] Refactor provider warnings formatting in internal/providers/warnings.go
- [x] Simplify metrics summary building in internal/recovery/metrics.go
- [x] Pre-allocate slice in error_recovery.go GetDegradedServices
- [x] Refactor context cancellation checks in redis.go
2026-01-15 10:40:49 +00:00
29 changed files with 147 additions and 151 deletions
+49 -32
View File
@@ -14,21 +14,22 @@ linters:
- gosec
- misspell
- noctx
- nolintlint
- prealloc
- revive
- rowserrcheck
- sqlclosecheck
- unconvert
- unparam
- whitespace
disable:
- exhaustive
- funlen
- gocognit
- gocyclo # Disabled: OAuth/OIDC flows are inherently complex
- goprintffuncname # Disabled: naming convention is project-specific
- lll
- mnd
- testpackage
- whitespace # Disabled: style preference about newlines
- wsl
settings:
dupl:
@@ -47,29 +48,13 @@ linters:
- fmt.Fprintln
goconst:
min-len: 3
min-occurrences: 10 # Increased to reduce noise for standard OAuth2/OIDC strings
min-occurrences: 15 # Increased to reduce noise for standard OAuth2/OIDC strings and common patterns like "true"
ignore-tests: true
gocritic:
# Using default enabled checks in v2
enabled-checks:
- appendCombine
- boolExprSimplify
- builtinShadow
- commentedOutCode
- emptyFallthrough
- equalFold
- hexLiteral
- indexAlloc
- initClause
- methodExprCall
- nestingReduce
- rangeExprCopy
- rangeValCopy
- stringXbytes
- typeAssertChain
- typeUnparen
- unlabelStmt
- yodaStyleExpr
# Disable style-only checks that add noise
disabled-checks:
- ifElseChain # Style preference, switch not always clearer
- elseif # Style preference
gocyclo:
min-complexity: 30 # OAuth/OIDC flows are inherently complex; set higher for Yaegi compatibility
gosec:
@@ -106,23 +91,23 @@ linters:
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
- name: if-return
# - name: exported # Disabled: too noisy, not all exported functions need comments
# - name: if-return # Disabled: style preference
- name: increment-decrement
- name: var-naming
- name: var-declaration
- name: package-comments
# - name: var-naming # Disabled: too strict for legacy code (IP vs Ip)
# - name: var-declaration # Disabled: explicit zero values can be clearer
# - name: package-comments # Disabled: handled by other tools
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
# - name: indent-error-flow # Disabled: style preference
- name: errorf
- name: empty-block
# - name: empty-block # Disabled: sometimes empty blocks are intentional
- name: superfluous-else
- name: unused-parameter
# - name: unused-parameter # Disabled: test callbacks and interface implementations often have required unused params
- name: unreachable-code
- name: redefines-builtin-id
# - name: redefines-builtin-id # Disabled: min/max helpers are common before Go 1.21
unparam:
check-exported: false
staticcheck:
@@ -132,8 +117,15 @@ linters:
- -QF1003 # Tagged switch - style preference, may affect Yaegi
- -QF1007 # Merge conditional assignment - style preference
- -QF1008 # Remove embedded field - may break Yaegi compatibility
- -QF1011 # Omit type from declaration - style preference
- -QF1012 # Use fmt.Fprintf - style preference
- -SA9003 # Empty branch - sometimes intentional for future work
- -ST1000 # Package comment format - not required for all packages
- -ST1003 # Package name format - allowed for test packages
- -ST1016 # Receiver name consistency - legacy code
- -ST1020 # Comment format for methods - style preference
- -ST1021 # Comment format for types - style preference
- -ST1023 # Omit type from declaration - style preference
exclusions:
generated: lax
rules:
@@ -144,18 +136,43 @@ linters:
- goconst
- gocyclo
- gosec
- govet
- ineffassign
- noctx
- prealloc
- unparam
- revive
- gocritic
path: _test\.go
- linters:
- dupl
- gocyclo
- govet
- noctx
- prealloc
- unparam
- revive
- gocritic
path: test.*\.go
- linters:
- gocritic
- unused
- errcheck
- revive
path: mocks.*\.go
- linters:
- errcheck
- revive
- gocritic
- govet
- unparam
path: internal/testutil/
- linters:
- govet
- unparam
- noctx
- prealloc
path: integration/
- linters:
- gosec
text: 'G404:'
+4 -3
View File
@@ -599,8 +599,9 @@ func GetGlobalTaskMemoryMonitor(logger *Logger) *TaskMemoryMonitor {
return globalTaskMemoryMonitor
}
// NewTaskMemoryMonitor creates a new memory monitor for task registry
// Deprecated: Use GetGlobalTaskMemoryMonitor instead for singleton behavior
// NewTaskMemoryMonitor creates a new memory monitor for task registry.
//
// Deprecated: Use GetGlobalTaskMemoryMonitor instead for singleton behavior.
func NewTaskMemoryMonitor(logger *Logger, registry *TaskRegistry) *TaskMemoryMonitor {
return GetGlobalTaskMemoryMonitor(logger)
}
@@ -712,7 +713,7 @@ func (mm *TaskMemoryMonitor) checkForMemoryIssues(stats TaskMemoryStats) {
// Check for goroutine leaks (arbitrary threshold)
if stats.Goroutines > 100 {
mm.logger.Infof("High goroutine count detected: %d", stats.Goroutines)
mm.logger.Debugf("High goroutine count detected: %d", stats.Goroutines)
}
// Check for heap growth without corresponding GC activity
+3 -2
View File
@@ -20,8 +20,9 @@ var (
cacheManagerInitOnce sync.Once
)
// GetGlobalCacheManager returns a singleton CacheManager instance
// Deprecated: Use GetGlobalCacheManagerWithConfig instead
// GetGlobalCacheManager returns a singleton CacheManager instance.
//
// Deprecated: Use GetGlobalCacheManagerWithConfig instead.
func GetGlobalCacheManager(wg *sync.WaitGroup) *CacheManager {
return GetGlobalCacheManagerWithConfig(wg, nil)
}
+2 -2
View File
@@ -7,7 +7,7 @@ import (
// REDACTED is the placeholder value for sensitive information
const REDACTED = "[REDACTED]"
// MarshalJSON implements custom JSON marshalling to redact sensitive fields
// MarshalJSON implements custom JSON marshaling to redact sensitive fields
// Rewritten without type aliases for yaegi compatibility
func (c Config) MarshalJSON() ([]byte, error) {
// Build a map manually to avoid type alias issues with yaegi
@@ -47,7 +47,7 @@ func (c Config) MarshalJSON() ([]byte, error) {
return json.Marshal(result)
}
// MarshalYAML implements custom YAML marshalling to redact sensitive fields
// MarshalYAML implements custom YAML marshaling to redact sensitive fields
// Rewritten without type aliases for yaegi compatibility
func (c Config) MarshalYAML() (interface{}, error) {
// Build a map manually to avoid type alias issues with yaegi
+1 -1
View File
@@ -954,7 +954,7 @@ func (gd *GracefulDegradation) GetDegradedServices() []string {
gd.mutex.RLock()
defer gd.mutex.RUnlock()
var degraded []string
degraded := make([]string, 0, len(gd.degradedServices))
for serviceName := range gd.degradedServices {
degraded = append(degraded, serviceName)
}
+14 -27
View File
@@ -10,6 +10,14 @@ import (
"unicode/utf8"
)
// Pre-compiled regex patterns for validation (const patterns should use MustCompile)
var (
emailRegexPattern = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
urlRegexPattern = regexp.MustCompile(`^https?://[a-zA-Z0-9.-]+(?:\.[a-zA-Z]{2,})?(?::[0-9]+)?(?:/[^\s]*)?$`)
tokenRegexPattern = regexp.MustCompile(`^[A-Za-z0-9._-]+$`)
usernameRegexPattern = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
)
// InputValidator provides comprehensive input validation and sanitization
// to protect against common security vulnerabilities including SQL injection,
// XSS, path traversal, and other injection attacks. It validates and sanitizes
@@ -73,7 +81,7 @@ func DefaultInputValidationConfig() InputValidationConfig {
}
// NewInputValidator creates a new input validator with the specified configuration.
// It compiles all necessary regex patterns and initializes security pattern lists.
// It uses pre-compiled regex patterns and initializes security pattern lists.
//
// Parameters:
// - config: Validation configuration with size limits and mode settings.
@@ -81,29 +89,8 @@ func DefaultInputValidationConfig() InputValidationConfig {
//
// Returns:
// - A configured InputValidator instance.
// - An error if regex compilation fails.
// - An error (always nil, kept for API compatibility).
func NewInputValidator(config InputValidationConfig, logger *Logger) (*InputValidator, error) {
// Compile regex patterns
emailRegex, err := regexp.Compile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if err != nil {
return nil, fmt.Errorf("failed to compile email regex: %w", err)
}
urlRegex, err := regexp.Compile(`^https?://[a-zA-Z0-9.-]+(?:\.[a-zA-Z]{2,})?(?::[0-9]+)?(?:/[^\s]*)?$`)
if err != nil {
return nil, fmt.Errorf("failed to compile URL regex: %w", err)
}
tokenRegex, err := regexp.Compile(`^[A-Za-z0-9._-]+$`)
if err != nil {
return nil, fmt.Errorf("failed to compile token regex: %w", err)
}
usernameRegex, err := regexp.Compile(`^[a-zA-Z0-9._-]+$`)
if err != nil {
return nil, fmt.Errorf("failed to compile username regex: %w", err)
}
return &InputValidator{
maxTokenLength: config.MaxTokenLength,
maxURLLength: config.MaxURLLength,
@@ -112,10 +99,10 @@ func NewInputValidator(config InputValidationConfig, logger *Logger) (*InputVali
maxEmailLength: config.MaxEmailLength,
maxUsernameLength: config.MaxUsernameLength,
allowPrivateIPAddresses: config.AllowPrivateIPAddresses,
emailRegex: emailRegex,
urlRegex: urlRegex,
tokenRegex: tokenRegex,
usernameRegex: usernameRegex,
emailRegex: emailRegexPattern,
urlRegex: urlRegexPattern,
tokenRegex: tokenRegexPattern,
usernameRegex: usernameRegexPattern,
sqlInjectionPatterns: []string{
"'", "\"", ";", "--", "/*", "*/", "xp_", "sp_",
"union", "select", "insert", "update", "delete", "drop",
+9 -5
View File
@@ -241,9 +241,11 @@ func (s *cacheShard) evictLRULocked() bool {
element := s.lruList.Back()
if element != nil {
item := element.Value.(*memoryCacheItem)
s.deleteItemLocked(item)
return true
item, ok := element.Value.(*memoryCacheItem)
if ok {
s.deleteItemLocked(item)
return true
}
}
return false
}
@@ -267,8 +269,10 @@ func (s *cacheShard) getOldestAccessTime() time.Time {
element := s.lruList.Back()
if element != nil {
item := element.Value.(*memoryCacheItem)
return item.accessedAt
item, ok := element.Value.(*memoryCacheItem)
if ok {
return item.accessedAt
}
}
return time.Time{}
}
+2 -2
View File
@@ -345,7 +345,7 @@ func (r *RedisBackend) prefixKey(key string) string {
// executeWithRetry executes a Redis operation with exponential backoff retry logic.
// It checks context cancellation at multiple points to ensure fast abort when the
// caller's context is cancelled (e.g., due to request timeout).
// caller's context is canceled (e.g., due to request timeout).
func (r *RedisBackend) executeWithRetry(ctx context.Context, operation func(*RedisConn) error) error {
maxRetries := 3
baseDelay := 50 * time.Millisecond // Reduced from 100ms to fail faster
@@ -377,7 +377,7 @@ func (r *RedisBackend) executeWithRetry(ctx context.Context, operation func(*Red
err = operation(conn)
r.pool.Put(conn)
// Check context after operation - if cancelled, don't bother retrying
// Check context after operation - if canceled, don't bother retrying
if ctx.Err() != nil {
return ctx.Err()
}
+1 -1
View File
@@ -201,7 +201,7 @@ func TestConnectionPool_ContextCancellation(t *testing.T) {
conn, err := pool.Get(context.Background())
require.NoError(t, err)
// Try to get another with cancelled context
// Try to get another with canceled context
ctx, cancel := context.WithCancel(context.Background())
cancel() // Cancel immediately
+15 -34
View File
@@ -7,52 +7,34 @@ import (
"io"
"strconv"
"strings"
"sync"
)
// RESP (REdis Serialization Protocol) implementation
// Pure Go implementation compatible with Yaegi interpreter (no unsafe package)
//
// NOTE: sync.Pool was intentionally removed for Yaegi compatibility.
// Yaegi (Traefik's Go interpreter) has issues with sync.Pool and reflection
// that cause "reflect: call of reflect.Value.Field on zero Value" panics.
// See: https://github.com/lukaszraczylo/traefikoidc/issues/120
var (
ErrInvalidRESP = errors.New("invalid RESP response")
ErrNilResponse = errors.New("nil response")
)
// Object pools for memory optimization - reduces allocations by 50-70%
var (
readerPool = sync.Pool{
New: func() interface{} {
return &RESPReader{
r: bufio.NewReaderSize(nil, 4096),
}
},
}
writerPool = sync.Pool{
New: func() interface{} {
return &RESPWriter{
w: nil,
}
},
}
)
// RESPWriter writes RESP protocol messages
type RESPWriter struct {
w io.Writer
}
// NewRESPWriter creates a new RESP writer from the pool (memory optimized)
// NewRESPWriter creates a new RESP writer
func NewRESPWriter(w io.Writer) *RESPWriter {
writer := writerPool.Get().(*RESPWriter)
writer.w = w
return writer
return &RESPWriter{w: w}
}
// Release returns the writer to the pool for reuse
// Release is a no-op for API compatibility (pooling removed for Yaegi compatibility)
func (w *RESPWriter) Release() {
w.w = nil
writerPool.Put(w)
// No-op: pooling removed for Yaegi compatibility
}
// WriteCommand writes a Redis command in RESP array format
@@ -78,17 +60,16 @@ type RESPReader struct {
r *bufio.Reader
}
// NewRESPReader creates a new RESP reader from the pool (memory optimized)
// NewRESPReader creates a new RESP reader
func NewRESPReader(r io.Reader) *RESPReader {
reader := readerPool.Get().(*RESPReader)
reader.r.Reset(r)
return reader
return &RESPReader{
r: bufio.NewReaderSize(r, 4096),
}
}
// Release returns the reader to the pool for reuse
// Release is a no-op for API compatibility (pooling removed for Yaegi compatibility)
func (r *RESPReader) Release() {
r.r.Reset(nil)
readerPool.Put(r)
// No-op: pooling removed for Yaegi compatibility
}
// ReadResponse reads a RESP response and returns the parsed value
+1 -1
View File
@@ -87,7 +87,7 @@ func (s *SingleflightCache) GetOrFetch(ctx context.Context, key string, fetcher
// If successful, store in cache
if call.err == nil && call.val != nil {
// Use a background context for cache storage to ensure it completes
// even if the original context is cancelled
// even if the original context is canceled
storeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
_ = s.backend.Set(storeCtx, key, call.val, call.ttl)
cancel()
+1 -1
View File
@@ -232,7 +232,7 @@ func (m *Manager) Close() error {
var firstErr error
if err := m.tokenCache.Close(); err != nil && firstErr == nil {
if err := m.tokenCache.Close(); err != nil {
firstErr = err
}
if err := m.metadataCache.Close(); err != nil && firstErr == nil {
+1 -1
View File
@@ -397,7 +397,7 @@ func (wp *WorkerPool) Submit(task func()) error {
}
// worker is the main worker routine
func (wp *WorkerPool) worker(id int) {
func (wp *WorkerPool) worker(_ int) {
defer wp.workerWg.Done()
for {
+1 -1
View File
@@ -173,7 +173,7 @@ func (m *FeatureManager) LoadFromEnv() {
for name, flag := range flags {
envVar := "FEATURE_" + name
if value := os.Getenv(envVar); value != "" {
enabled := strings.ToLower(value) == "true" || value == "1"
enabled := strings.EqualFold(value, "true") || value == "1"
flag.enabled.Store(enabled)
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ func (p *AWSCognitoProvider) BuildAuthParams(baseParams url.Values, scopes []str
// Remove offline_access scope as Cognito doesn't use it (case-insensitive)
var filteredScopes []string
for _, scope := range scopes {
if strings.ToLower(scope) != ScopeOfflineAccess {
if !strings.EqualFold(scope, ScopeOfflineAccess) {
filteredScopes = append(filteredScopes, scope)
}
}
+11 -10
View File
@@ -18,16 +18,17 @@ func GetProviderWarnings(providerType ProviderType) []ProviderWarning {
switch providerType {
case ProviderTypeGitHub:
warnings = append(warnings, ProviderWarning{
ProviderType: ProviderTypeGitHub,
Level: "warning",
Message: "GitHub uses OAuth 2.0, not OpenID Connect. ID tokens are not available. Use access tokens for API calls only.",
})
warnings = append(warnings, ProviderWarning{
ProviderType: ProviderTypeGitHub,
Level: "info",
Message: "GitHub OAuth apps do not support refresh tokens. Users will need to re-authenticate when tokens expire.",
})
warnings = append(warnings,
ProviderWarning{
ProviderType: ProviderTypeGitHub,
Level: "warning",
Message: "GitHub uses OAuth 2.0, not OpenID Connect. ID tokens are not available. Use access tokens for API calls only.",
},
ProviderWarning{
ProviderType: ProviderTypeGitHub,
Level: "info",
Message: "GitHub OAuth apps do not support refresh tokens. Users will need to re-authenticate when tokens expire.",
})
case ProviderTypeAuth0:
warnings = append(warnings, ProviderWarning{
+4 -3
View File
@@ -116,7 +116,7 @@ func (re *RetryExecutor) ExecuteWithContext(ctx context.Context, fn func() error
// Continue to next attempt
case <-ctx.Done():
re.RecordFailure()
return fmt.Errorf("retry cancelled: %w", ctx.Err())
return fmt.Errorf("retry canceled: %w", ctx.Err())
}
}
@@ -301,7 +301,7 @@ func (rm *RecoveryMetrics) GetAllMetrics() map[string]interface{} {
}
}
allMetrics["summary"] = map[string]interface{}{
summary := map[string]interface{}{
"totalMechanisms": len(rm.mechanisms),
"totalRequests": totalRequests,
"totalSuccesses": totalSuccesses,
@@ -310,8 +310,9 @@ func (rm *RecoveryMetrics) GetAllMetrics() map[string]interface{} {
if totalRequests > 0 {
successRate := float64(totalSuccesses) / float64(totalRequests) * 100
allMetrics["summary"].(map[string]interface{})["overallSuccessRate"] = fmt.Sprintf("%.2f%%", successRate)
summary["overallSuccessRate"] = fmt.Sprintf("%.2f%%", successRate)
}
allMetrics["summary"] = summary
return allMetrics
}
+3 -3
View File
@@ -223,7 +223,7 @@ func TestRetryExecutor_ExecuteWithContext_ContextCancelled(t *testing.T) {
wg.Wait()
if execErr == nil {
t.Error("Expected error when context is cancelled")
t.Error("Expected error when context is canceled")
}
}
@@ -240,7 +240,7 @@ func TestRetryExecutor_ExecuteWithContext_ContextCancelledBeforeStart(t *testing
})
if err == nil {
t.Error("Expected error when context is already cancelled")
t.Error("Expected error when context is already canceled")
}
}
@@ -282,7 +282,7 @@ func TestRetryExecutor_isRetryableError(t *testing.T) {
{name: "timeout", err: errors.New("TIMEOUT"), expected: true}, // case insensitive
{name: "EOF", err: errors.New("EOF"), expected: false},
{name: "random error", err: errors.New("something else"), expected: false},
{name: "context cancelled", err: context.Canceled, expected: false},
{name: "context canceled", err: context.Canceled, expected: false},
{name: "context deadline exceeded", err: context.DeadlineExceeded, expected: false},
}
+3 -3
View File
@@ -213,9 +213,9 @@ func (jwk *JWK) ToECDSAPublicKey() (*ecdsa.PublicKey, error) {
// GetKey finds a key by its ID (kid) in the JWKSet.
// Returns nil if no key with the given ID is found.
func (jwks *JWKSet) GetKey(kid string) *JWK {
for _, key := range jwks.Keys {
if key.Kid == kid {
return &key
for i := range jwks.Keys {
if jwks.Keys[i].Kid == kid {
return &jwks.Keys[i]
}
}
return nil
+1 -1
View File
@@ -120,7 +120,7 @@ func getReplayCacheStats() (size int, maxSize int) {
// Parameters:
// - ctx: Parent context for cancellation
// - logger: Logger for debug output (can be nil)
func startReplayCacheCleanup(ctx context.Context, logger *Logger) {
func startReplayCacheCleanup(_ context.Context, logger *Logger) {
registry := GetGlobalTaskRegistry()
// Define the cleanup task function
+3 -3
View File
@@ -316,9 +316,9 @@ func (t *TraefikOidc) verifyLogoutTokenSignature(jwt *JWT, tokenString string) e
// Find the matching key in JWKS
var matchingKey *JWK
for _, key := range jwks.Keys {
if key.Kid == kid {
matchingKey = &key
for i := range jwks.Keys {
if jwks.Keys[i].Kid == kid {
matchingKey = &jwks.Keys[i]
break
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"gopkg.in/yaml.v3"
)
// Config Marshalling Tests
// Config Marshaling Tests
func TestConfig_MarshalJSON(t *testing.T) {
config := &Config{
+1 -1
View File
@@ -229,7 +229,7 @@ func (mm *MemoryMonitor) updateGoroutineTracking(stats *MemoryStats) {
}
// Check for potential goroutine leak
if stats.NumGoroutines > mm.baselineGoroutines+int(mm.alertThresholds.GoroutineCount) {
if stats.NumGoroutines > mm.baselineGoroutines+mm.alertThresholds.GoroutineCount {
mm.mu.Lock()
wasAlert := mm.goroutineLeakAlert
if !wasAlert {
+2 -2
View File
@@ -234,7 +234,7 @@ func (rc *RefreshCoordinator) CoordinateRefresh(
// Returns (operation, false, nil) if joined an existing operation
// Returns (nil, false, error) if the operation was rejected
func (rc *RefreshCoordinator) getOrCreateOperation(
ctx context.Context,
_ context.Context,
sessionID string,
tokenHash string,
refreshToken string,
@@ -293,7 +293,7 @@ func (rc *RefreshCoordinator) getOrCreateOperation(
// executeRefreshAsync performs the actual refresh operation asynchronously
func (rc *RefreshCoordinator) executeRefreshAsync(
operation *refreshOperation,
sessionID string,
_ string, // sessionID - reserved for future metrics/logging
tokenHash string,
refreshFunc func() (*TokenResponse, error),
) {
+5 -4
View File
@@ -164,7 +164,7 @@ func decompressCombinedPayload(compressed string) (*combinedSessionPayload, erro
if err != nil {
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
}
defer gr.Close()
defer func() { _ = gr.Close() }()
// Limit decompressed size to prevent zip bombs
limitedReader := io.LimitReader(gr, 512*1024) // 512KB max
@@ -1548,9 +1548,10 @@ func (sd *SessionData) Clear(r *http.Request, w http.ResponseWriter) error {
}()
sd.sessionMutex.Lock()
defer sd.sessionMutex.Unlock()
sd.clearAllSessionData(r, true)
// Release the lock before calling Save to prevent deadlock
sd.sessionMutex.Unlock()
// This is primarily for testing - in production w will often be nil
var err error
@@ -1588,7 +1589,7 @@ func (sd *SessionData) returnToPoolSafely() {
// Parameters:
// - r: The HTTP request context.
// - chunks: The map of session chunks (e.g., sd.accessTokenChunks) to clear and expire.
func (sd *SessionData) clearTokenChunks(r *http.Request, chunks map[int]*sessions.Session) {
func (sd *SessionData) clearTokenChunks(_ *http.Request, chunks map[int]*sessions.Session) {
for _, session := range chunks {
clearSessionValues(session, true)
}
+2
View File
@@ -926,6 +926,8 @@ func (cm *ChunkManager) detectRepeatedCharacters(token string, config TokenConfi
//
// Returns:
// - An error if the token is expired or has invalid expiration, nil if valid.
//
//nolint:unparam // error return kept for API consistency and future use
func (cm *ChunkManager) validateTokenExpiration(token string, config TokenConfig) error {
if !strings.Contains(token, ".") {
return nil
+1
View File
@@ -734,6 +734,7 @@ func (l *Logger) Errorf(format string, args ...interface{}) {
}
// newNoOpLogger creates a logger that discards all output.
//
// Deprecated: Use GetSingletonNoOpLogger() instead for better memory efficiency.
func newNoOpLogger() *Logger {
return GetSingletonNoOpLogger()
+5 -5
View File
@@ -22,7 +22,7 @@ func (w *testWriter) Write(p []byte) (n int, err error) {
// Test helper adapters for the new test files
// resetGlobalState resets all global singletons to prevent test interference
// nolint:unused // Kept for potential future use in integration tests
//nolint:unused // Kept for potential future use in integration tests
/*
func resetGlobalState() {
// Reset global task registry first to stop all background tasks
@@ -137,7 +137,7 @@ func (tc *testCleanup) cleanupAll() {
}
// createTestConfig creates a config with all required fields populated for testing
// nolint:unused // Kept for potential future use in integration tests
//nolint:unused // Kept for potential future use in integration tests
/*
func createTestConfig() *Config {
config := CreateConfig()
@@ -151,7 +151,7 @@ func createTestConfig() *Config {
*/
// setupTestOIDCMiddleware creates a test OIDC middleware instance with mock servers
// nolint:unused // Kept for potential future use in integration tests
//nolint:unused // Kept for potential future use in integration tests
/*
func setupTestOIDCMiddleware(t *testing.T, config *Config) (*TraefikOidc, *httptest.Server) {
// Reset global state to ensure test isolation
@@ -339,7 +339,7 @@ func setupTestOIDCMiddleware(t *testing.T, config *Config) (*TraefikOidc, *httpt
*/
// createMockJWT creates a mock JWT token for testing - adapter for existing tests
// nolint:unused // Kept for potential future use in integration tests
//nolint:unused // Kept for potential future use in integration tests
/*
func createMockJWT(t *testing.T, sub, email string) string {
return ValidIDToken
@@ -361,7 +361,7 @@ func createTestSession() *SessionData {
}
// injectSessionIntoRequest saves the session and adds the resulting cookies to the request
// nolint:unused // Kept for potential future use in integration tests
//nolint:unused // Kept for potential future use in integration tests
/*
func injectSessionIntoRequest(t *testing.T, req *http.Request, session *SessionData) {
// Create a response recorder to capture cookies
-1
View File
@@ -454,7 +454,6 @@ func (t *TraefikOidc) refreshToken(rw http.ResponseWriter, req *http.Request, se
newToken, err := t.tokenExchanger.GetNewTokenWithRefreshToken(initialRefreshToken)
if err != nil {
errMsg := err.Error()
//nolint:gocritic // Complex error handling with provider-specific conditions
if strings.Contains(errMsg, "invalid_grant") || strings.Contains(errMsg, "token expired") {
t.logger.Debug("Refresh token expired or revoked: %v", err)
// Clear all tokens and authentication state when refresh token is invalid