mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-18 04:47:28 +00:00
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
This commit is contained in:
+49
-32
@@ -14,21 +14,22 @@ linters:
|
|||||||
- gosec
|
- gosec
|
||||||
- misspell
|
- misspell
|
||||||
- noctx
|
- noctx
|
||||||
- nolintlint
|
|
||||||
- prealloc
|
- prealloc
|
||||||
- revive
|
- revive
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
- unparam
|
||||||
- whitespace
|
|
||||||
disable:
|
disable:
|
||||||
- exhaustive
|
- exhaustive
|
||||||
- funlen
|
- funlen
|
||||||
- gocognit
|
- gocognit
|
||||||
|
- gocyclo # Disabled: OAuth/OIDC flows are inherently complex
|
||||||
|
- goprintffuncname # Disabled: naming convention is project-specific
|
||||||
- lll
|
- lll
|
||||||
- mnd
|
- mnd
|
||||||
- testpackage
|
- testpackage
|
||||||
|
- whitespace # Disabled: style preference about newlines
|
||||||
- wsl
|
- wsl
|
||||||
settings:
|
settings:
|
||||||
dupl:
|
dupl:
|
||||||
@@ -47,29 +48,13 @@ linters:
|
|||||||
- fmt.Fprintln
|
- fmt.Fprintln
|
||||||
goconst:
|
goconst:
|
||||||
min-len: 3
|
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
|
ignore-tests: true
|
||||||
gocritic:
|
gocritic:
|
||||||
# Using default enabled checks in v2
|
# Disable style-only checks that add noise
|
||||||
enabled-checks:
|
disabled-checks:
|
||||||
- appendCombine
|
- ifElseChain # Style preference, switch not always clearer
|
||||||
- boolExprSimplify
|
- elseif # Style preference
|
||||||
- builtinShadow
|
|
||||||
- commentedOutCode
|
|
||||||
- emptyFallthrough
|
|
||||||
- equalFold
|
|
||||||
- hexLiteral
|
|
||||||
- indexAlloc
|
|
||||||
- initClause
|
|
||||||
- methodExprCall
|
|
||||||
- nestingReduce
|
|
||||||
- rangeExprCopy
|
|
||||||
- rangeValCopy
|
|
||||||
- stringXbytes
|
|
||||||
- typeAssertChain
|
|
||||||
- typeUnparen
|
|
||||||
- unlabelStmt
|
|
||||||
- yodaStyleExpr
|
|
||||||
gocyclo:
|
gocyclo:
|
||||||
min-complexity: 30 # OAuth/OIDC flows are inherently complex; set higher for Yaegi compatibility
|
min-complexity: 30 # OAuth/OIDC flows are inherently complex; set higher for Yaegi compatibility
|
||||||
gosec:
|
gosec:
|
||||||
@@ -106,23 +91,23 @@ linters:
|
|||||||
- name: error-return
|
- name: error-return
|
||||||
- name: error-strings
|
- name: error-strings
|
||||||
- name: error-naming
|
- name: error-naming
|
||||||
- name: exported
|
# - name: exported # Disabled: too noisy, not all exported functions need comments
|
||||||
- name: if-return
|
# - name: if-return # Disabled: style preference
|
||||||
- name: increment-decrement
|
- name: increment-decrement
|
||||||
- name: var-naming
|
# - name: var-naming # Disabled: too strict for legacy code (IP vs Ip)
|
||||||
- name: var-declaration
|
# - name: var-declaration # Disabled: explicit zero values can be clearer
|
||||||
- name: package-comments
|
# - name: package-comments # Disabled: handled by other tools
|
||||||
- name: range
|
- name: range
|
||||||
- name: receiver-naming
|
- name: receiver-naming
|
||||||
- name: time-naming
|
- name: time-naming
|
||||||
- name: unexported-return
|
- name: unexported-return
|
||||||
- name: indent-error-flow
|
# - name: indent-error-flow # Disabled: style preference
|
||||||
- name: errorf
|
- name: errorf
|
||||||
- name: empty-block
|
# - name: empty-block # Disabled: sometimes empty blocks are intentional
|
||||||
- name: superfluous-else
|
- name: superfluous-else
|
||||||
- name: unused-parameter
|
# - name: unused-parameter # Disabled: test callbacks and interface implementations often have required unused params
|
||||||
- name: unreachable-code
|
- name: unreachable-code
|
||||||
- name: redefines-builtin-id
|
# - name: redefines-builtin-id # Disabled: min/max helpers are common before Go 1.21
|
||||||
unparam:
|
unparam:
|
||||||
check-exported: false
|
check-exported: false
|
||||||
staticcheck:
|
staticcheck:
|
||||||
@@ -132,8 +117,15 @@ linters:
|
|||||||
- -QF1003 # Tagged switch - style preference, may affect Yaegi
|
- -QF1003 # Tagged switch - style preference, may affect Yaegi
|
||||||
- -QF1007 # Merge conditional assignment - style preference
|
- -QF1007 # Merge conditional assignment - style preference
|
||||||
- -QF1008 # Remove embedded field - may break Yaegi compatibility
|
- -QF1008 # Remove embedded field - may break Yaegi compatibility
|
||||||
|
- -QF1011 # Omit type from declaration - style preference
|
||||||
- -QF1012 # Use fmt.Fprintf - 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
|
- -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:
|
exclusions:
|
||||||
generated: lax
|
generated: lax
|
||||||
rules:
|
rules:
|
||||||
@@ -144,18 +136,43 @@ linters:
|
|||||||
- goconst
|
- goconst
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- gosec
|
- gosec
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
- noctx
|
- noctx
|
||||||
- prealloc
|
- prealloc
|
||||||
- unparam
|
- unparam
|
||||||
|
- revive
|
||||||
|
- gocritic
|
||||||
path: _test\.go
|
path: _test\.go
|
||||||
- linters:
|
- linters:
|
||||||
- dupl
|
- dupl
|
||||||
- gocyclo
|
- gocyclo
|
||||||
|
- govet
|
||||||
|
- noctx
|
||||||
|
- prealloc
|
||||||
|
- unparam
|
||||||
|
- revive
|
||||||
|
- gocritic
|
||||||
path: test.*\.go
|
path: test.*\.go
|
||||||
- linters:
|
- linters:
|
||||||
- gocritic
|
- gocritic
|
||||||
- unused
|
- unused
|
||||||
|
- errcheck
|
||||||
|
- revive
|
||||||
path: mocks.*\.go
|
path: mocks.*\.go
|
||||||
|
- linters:
|
||||||
|
- errcheck
|
||||||
|
- revive
|
||||||
|
- gocritic
|
||||||
|
- govet
|
||||||
|
- unparam
|
||||||
|
path: internal/testutil/
|
||||||
|
- linters:
|
||||||
|
- govet
|
||||||
|
- unparam
|
||||||
|
- noctx
|
||||||
|
- prealloc
|
||||||
|
path: integration/
|
||||||
- linters:
|
- linters:
|
||||||
- gosec
|
- gosec
|
||||||
text: 'G404:'
|
text: 'G404:'
|
||||||
|
|||||||
+4
-3
@@ -599,8 +599,9 @@ func GetGlobalTaskMemoryMonitor(logger *Logger) *TaskMemoryMonitor {
|
|||||||
return globalTaskMemoryMonitor
|
return globalTaskMemoryMonitor
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTaskMemoryMonitor creates a new memory monitor for task registry
|
// NewTaskMemoryMonitor creates a new memory monitor for task registry.
|
||||||
// Deprecated: Use GetGlobalTaskMemoryMonitor instead for singleton behavior
|
//
|
||||||
|
// Deprecated: Use GetGlobalTaskMemoryMonitor instead for singleton behavior.
|
||||||
func NewTaskMemoryMonitor(logger *Logger, registry *TaskRegistry) *TaskMemoryMonitor {
|
func NewTaskMemoryMonitor(logger *Logger, registry *TaskRegistry) *TaskMemoryMonitor {
|
||||||
return GetGlobalTaskMemoryMonitor(logger)
|
return GetGlobalTaskMemoryMonitor(logger)
|
||||||
}
|
}
|
||||||
@@ -712,7 +713,7 @@ func (mm *TaskMemoryMonitor) checkForMemoryIssues(stats TaskMemoryStats) {
|
|||||||
|
|
||||||
// Check for goroutine leaks (arbitrary threshold)
|
// Check for goroutine leaks (arbitrary threshold)
|
||||||
if stats.Goroutines > 100 {
|
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
|
// Check for heap growth without corresponding GC activity
|
||||||
|
|||||||
+3
-2
@@ -20,8 +20,9 @@ var (
|
|||||||
cacheManagerInitOnce sync.Once
|
cacheManagerInitOnce sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetGlobalCacheManager returns a singleton CacheManager instance
|
// GetGlobalCacheManager returns a singleton CacheManager instance.
|
||||||
// Deprecated: Use GetGlobalCacheManagerWithConfig instead
|
//
|
||||||
|
// Deprecated: Use GetGlobalCacheManagerWithConfig instead.
|
||||||
func GetGlobalCacheManager(wg *sync.WaitGroup) *CacheManager {
|
func GetGlobalCacheManager(wg *sync.WaitGroup) *CacheManager {
|
||||||
return GetGlobalCacheManagerWithConfig(wg, nil)
|
return GetGlobalCacheManagerWithConfig(wg, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
// REDACTED is the placeholder value for sensitive information
|
// REDACTED is the placeholder value for sensitive information
|
||||||
const REDACTED = "[REDACTED]"
|
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
|
// Rewritten without type aliases for yaegi compatibility
|
||||||
func (c Config) MarshalJSON() ([]byte, error) {
|
func (c Config) MarshalJSON() ([]byte, error) {
|
||||||
// Build a map manually to avoid type alias issues with yaegi
|
// 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)
|
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
|
// Rewritten without type aliases for yaegi compatibility
|
||||||
func (c Config) MarshalYAML() (interface{}, error) {
|
func (c Config) MarshalYAML() (interface{}, error) {
|
||||||
// Build a map manually to avoid type alias issues with yaegi
|
// Build a map manually to avoid type alias issues with yaegi
|
||||||
|
|||||||
+1
-1
@@ -954,7 +954,7 @@ func (gd *GracefulDegradation) GetDegradedServices() []string {
|
|||||||
gd.mutex.RLock()
|
gd.mutex.RLock()
|
||||||
defer gd.mutex.RUnlock()
|
defer gd.mutex.RUnlock()
|
||||||
|
|
||||||
var degraded []string
|
degraded := make([]string, 0, len(gd.degradedServices))
|
||||||
for serviceName := range gd.degradedServices {
|
for serviceName := range gd.degradedServices {
|
||||||
degraded = append(degraded, serviceName)
|
degraded = append(degraded, serviceName)
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-27
@@ -10,6 +10,14 @@ import (
|
|||||||
"unicode/utf8"
|
"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
|
// InputValidator provides comprehensive input validation and sanitization
|
||||||
// to protect against common security vulnerabilities including SQL injection,
|
// to protect against common security vulnerabilities including SQL injection,
|
||||||
// XSS, path traversal, and other injection attacks. It validates and sanitizes
|
// 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.
|
// 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:
|
// Parameters:
|
||||||
// - config: Validation configuration with size limits and mode settings.
|
// - config: Validation configuration with size limits and mode settings.
|
||||||
@@ -81,29 +89,8 @@ func DefaultInputValidationConfig() InputValidationConfig {
|
|||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - A configured InputValidator instance.
|
// - 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) {
|
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{
|
return &InputValidator{
|
||||||
maxTokenLength: config.MaxTokenLength,
|
maxTokenLength: config.MaxTokenLength,
|
||||||
maxURLLength: config.MaxURLLength,
|
maxURLLength: config.MaxURLLength,
|
||||||
@@ -112,10 +99,10 @@ func NewInputValidator(config InputValidationConfig, logger *Logger) (*InputVali
|
|||||||
maxEmailLength: config.MaxEmailLength,
|
maxEmailLength: config.MaxEmailLength,
|
||||||
maxUsernameLength: config.MaxUsernameLength,
|
maxUsernameLength: config.MaxUsernameLength,
|
||||||
allowPrivateIPAddresses: config.AllowPrivateIPAddresses,
|
allowPrivateIPAddresses: config.AllowPrivateIPAddresses,
|
||||||
emailRegex: emailRegex,
|
emailRegex: emailRegexPattern,
|
||||||
urlRegex: urlRegex,
|
urlRegex: urlRegexPattern,
|
||||||
tokenRegex: tokenRegex,
|
tokenRegex: tokenRegexPattern,
|
||||||
usernameRegex: usernameRegex,
|
usernameRegex: usernameRegexPattern,
|
||||||
sqlInjectionPatterns: []string{
|
sqlInjectionPatterns: []string{
|
||||||
"'", "\"", ";", "--", "/*", "*/", "xp_", "sp_",
|
"'", "\"", ";", "--", "/*", "*/", "xp_", "sp_",
|
||||||
"union", "select", "insert", "update", "delete", "drop",
|
"union", "select", "insert", "update", "delete", "drop",
|
||||||
|
|||||||
+9
-5
@@ -241,9 +241,11 @@ func (s *cacheShard) evictLRULocked() bool {
|
|||||||
|
|
||||||
element := s.lruList.Back()
|
element := s.lruList.Back()
|
||||||
if element != nil {
|
if element != nil {
|
||||||
item := element.Value.(*memoryCacheItem)
|
item, ok := element.Value.(*memoryCacheItem)
|
||||||
s.deleteItemLocked(item)
|
if ok {
|
||||||
return true
|
s.deleteItemLocked(item)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -267,8 +269,10 @@ func (s *cacheShard) getOldestAccessTime() time.Time {
|
|||||||
|
|
||||||
element := s.lruList.Back()
|
element := s.lruList.Back()
|
||||||
if element != nil {
|
if element != nil {
|
||||||
item := element.Value.(*memoryCacheItem)
|
item, ok := element.Value.(*memoryCacheItem)
|
||||||
return item.accessedAt
|
if ok {
|
||||||
|
return item.accessedAt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
-2
@@ -345,7 +345,7 @@ func (r *RedisBackend) prefixKey(key string) string {
|
|||||||
|
|
||||||
// executeWithRetry executes a Redis operation with exponential backoff retry logic.
|
// executeWithRetry executes a Redis operation with exponential backoff retry logic.
|
||||||
// It checks context cancellation at multiple points to ensure fast abort when the
|
// 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 {
|
func (r *RedisBackend) executeWithRetry(ctx context.Context, operation func(*RedisConn) error) error {
|
||||||
maxRetries := 3
|
maxRetries := 3
|
||||||
baseDelay := 50 * time.Millisecond // Reduced from 100ms to fail faster
|
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)
|
err = operation(conn)
|
||||||
r.pool.Put(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 {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -201,7 +201,7 @@ func TestConnectionPool_ContextCancellation(t *testing.T) {
|
|||||||
conn, err := pool.Get(context.Background())
|
conn, err := pool.Get(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Try to get another with cancelled context
|
// Try to get another with canceled context
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel() // Cancel immediately
|
cancel() // Cancel immediately
|
||||||
|
|
||||||
|
|||||||
Vendored
+2
-2
@@ -44,7 +44,7 @@ type RESPWriter struct {
|
|||||||
|
|
||||||
// NewRESPWriter creates a new RESP writer from the pool (memory optimized)
|
// NewRESPWriter creates a new RESP writer from the pool (memory optimized)
|
||||||
func NewRESPWriter(w io.Writer) *RESPWriter {
|
func NewRESPWriter(w io.Writer) *RESPWriter {
|
||||||
writer := writerPool.Get().(*RESPWriter)
|
writer, _ := writerPool.Get().(*RESPWriter)
|
||||||
writer.w = w
|
writer.w = w
|
||||||
return writer
|
return writer
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ type RESPReader struct {
|
|||||||
|
|
||||||
// NewRESPReader creates a new RESP reader from the pool (memory optimized)
|
// NewRESPReader creates a new RESP reader from the pool (memory optimized)
|
||||||
func NewRESPReader(r io.Reader) *RESPReader {
|
func NewRESPReader(r io.Reader) *RESPReader {
|
||||||
reader := readerPool.Get().(*RESPReader)
|
reader, _ := readerPool.Get().(*RESPReader)
|
||||||
reader.r.Reset(r)
|
reader.r.Reset(r)
|
||||||
return reader
|
return reader
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ func (s *SingleflightCache) GetOrFetch(ctx context.Context, key string, fetcher
|
|||||||
// If successful, store in cache
|
// If successful, store in cache
|
||||||
if call.err == nil && call.val != nil {
|
if call.err == nil && call.val != nil {
|
||||||
// Use a background context for cache storage to ensure it completes
|
// 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)
|
storeCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
_ = s.backend.Set(storeCtx, key, call.val, call.ttl)
|
_ = s.backend.Set(storeCtx, key, call.val, call.ttl)
|
||||||
cancel()
|
cancel()
|
||||||
|
|||||||
Vendored
+1
-1
@@ -232,7 +232,7 @@ func (m *Manager) Close() error {
|
|||||||
|
|
||||||
var firstErr error
|
var firstErr error
|
||||||
|
|
||||||
if err := m.tokenCache.Close(); err != nil && firstErr == nil {
|
if err := m.tokenCache.Close(); err != nil {
|
||||||
firstErr = err
|
firstErr = err
|
||||||
}
|
}
|
||||||
if err := m.metadataCache.Close(); err != nil && firstErr == nil {
|
if err := m.metadataCache.Close(); err != nil && firstErr == nil {
|
||||||
|
|||||||
@@ -397,7 +397,7 @@ func (wp *WorkerPool) Submit(task func()) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// worker is the main worker routine
|
// worker is the main worker routine
|
||||||
func (wp *WorkerPool) worker(id int) {
|
func (wp *WorkerPool) worker(_ int) {
|
||||||
defer wp.workerWg.Done()
|
defer wp.workerWg.Done()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ func (m *FeatureManager) LoadFromEnv() {
|
|||||||
for name, flag := range flags {
|
for name, flag := range flags {
|
||||||
envVar := "FEATURE_" + name
|
envVar := "FEATURE_" + name
|
||||||
if value := os.Getenv(envVar); value != "" {
|
if value := os.Getenv(envVar); value != "" {
|
||||||
enabled := strings.ToLower(value) == "true" || value == "1"
|
enabled := strings.EqualFold(value, "true") || value == "1"
|
||||||
flag.enabled.Store(enabled)
|
flag.enabled.Store(enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
// Remove offline_access scope as Cognito doesn't use it (case-insensitive)
|
||||||
var filteredScopes []string
|
var filteredScopes []string
|
||||||
for _, scope := range scopes {
|
for _, scope := range scopes {
|
||||||
if strings.ToLower(scope) != ScopeOfflineAccess {
|
if !strings.EqualFold(scope, ScopeOfflineAccess) {
|
||||||
filteredScopes = append(filteredScopes, scope)
|
filteredScopes = append(filteredScopes, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,17 @@ func GetProviderWarnings(providerType ProviderType) []ProviderWarning {
|
|||||||
|
|
||||||
switch providerType {
|
switch providerType {
|
||||||
case ProviderTypeGitHub:
|
case ProviderTypeGitHub:
|
||||||
warnings = append(warnings, ProviderWarning{
|
warnings = append(warnings,
|
||||||
ProviderType: ProviderTypeGitHub,
|
ProviderWarning{
|
||||||
Level: "warning",
|
ProviderType: ProviderTypeGitHub,
|
||||||
Message: "GitHub uses OAuth 2.0, not OpenID Connect. ID tokens are not available. Use access tokens for API calls only.",
|
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,
|
ProviderWarning{
|
||||||
Level: "info",
|
ProviderType: ProviderTypeGitHub,
|
||||||
Message: "GitHub OAuth apps do not support refresh tokens. Users will need to re-authenticate when tokens expire.",
|
Level: "info",
|
||||||
})
|
Message: "GitHub OAuth apps do not support refresh tokens. Users will need to re-authenticate when tokens expire.",
|
||||||
|
})
|
||||||
|
|
||||||
case ProviderTypeAuth0:
|
case ProviderTypeAuth0:
|
||||||
warnings = append(warnings, ProviderWarning{
|
warnings = append(warnings, ProviderWarning{
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func (re *RetryExecutor) ExecuteWithContext(ctx context.Context, fn func() error
|
|||||||
// Continue to next attempt
|
// Continue to next attempt
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
re.RecordFailure()
|
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),
|
"totalMechanisms": len(rm.mechanisms),
|
||||||
"totalRequests": totalRequests,
|
"totalRequests": totalRequests,
|
||||||
"totalSuccesses": totalSuccesses,
|
"totalSuccesses": totalSuccesses,
|
||||||
@@ -310,8 +310,9 @@ func (rm *RecoveryMetrics) GetAllMetrics() map[string]interface{} {
|
|||||||
|
|
||||||
if totalRequests > 0 {
|
if totalRequests > 0 {
|
||||||
successRate := float64(totalSuccesses) / float64(totalRequests) * 100
|
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
|
return allMetrics
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ func TestRetryExecutor_ExecuteWithContext_ContextCancelled(t *testing.T) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
if execErr == nil {
|
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 {
|
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: "timeout", err: errors.New("TIMEOUT"), expected: true}, // case insensitive
|
||||||
{name: "EOF", err: errors.New("EOF"), expected: false},
|
{name: "EOF", err: errors.New("EOF"), expected: false},
|
||||||
{name: "random error", err: errors.New("something else"), 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},
|
{name: "context deadline exceeded", err: context.DeadlineExceeded, expected: false},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -213,9 +213,9 @@ func (jwk *JWK) ToECDSAPublicKey() (*ecdsa.PublicKey, error) {
|
|||||||
// GetKey finds a key by its ID (kid) in the JWKSet.
|
// GetKey finds a key by its ID (kid) in the JWKSet.
|
||||||
// Returns nil if no key with the given ID is found.
|
// Returns nil if no key with the given ID is found.
|
||||||
func (jwks *JWKSet) GetKey(kid string) *JWK {
|
func (jwks *JWKSet) GetKey(kid string) *JWK {
|
||||||
for _, key := range jwks.Keys {
|
for i := range jwks.Keys {
|
||||||
if key.Kid == kid {
|
if jwks.Keys[i].Kid == kid {
|
||||||
return &key
|
return &jwks.Keys[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func getReplayCacheStats() (size int, maxSize int) {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - ctx: Parent context for cancellation
|
// - ctx: Parent context for cancellation
|
||||||
// - logger: Logger for debug output (can be nil)
|
// - logger: Logger for debug output (can be nil)
|
||||||
func startReplayCacheCleanup(ctx context.Context, logger *Logger) {
|
func startReplayCacheCleanup(_ context.Context, logger *Logger) {
|
||||||
registry := GetGlobalTaskRegistry()
|
registry := GetGlobalTaskRegistry()
|
||||||
|
|
||||||
// Define the cleanup task function
|
// Define the cleanup task function
|
||||||
|
|||||||
@@ -316,9 +316,9 @@ func (t *TraefikOidc) verifyLogoutTokenSignature(jwt *JWT, tokenString string) e
|
|||||||
|
|
||||||
// Find the matching key in JWKS
|
// Find the matching key in JWKS
|
||||||
var matchingKey *JWK
|
var matchingKey *JWK
|
||||||
for _, key := range jwks.Keys {
|
for i := range jwks.Keys {
|
||||||
if key.Kid == kid {
|
if jwks.Keys[i].Kid == kid {
|
||||||
matchingKey = &key
|
matchingKey = &jwks.Keys[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config Marshalling Tests
|
// Config Marshaling Tests
|
||||||
|
|
||||||
func TestConfig_MarshalJSON(t *testing.T) {
|
func TestConfig_MarshalJSON(t *testing.T) {
|
||||||
config := &Config{
|
config := &Config{
|
||||||
|
|||||||
+1
-1
@@ -229,7 +229,7 @@ func (mm *MemoryMonitor) updateGoroutineTracking(stats *MemoryStats) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for potential goroutine leak
|
// 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()
|
mm.mu.Lock()
|
||||||
wasAlert := mm.goroutineLeakAlert
|
wasAlert := mm.goroutineLeakAlert
|
||||||
if !wasAlert {
|
if !wasAlert {
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ func (rc *RefreshCoordinator) CoordinateRefresh(
|
|||||||
// Returns (operation, false, nil) if joined an existing operation
|
// Returns (operation, false, nil) if joined an existing operation
|
||||||
// Returns (nil, false, error) if the operation was rejected
|
// Returns (nil, false, error) if the operation was rejected
|
||||||
func (rc *RefreshCoordinator) getOrCreateOperation(
|
func (rc *RefreshCoordinator) getOrCreateOperation(
|
||||||
ctx context.Context,
|
_ context.Context,
|
||||||
sessionID string,
|
sessionID string,
|
||||||
tokenHash string,
|
tokenHash string,
|
||||||
refreshToken string,
|
refreshToken string,
|
||||||
@@ -293,7 +293,7 @@ func (rc *RefreshCoordinator) getOrCreateOperation(
|
|||||||
// executeRefreshAsync performs the actual refresh operation asynchronously
|
// executeRefreshAsync performs the actual refresh operation asynchronously
|
||||||
func (rc *RefreshCoordinator) executeRefreshAsync(
|
func (rc *RefreshCoordinator) executeRefreshAsync(
|
||||||
operation *refreshOperation,
|
operation *refreshOperation,
|
||||||
sessionID string,
|
_ string, // sessionID - reserved for future metrics/logging
|
||||||
tokenHash string,
|
tokenHash string,
|
||||||
refreshFunc func() (*TokenResponse, error),
|
refreshFunc func() (*TokenResponse, error),
|
||||||
) {
|
) {
|
||||||
|
|||||||
+2
-2
@@ -164,7 +164,7 @@ func decompressCombinedPayload(compressed string) (*combinedSessionPayload, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create gzip reader: %w", err)
|
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
|
// Limit decompressed size to prevent zip bombs
|
||||||
limitedReader := io.LimitReader(gr, 512*1024) // 512KB max
|
limitedReader := io.LimitReader(gr, 512*1024) // 512KB max
|
||||||
@@ -1588,7 +1588,7 @@ func (sd *SessionData) returnToPoolSafely() {
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - r: The HTTP request context.
|
// - r: The HTTP request context.
|
||||||
// - chunks: The map of session chunks (e.g., sd.accessTokenChunks) to clear and expire.
|
// - 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 {
|
for _, session := range chunks {
|
||||||
clearSessionValues(session, true)
|
clearSessionValues(session, true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -926,6 +926,8 @@ func (cm *ChunkManager) detectRepeatedCharacters(token string, config TokenConfi
|
|||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - An error if the token is expired or has invalid expiration, nil if valid.
|
// - 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 {
|
func (cm *ChunkManager) validateTokenExpiration(token string, config TokenConfig) error {
|
||||||
if !strings.Contains(token, ".") {
|
if !strings.Contains(token, ".") {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -734,6 +734,7 @@ func (l *Logger) Errorf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newNoOpLogger creates a logger that discards all output.
|
// newNoOpLogger creates a logger that discards all output.
|
||||||
|
//
|
||||||
// Deprecated: Use GetSingletonNoOpLogger() instead for better memory efficiency.
|
// Deprecated: Use GetSingletonNoOpLogger() instead for better memory efficiency.
|
||||||
func newNoOpLogger() *Logger {
|
func newNoOpLogger() *Logger {
|
||||||
return GetSingletonNoOpLogger()
|
return GetSingletonNoOpLogger()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func (w *testWriter) Write(p []byte) (n int, err error) {
|
|||||||
// Test helper adapters for the new test files
|
// Test helper adapters for the new test files
|
||||||
|
|
||||||
// resetGlobalState resets all global singletons to prevent test interference
|
// 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() {
|
func resetGlobalState() {
|
||||||
// Reset global task registry first to stop all background tasks
|
// 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
|
// 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 {
|
func createTestConfig() *Config {
|
||||||
config := CreateConfig()
|
config := CreateConfig()
|
||||||
@@ -151,7 +151,7 @@ func createTestConfig() *Config {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// setupTestOIDCMiddleware creates a test OIDC middleware instance with mock servers
|
// 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) {
|
func setupTestOIDCMiddleware(t *testing.T, config *Config) (*TraefikOidc, *httptest.Server) {
|
||||||
// Reset global state to ensure test isolation
|
// 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
|
// 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 {
|
func createMockJWT(t *testing.T, sub, email string) string {
|
||||||
return ValidIDToken
|
return ValidIDToken
|
||||||
@@ -361,7 +361,7 @@ func createTestSession() *SessionData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// injectSessionIntoRequest saves the session and adds the resulting cookies to the request
|
// 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) {
|
func injectSessionIntoRequest(t *testing.T, req *http.Request, session *SessionData) {
|
||||||
// Create a response recorder to capture cookies
|
// Create a response recorder to capture cookies
|
||||||
|
|||||||
@@ -454,7 +454,6 @@ func (t *TraefikOidc) refreshToken(rw http.ResponseWriter, req *http.Request, se
|
|||||||
newToken, err := t.tokenExchanger.GetNewTokenWithRefreshToken(initialRefreshToken)
|
newToken, err := t.tokenExchanger.GetNewTokenWithRefreshToken(initialRefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := err.Error()
|
errMsg := err.Error()
|
||||||
//nolint:gocritic // Complex error handling with provider-specific conditions
|
|
||||||
if strings.Contains(errMsg, "invalid_grant") || strings.Contains(errMsg, "token expired") {
|
if strings.Contains(errMsg, "invalid_grant") || strings.Contains(errMsg, "token expired") {
|
||||||
t.logger.Debug("Refresh token expired or revoked: %v", err)
|
t.logger.Debug("Refresh token expired or revoked: %v", err)
|
||||||
// Clear all tokens and authentication state when refresh token is invalid
|
// Clear all tokens and authentication state when refresh token is invalid
|
||||||
|
|||||||
Reference in New Issue
Block a user