diff --git a/cache.go b/cache.go index 52842c5..b168305 100644 --- a/cache.go +++ b/cache.go @@ -23,24 +23,13 @@ type lruEntry struct { // Cache provides a thread-safe in-memory caching mechanism with expiration support. // It implements an LRU (Least Recently Used) eviction policy using a doubly-linked list for efficiency. type Cache struct { - // items stores the cached data with string keys. - items map[string]CacheItem - - // order maintains the usage order; most recently used items are at the back. - order *list.List - - // elems maps keys to their corresponding list elements for O(1) access. - elems map[string]*list.Element - - // mutex protects concurrent access to the cache. - mutex sync.RWMutex - - // maxSize is the maximum number of items allowed in the cache. - maxSize int - // autoCleanupInterval defines how often Cleanup is called automatically. + items map[string]CacheItem + order *list.List + elems map[string]*list.Element + stopCleanup chan struct{} + maxSize int autoCleanupInterval time.Duration - // stopCleanup channel to terminate the auto cleanup goroutine. - stopCleanup chan struct{} + mutex sync.RWMutex } // DefaultMaxSize is the default maximum number of items in the cache. diff --git a/error_recovery.go b/error_recovery.go index b327159..0910bda 100644 --- a/error_recovery.go +++ b/error_recovery.go @@ -26,25 +26,18 @@ const ( // CircuitBreaker implements the circuit breaker pattern for external service calls type CircuitBreaker struct { - // Configuration - maxFailures int // Maximum failures before opening - timeout time.Duration // How long to wait before trying again - resetTimeout time.Duration // How long to wait in half-open state - - // State - state CircuitBreakerState - failures int64 lastFailureTime time.Time lastSuccessTime time.Time + logger *Logger + maxFailures int + timeout time.Duration + resetTimeout time.Duration + state CircuitBreakerState + failures int64 + totalRequests int64 + totalFailures int64 + totalSuccesses int64 mutex sync.RWMutex - - // Metrics - totalRequests int64 - totalFailures int64 - totalSuccesses int64 - - // Logger - logger *Logger } // CircuitBreakerConfig holds configuration for circuit breakers @@ -193,12 +186,12 @@ func (cb *CircuitBreaker) GetMetrics() map[string]any { // RetryConfig holds configuration for retry mechanisms type RetryConfig struct { + RetryableErrors []string `json:"retryable_errors"` MaxAttempts int `json:"max_attempts"` InitialDelay time.Duration `json:"initial_delay"` MaxDelay time.Duration `json:"max_delay"` BackoffFactor float64 `json:"backoff_factor"` EnableJitter bool `json:"enable_jitter"` - RetryableErrors []string `json:"retryable_errors"` } // DefaultRetryConfig returns default retry configuration @@ -220,8 +213,8 @@ func DefaultRetryConfig() RetryConfig { // RetryExecutor implements retry logic with exponential backoff type RetryExecutor struct { - config RetryConfig logger *Logger + config RetryConfig } // NewRetryExecutor creates a new retry executor @@ -344,8 +337,8 @@ func (re *RetryExecutor) calculateDelay(attempt int) time.Duration { // HTTPError represents an HTTP error with status code type HTTPError struct { - StatusCode int Message string + StatusCode int } // Error implements the error interface @@ -355,20 +348,12 @@ func (e *HTTPError) Error() string { // GracefulDegradation implements graceful degradation patterns type GracefulDegradation struct { - // Fallback functions for different operations - fallbacks map[string]func() (any, error) - - // Health checks for dependencies - healthChecks map[string]func() bool - - // Configuration - config GracefulDegradationConfig - - // State tracking + fallbacks map[string]func() (any, error) + healthChecks map[string]func() bool degradedServices map[string]time.Time + logger *Logger + config GracefulDegradationConfig mutex sync.RWMutex - - logger *Logger } // GracefulDegradationConfig holds configuration for graceful degradation @@ -537,8 +522,8 @@ type ErrorRecoveryManager struct { circuitBreakers map[string]*CircuitBreaker retryExecutor *RetryExecutor gracefulDegradation *GracefulDegradation - mutex sync.RWMutex logger *Logger + mutex sync.RWMutex } // NewErrorRecoveryManager creates a new error recovery manager diff --git a/helpers.go b/helpers.go index 138bcae..595d7b0 100644 --- a/helpers.go +++ b/helpers.go @@ -72,20 +72,11 @@ func deriveCodeChallenge(codeVerifier string) string { // It contains the various tokens and metadata returned after successful // code exchange or token refresh operations. type TokenResponse struct { - // IDToken is the OIDC ID token containing user claims - IDToken string `json:"id_token"` - - // AccessToken is the OAuth 2.0 access token for API access - AccessToken string `json:"access_token"` - - // RefreshToken is the OAuth 2.0 refresh token for obtaining new tokens + IDToken string `json:"id_token"` + AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` - - // ExpiresIn is the lifetime in seconds of the access token - ExpiresIn int `json:"expires_in"` - - // TokenType is the type of token, typically "Bearer" - TokenType string `json:"token_type"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` } // exchangeTokens performs the OAuth 2.0 token exchange with the OIDC provider's token endpoint. diff --git a/input_validation.go b/input_validation.go index a74ad4c..5c9dc9a 100644 --- a/input_validation.go +++ b/input_validation.go @@ -11,35 +11,29 @@ import ( // InputValidator provides comprehensive input validation and sanitization type InputValidator struct { - // Configuration - maxTokenLength int - maxURLLength int - maxHeaderLength int - maxClaimLength int - maxEmailLength int - maxUsernameLength int - - // Compiled regex patterns - emailRegex *regexp.Regexp - urlRegex *regexp.Regexp - tokenRegex *regexp.Regexp - usernameRegex *regexp.Regexp - - // Security patterns to detect + usernameRegex *regexp.Regexp + tokenRegex *regexp.Regexp + logger *Logger + urlRegex *regexp.Regexp + emailRegex *regexp.Regexp sqlInjectionPatterns []string - xssPatterns []string pathTraversalPatterns []string - - logger *Logger + xssPatterns []string + maxUsernameLength int + maxURLLength int + maxTokenLength int + maxEmailLength int + maxClaimLength int + maxHeaderLength int } // ValidationResult represents the result of input validation type ValidationResult struct { - IsValid bool `json:"is_valid"` - Errors []string `json:"errors,omitempty"` - Warnings []string `json:"warnings,omitempty"` SanitizedValue string `json:"sanitized_value,omitempty"` SecurityRisk string `json:"security_risk,omitempty"` + Errors []string `json:"errors,omitempty"` + Warnings []string `json:"warnings,omitempty"` + IsValid bool `json:"is_valid"` } // InputValidationConfig holds configuration for input validation diff --git a/input_validation_test.go b/input_validation_test.go index 510bfb5..cd196c3 100644 --- a/input_validation_test.go +++ b/input_validation_test.go @@ -204,8 +204,8 @@ func TestSanitizeInput(t *testing.T) { tests := []struct { name string input string - maxLen int expected string + maxLen int }{ { name: "Normal text", diff --git a/jwk.go b/jwk.go index 1ff5e90..28d5e37 100644 --- a/jwk.go +++ b/jwk.go @@ -33,13 +33,12 @@ type JWKSet struct { } type JWKCache struct { - jwks *JWKSet - expiresAt time.Time - mutex sync.RWMutex - // CacheLifetime is configurable to determine how long the JWKS is cached. + expiresAt time.Time + jwks *JWKSet + internalCache *Cache CacheLifetime time.Duration - internalCache *Cache // To hold the closable Cache instance from cache.go - maxSize int // Maximum number of items in the cache + maxSize int + mutex sync.RWMutex } type JWKCacheInterface interface { diff --git a/jwt.go b/jwt.go index cf14af7..9f94d49 100644 --- a/jwt.go +++ b/jwt.go @@ -89,8 +89,8 @@ var ClockSkewTolerance = ClockSkewToleranceFuture type JWT struct { Header map[string]any Claims map[string]any - Signature []byte Token string + Signature []byte } // parseJWT decodes a raw JWT string into its constituent parts: header, claims, and signature. diff --git a/main.go b/main.go index 8c38d88..65670c6 100644 --- a/main.go +++ b/main.go @@ -196,52 +196,51 @@ type TokenExchanger interface { // TraefikOidc is the main struct for the OIDC middleware type TraefikOidc struct { + jwkCache JWKCacheInterface + jwtVerifier JWTVerifier + ctx context.Context + tokenVerifier TokenVerifier next http.Handler + tokenExchanger TokenExchanger + initComplete chan struct{} + limiter *rate.Limiter + tokenBlacklist *Cache + headerTemplates map[string]*template.Template + sessionManager *SessionManager + tokenCleanupStopChan chan struct{} + excludedURLs map[string]struct{} + extractClaimsFunc func(tokenString string) (map[string]any, error) + initiateAuthenticationFunc func(rw http.ResponseWriter, req *http.Request, session *SessionData, redirectURL string) + metadataCache *MetadataCache + allowedRolesAndGroups map[string]struct{} + allowedUsers map[string]struct{} + allowedUserDomains map[string]struct{} + tokenCache *TokenCache + httpClient *http.Client + tokenHTTPClient *http.Client + logger *Logger + metadataRefreshStopChan chan struct{} + cancelFunc context.CancelFunc + clientSecret string + clientID string name string redirURLPath string logoutURLPath string + tokenURL string + authURL string + endSessionURL string + postLogoutRedirectURI string + scheme string + jwksURL string issuerURL string revocationURL string - jwkCache JWKCacheInterface - metadataCache *MetadataCache - tokenBlacklist *Cache // Replaced TokenBlacklist with generic Cache - jwksURL string - clientID string - clientSecret string - authURL string - tokenURL string scopes []string - limiter *rate.Limiter + goroutineWG sync.WaitGroup + refreshGracePeriod time.Duration + shutdownOnce sync.Once forceHTTPS bool enablePKCE bool - scheme string - tokenCache *TokenCache - httpClient *http.Client - tokenHTTPClient *http.Client // Reusable HTTP client for token operations - logger *Logger - tokenVerifier TokenVerifier - jwtVerifier JWTVerifier - excludedURLs map[string]struct{} - allowedUserDomains map[string]struct{} - allowedUsers map[string]struct{} // Map for case-insensitive lookup of allowed email addresses - allowedRolesAndGroups map[string]struct{} - initiateAuthenticationFunc func(rw http.ResponseWriter, req *http.Request, session *SessionData, redirectURL string) - // exchangeCodeForTokenFunc func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error) // Replaced by interface - extractClaimsFunc func(tokenString string) (map[string]any, error) - initComplete chan struct{} - endSessionURL string - postLogoutRedirectURI string - sessionManager *SessionManager - tokenExchanger TokenExchanger // Added field for mocking - refreshGracePeriod time.Duration // Configurable grace period for proactive refresh - headerTemplates map[string]*template.Template // Parsed templates for custom headers - tokenCleanupStopChan chan struct{} // Channel to stop token cleanup goroutine - metadataRefreshStopChan chan struct{} // Channel to stop metadata refresh goroutine - goroutineWG sync.WaitGroup // WaitGroup to track background goroutines - ctx context.Context - cancelFunc context.CancelFunc - shutdownOnce sync.Once - suppressDiagnosticLogs bool // Flag to suppress diagnostic logs during tests + suppressDiagnosticLogs bool } // ProviderMetadata holds OIDC provider metadata @@ -1195,11 +1194,10 @@ func (t *TraefikOidc) processAuthorizedRequest(rw http.ResponseWriter, req *http // Create template data context with available tokens and claims // Fields must be exported (uppercase) to be accessible in templates templateData := struct { - // These fields need to be exported (uppercase) for template access + Claims map[string]any AccessToken string IdToken string RefreshToken string - Claims map[string]any }{ AccessToken: session.GetAccessToken(), // Provide AccessToken for templates if needed IdToken: session.GetIDToken(), diff --git a/main_test.go b/main_test.go index 69564d5..ac608ea 100644 --- a/main_test.go +++ b/main_test.go @@ -30,8 +30,8 @@ type TestSuite struct { ecPrivateKey *ecdsa.PrivateKey tOidc *TraefikOidc mockJWKCache *MockJWKCache - token string sessionManager *SessionManager + token string } // Setup initializes the test suite @@ -410,15 +410,15 @@ func TestServeHTTP(t *testing.T) { } tests := []struct { - name string - requestPath string sessionValues map[any]any - expectedStatus int - expectedBody string setupSession func(*SessionData) mockRefreshTokenFunc func(originalFunc func(refreshToken string) (*TokenResponse, error)) func(refreshToken string) (*TokenResponse, error) - assertSessionAfterRequest func(t *testing.T, rr *httptest.ResponseRecorder, req *http.Request, sessionManager *SessionManager) // Added for post-request checks - requestHeaders map[string]string // Added for setting headers like Accept + assertSessionAfterRequest func(t *testing.T, rr *httptest.ResponseRecorder, req *http.Request, sessionManager *SessionManager) + requestHeaders map[string]string + name string + requestPath string + expectedBody string + expectedStatus int }{ { name: "Excluded URL", @@ -873,10 +873,10 @@ func TestJWKToPEM(t *testing.T) { ts.Setup() tests := []struct { - name string jwk *JWK - expectError bool + name string errorContains string + expectError bool }{ { name: "Unsupported Key Type", @@ -928,8 +928,8 @@ func TestParseJWT(t *testing.T) { tests := []struct { name string token string - expectError bool errorContains string + expectError bool }{ { name: "Invalid Format", @@ -989,11 +989,11 @@ func TestHandleCallback(t *testing.T) { redirectURL := "http://example.com/" tests := []struct { - name string - queryParams string exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error) extractClaimsFunc func(tokenString string) (map[string]any, error) sessionSetupFunc func(*SessionData) + name string + queryParams string expectedStatus int }{ { @@ -1256,12 +1256,12 @@ func TestIsAllowedDomain(t *testing.T) { ts.Setup() tests := []struct { - name string - email string allowedDomains map[string]struct{} allowedUsers map[string]struct{} + name string + email string + expectedLogOutput string allowed bool - expectedLogOutput string // For testing log messages }{ { name: "Allowed domain", @@ -1343,11 +1343,11 @@ func TestOIDCHandler(t *testing.T) { ts.token = "valid.jwt.token" tests := []struct { - name string - queryParams string exchangeCodeForToken func(code string, redirectURL string, codeVerifier string) (*TokenResponse, error) extractClaimsFunc func(tokenString string) (map[string]any, error) sessionSetupFunc func(session *sessions.Session) + name string + queryParams string expectedStatus int blacklist bool rateLimit bool @@ -1504,12 +1504,12 @@ func TestHandleLogout(t *testing.T) { defer mockRevocationServer.Close() tests := []struct { - name string setupSession func(*SessionData) + name string endSessionURL string - expectedStatus int expectedURL string host string + expectedStatus int }{ { name: "Successful logout with end session endpoint", @@ -2104,12 +2104,12 @@ func TestServeHTTPRolesAndGroups(t *testing.T) { nbf := now.Add(-2 * time.Minute).Unix() // Account for clock skew tests := []struct { - name string allowedRolesAndGroups map[string]struct{} claims map[string]any setupSession func(*SessionData) - expectedStatus int expectedHeaders map[string]string + name string + expectedStatus int }{ { name: "User with allowed role", @@ -2313,10 +2313,10 @@ func TestExchangeTokensWithRedirects(t *testing.T) { ts.Setup() tests := []struct { - name string setupServer func() *httptest.Server - expectError bool + name string errorContains string + expectError bool }{ { name: "Successful token exchange with redirects", @@ -2424,9 +2424,9 @@ func TestBuildAuthURL(t *testing.T) { redirectURL string state string nonce string - enablePKCE bool codeChallenge string expectedPrefix string + enablePKCE bool checkPKCE bool }{ { @@ -2574,10 +2574,10 @@ func TestExchangeCodeForToken(t *testing.T) { ts.Setup() tests := []struct { - name string - enablePKCE bool - codeVerifier string setupMock func(t *testing.T) *httptest.Server + name string + codeVerifier string + enablePKCE bool }{ { name: "With PKCE Enabled and Code Verifier", @@ -2883,10 +2883,10 @@ func TestJWTVerifyWithSkipReplayCheck(t *testing.T) { tests := []struct { name string + errorContains string skipReplayCheck bool firstCall bool expectError bool - errorContains string }{ { name: "First verification with skipReplayCheck=false should succeed", @@ -3339,10 +3339,10 @@ func TestJTIBlacklistBehavior(t *testing.T) { // Test JTI blacklist behavior tests := []struct { - name string action func() error - expectError bool + name string description string + expectError bool }{ { name: "Initial verification adds JTI to blacklist", @@ -3495,9 +3495,9 @@ func TestEdgeCasesWithDifferentTokenTypes(t *testing.T) { nbf := now.Unix() tests := []struct { + claims map[string]any name string tokenType string - claims map[string]any expectError bool }{ { @@ -3697,10 +3697,10 @@ func TestScopeMergingEdgeCases(t *testing.T) { tests := []struct { name string + description string defaultScopes []string userScopes []string expectedScopes []string - description string }{ { name: "Case sensitivity preserved", @@ -3974,8 +3974,8 @@ func TestBuildAuthURLWithMergedScopes(t *testing.T) { tests := []struct { name string - scopes []string expectedScopes string + scopes []string }{ { name: "Default scopes only", diff --git a/metadata_cache.go b/metadata_cache.go index 9ad8907..895f1f2 100644 --- a/metadata_cache.go +++ b/metadata_cache.go @@ -8,11 +8,11 @@ import ( ) type MetadataCache struct { - metadata *ProviderMetadata expiresAt time.Time - mutex sync.RWMutex - autoCleanupInterval time.Duration + metadata *ProviderMetadata stopCleanup chan struct{} + autoCleanupInterval time.Duration + mutex sync.RWMutex } // NewMetadataCache creates a new MetadataCache instance. diff --git a/performance_monitoring.go b/performance_monitoring.go index 555e764..d499d3c 100644 --- a/performance_monitoring.go +++ b/performance_monitoring.go @@ -10,61 +10,41 @@ import ( // PerformanceMetrics tracks various performance-related metrics type PerformanceMetrics struct { - // Cache metrics - cacheHits int64 - cacheMisses int64 - cacheEvictions int64 - cacheSize int64 - - // Token operation metrics - tokenVerifications int64 - tokenValidations int64 - tokenRefreshes int64 - - // Success/failure tracking + startTime time.Time + logger *Logger + verificationTimes []time.Duration + refreshTimes []time.Duration + validationTimes []time.Duration + memoryUsage int64 + heapSize int64 successfulVerifications int64 successfulValidations int64 successfulRefreshes int64 failedVerifications int64 failedValidations int64 failedRefreshes int64 - - // Timing metrics - avgVerificationTime time.Duration - avgValidationTime time.Duration - avgRefreshTime time.Duration - - // Resource metrics - memoryUsage int64 - goroutineCount int64 - memoryPressure int64 // Memory pressure level (0-100) - gcPauseTime int64 // Last GC pause time in nanoseconds - heapSize int64 // Current heap size - heapInUse int64 // Heap memory in use - - // Error metrics (kept for backward compatibility) - verificationErrors int64 - validationErrors int64 - refreshErrors int64 - - // Rate limiting metrics - rateLimitedRequests int64 - - // Session metrics - activeSessions int64 - sessionCreations int64 - sessionDeletions int64 - - // Timing tracking - timingMutex sync.RWMutex - verificationTimes []time.Duration - validationTimes []time.Duration - refreshTimes []time.Duration - - // Start time for uptime calculation - startTime time.Time - - logger *Logger + avgVerificationTime time.Duration + avgValidationTime time.Duration + avgRefreshTime time.Duration + cacheHits int64 + goroutineCount int64 + memoryPressure int64 + gcPauseTime int64 + tokenRefreshes int64 + heapInUse int64 + verificationErrors int64 + validationErrors int64 + refreshErrors int64 + rateLimitedRequests int64 + activeSessions int64 + sessionCreations int64 + sessionDeletions int64 + cacheMisses int64 + tokenValidations int64 + tokenVerifications int64 + cacheSize int64 + cacheEvictions int64 + timingMutex sync.RWMutex } // NewPerformanceMetrics creates a new performance metrics tracker @@ -441,38 +421,26 @@ func (pm *PerformanceMetrics) calculateTimingStats(times []time.Duration) map[st // ResourceMonitor tracks resource usage and limits type ResourceMonitor struct { - // Memory limits - maxMemoryBytes int64 - - // Cache limits - maxCacheSize int64 - - // Session limits - maxSessions int64 - - // Cache size tracking - cacheSizes map[string]int64 - cacheMutex sync.RWMutex - - // Monitoring state + cacheSizes map[string]int64 alertThresholds map[string]float64 + perfMetrics *PerformanceMetrics + logger *Logger alerts []ResourceAlert + maxMemoryBytes int64 + maxCacheSize int64 + maxSessions int64 + cacheMutex sync.RWMutex alertsMutex sync.RWMutex - - // Performance metrics reference - perfMetrics *PerformanceMetrics - - logger *Logger } // ResourceAlert represents a resource usage alert type ResourceAlert struct { + Timestamp time.Time `json:"timestamp"` Type string `json:"type"` Message string `json:"message"` + Severity string `json:"severity"` Threshold float64 `json:"threshold"` CurrentValue float64 `json:"current_value"` - Timestamp time.Time `json:"timestamp"` - Severity string `json:"severity"` } // NewResourceMonitor creates a new resource monitor diff --git a/security_edge_cases_test.go b/security_edge_cases_test.go index 0ebbf64..7ef0259 100644 --- a/security_edge_cases_test.go +++ b/security_edge_cases_test.go @@ -390,8 +390,8 @@ func TestMissingClaims(t *testing.T) { // Test cases for missing claims testCases := []struct { name string - omittedClaims []string expectedError string + omittedClaims []string }{ { name: "Missing Issuer", diff --git a/security_monitoring.go b/security_monitoring.go index 5c7821e..512c1ca 100644 --- a/security_monitoring.go +++ b/security_monitoring.go @@ -13,67 +13,51 @@ import ( // SecurityEvent represents a security-related event that should be logged and monitored type SecurityEvent struct { + Timestamp time.Time `json:"timestamp"` + Details map[string]any `json:"details,omitempty"` Type string `json:"type"` Severity string `json:"severity"` - Timestamp time.Time `json:"timestamp"` ClientIP string `json:"client_ip"` UserAgent string `json:"user_agent"` RequestPath string `json:"request_path"` Message string `json:"message"` - Details map[string]any `json:"details,omitempty"` } // SecurityMonitor tracks security events and suspicious activity patterns type SecurityMonitor struct { - // Event counters + ipFailures map[string]*IPFailureTracker + patternDetector *SuspiciousPatternDetector + logger *Logger + eventHandlers []SecurityEventHandler + config SecurityMonitorConfig authFailures int64 tokenValidationFails int64 rateLimitHits int64 suspiciousRequests int64 - - // IP-based tracking - ipFailures map[string]*IPFailureTracker - ipMutex sync.RWMutex - - // Pattern detection - patternDetector *SuspiciousPatternDetector - - // Event handlers - eventHandlers []SecurityEventHandler - - // Configuration - config SecurityMonitorConfig - - // Logger - logger *Logger + ipMutex sync.RWMutex } // IPFailureTracker tracks failures for a specific IP address type IPFailureTracker struct { - FailureCount int64 LastFailure time.Time FirstFailure time.Time - FailureTypes map[string]int64 - IsBlocked bool BlockedUntil time.Time + FailureTypes map[string]int64 + FailureCount int64 mutex sync.RWMutex + IsBlocked bool } // SuspiciousPatternDetector identifies patterns that may indicate attacks type SuspiciousPatternDetector struct { - // Time-based windows for pattern detection - shortWindow time.Duration // 1 minute - mediumWindow time.Duration // 5 minutes - longWindow time.Duration // 15 minutes - - // Pattern thresholds - rapidFailureThreshold int // failures in short window - distributedAttackThreshold int // failures across IPs in medium window - persistentAttackThreshold int // failures in long window - - // Pattern tracking - recentEvents []SecurityEvent - eventsMutex sync.RWMutex + recentEvents []SecurityEvent + shortWindow time.Duration + mediumWindow time.Duration + longWindow time.Duration + rapidFailureThreshold int + distributedAttackThreshold int + persistentAttackThreshold int + eventsMutex sync.RWMutex } // SecurityEventHandler defines the interface for handling security events @@ -83,22 +67,15 @@ type SecurityEventHandler interface { // SecurityMonitorConfig contains configuration for the security monitor type SecurityMonitorConfig struct { - // Failure thresholds - MaxFailuresPerIP int `json:"max_failures_per_ip"` - FailureWindowMinutes int `json:"failure_window_minutes"` - BlockDurationMinutes int `json:"block_duration_minutes"` - - // Pattern detection settings - EnablePatternDetection bool `json:"enable_pattern_detection"` + MaxFailuresPerIP int `json:"max_failures_per_ip"` + FailureWindowMinutes int `json:"failure_window_minutes"` + BlockDurationMinutes int `json:"block_duration_minutes"` RapidFailureThreshold int `json:"rapid_failure_threshold"` - - // Monitoring settings - EnableDetailedLogging bool `json:"enable_detailed_logging"` - LogSuspiciousOnly bool `json:"log_suspicious_only"` - - // Cleanup settings - CleanupIntervalMinutes int `json:"cleanup_interval_minutes"` - RetentionHours int `json:"retention_hours"` + CleanupIntervalMinutes int `json:"cleanup_interval_minutes"` + RetentionHours int `json:"retention_hours"` + EnablePatternDetection bool `json:"enable_pattern_detection"` + EnableDetailedLogging bool `json:"enable_detailed_logging"` + LogSuspiciousOnly bool `json:"log_suspicious_only"` } // DefaultSecurityMonitorConfig returns a default configuration diff --git a/session.go b/session.go index 4c24942..3957936 100644 --- a/session.go +++ b/session.go @@ -213,17 +213,10 @@ func decompressTokenInternal(compressed string) string { // It provides functionality for storing and retrieving authentication state, tokens, // and other session-related data across multiple cookies. type SessionManager struct { - // store is the underlying session store for cookie management. - store sessions.Store - - // forceHTTPS enforces secure cookie attributes regardless of request scheme. - forceHTTPS bool - - // logger provides structured logging capabilities. - logger *Logger - - // sessionPool is a sync.Pool for reusing SessionData objects. sessionPool sync.Pool + store sessions.Store + logger *Logger + forceHTTPS bool } // NewSessionManager creates a new session manager with the specified configuration. diff --git a/session_test.go b/session_test.go index eeede7a..091fc1f 100644 --- a/session_test.go +++ b/session_test.go @@ -441,8 +441,8 @@ func TestTokenChunkingCorruptionResistance(t *testing.T) { } tests := []struct { - name string corruptChunk func(chunks map[int]*sessions.Session) + name string expectEmpty bool }{ { diff --git a/settings.go b/settings.go index d6a7cfe..ffeff65 100644 --- a/settings.go +++ b/settings.go @@ -26,96 +26,27 @@ type TemplatedHeader struct { // It provides all necessary settings to configure OpenID Connect authentication // with various providers like Auth0, Logto, or any standard OIDC provider. type Config struct { - // ProviderURL is the base URL of the OIDC provider (required) - // Example: https://accounts.google.com - ProviderURL string `json:"providerURL"` - - // RevocationURL is the endpoint for revoking tokens (optional) - // If not provided, it will be discovered from provider metadata - RevocationURL string `json:"revocationURL"` - - // EnablePKCE enables Proof Key for Code Exchange (PKCE) for the authorization code flow (optional) - // This enhances security but might not be supported by all OIDC providers - // Default: false - EnablePKCE bool `json:"enablePKCE"` - - // CallbackURL is the path where the OIDC provider will redirect after authentication (required) - // Example: /oauth2/callback - CallbackURL string `json:"callbackURL"` - - // LogoutURL is the path for handling logout requests (optional) - // If not provided, it will be set to CallbackURL + "/logout" - LogoutURL string `json:"logoutURL"` - - // ClientID is the OAuth 2.0 client identifier (required) - ClientID string `json:"clientID"` - - // ClientSecret is the OAuth 2.0 client secret (required) - ClientSecret string `json:"clientSecret"` - - // Scopes defines the OAuth 2.0 scopes to request (optional) - // Defaults to ["openid", "profile", "email"] if not provided - Scopes []string `json:"scopes"` - - // LogLevel sets the logging verbosity (optional) - // Valid values: "debug", "info", "error" - // Default: "info" - LogLevel string `json:"logLevel"` - - // SessionEncryptionKey is used to encrypt session data (required) - // Must be a secure random string - SessionEncryptionKey string `json:"sessionEncryptionKey"` - - // ForceHTTPS forces the use of HTTPS for all URLs (optional) - // Default: false - ForceHTTPS bool `json:"forceHTTPS"` - - // RateLimit sets the maximum number of requests per second (optional) - // Default: 100 - RateLimit int `json:"rateLimit"` - - // ExcludedURLs lists paths that bypass authentication (optional) - // Example: ["/health", "/metrics"] - ExcludedURLs []string `json:"excludedURLs"` - - // AllowedUserDomains restricts access to specific email domains (optional) - // Example: ["company.com", "subsidiary.com"] - AllowedUserDomains []string `json:"allowedUserDomains"` - - // AllowedUsers restricts access to specific email addresses (optional) - // Example: ["user1@example.com", "user2@example.com"] - AllowedUsers []string `json:"allowedUsers"` - - // AllowedRolesAndGroups restricts access to users with specific roles or groups (optional) - // Example: ["admin", "developer"] - AllowedRolesAndGroups []string `json:"allowedRolesAndGroups"` - - // OIDCEndSessionURL is the provider's end session endpoint (optional) - // If not provided, it will be discovered from provider metadata - OIDCEndSessionURL string `json:"oidcEndSessionURL"` - - // PostLogoutRedirectURI is the URL to redirect to after logout (optional) - // Default: "/" - PostLogoutRedirectURI string `json:"postLogoutRedirectURI"` - - // HTTPClient allows customizing the HTTP client used for OIDC operations (optional) - HTTPClient *http.Client - - // RefreshGracePeriodSeconds defines how many seconds before a token expires - // the plugin should attempt to refresh it proactively (optional) - // Default: 60 - RefreshGracePeriodSeconds int `json:"refreshGracePeriodSeconds"` - // Headers defines custom HTTP headers to set with templated values (optional) - // Values can reference tokens and claims using Go templates with the following variables: - // - {{.AccessToken}} - The access token (ID token) - // - {{.IdToken}} - Same as AccessToken (for consistency) - // - {{.RefreshToken}} - The refresh token - // - {{.Claims.email}} - Access token claims (use proper case for claim names) - // Examples: - // - // [{Name: "X-Forwarded-Email", Value: "{{.Claims.email}}"}] - // [{Name: "Authorization", Value: "Bearer {{.AccessToken}}"}] - Headers []TemplatedHeader `json:"headers"` + HTTPClient *http.Client + ProviderURL string `json:"providerURL"` + RevocationURL string `json:"revocationURL"` + CallbackURL string `json:"callbackURL"` + LogoutURL string `json:"logoutURL"` + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + PostLogoutRedirectURI string `json:"postLogoutRedirectURI"` + LogLevel string `json:"logLevel"` + SessionEncryptionKey string `json:"sessionEncryptionKey"` + OIDCEndSessionURL string `json:"oidcEndSessionURL"` + AllowedRolesAndGroups []string `json:"allowedRolesAndGroups"` + ExcludedURLs []string `json:"excludedURLs"` + AllowedUserDomains []string `json:"allowedUserDomains"` + AllowedUsers []string `json:"allowedUsers"` + Scopes []string `json:"scopes"` + Headers []TemplatedHeader `json:"headers"` + RateLimit int `json:"rateLimit"` + RefreshGracePeriodSeconds int `json:"refreshGracePeriodSeconds"` + ForceHTTPS bool `json:"forceHTTPS"` + EnablePKCE bool `json:"enablePKCE"` } const ( diff --git a/settings_test.go b/settings_test.go index 110b60d..edafc01 100644 --- a/settings_test.go +++ b/settings_test.go @@ -254,10 +254,10 @@ func TestLogger(t *testing.T) { var debugBuf, infoBuf, errorBuf bytes.Buffer tests := []struct { - name string - logLevel string testFunc func(*Logger) checkFunc func(t *testing.T, debugOut, infoOut, errorOut string) + name string + logLevel string }{ { name: "Debug Level", @@ -405,9 +405,9 @@ func TestHandleError(t *testing.T) { // Test helper types type testResponseRecorder struct { - statusCode int - body string headers map[string][]string + body string + statusCode int } func (r *testResponseRecorder) Header() http.Header { diff --git a/templated_header_execution_test.go b/templated_header_execution_test.go index 37c5f46..cb9ef9d 100644 --- a/templated_header_execution_test.go +++ b/templated_header_execution_test.go @@ -178,10 +178,10 @@ func TestTemplateExecution(t *testing.T) { func TestTemplateExecutionContext(t *testing.T) { // Define a test struct that matches the one used in processAuthorizedRequest type templateData struct { + Claims map[string]any AccessToken string IdToken string RefreshToken string - Claims map[string]any } // Test cases diff --git a/templated_header_integration_test.go b/templated_header_integration_test.go index 3de9c41..9b8c17a 100644 --- a/templated_header_integration_test.go +++ b/templated_header_integration_test.go @@ -20,12 +20,12 @@ func TestTemplatedHeadersIntegration(t *testing.T) { ts.Setup() tests := []struct { - name string - headers []TemplatedHeader sessionSetup func(*SessionData) claims map[string]any expectedHeaders map[string]string interceptedHeaders map[string]string + name string + headers []TemplatedHeader }{ { name: "Basic Email Header", @@ -423,9 +423,9 @@ func TestEdgeCaseTemplatedHeaders(t *testing.T) { ts.Setup() tests := []struct { + claims map[string]any name string headers []TemplatedHeader - claims map[string]any shouldExecuteCheck bool }{ { diff --git a/token_corruption_test.go b/token_corruption_test.go index bc30c6f..d0b3a47 100644 --- a/token_corruption_test.go +++ b/token_corruption_test.go @@ -28,11 +28,11 @@ func TestTokenCorruptionScenario(t *testing.T) { validJWT := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6OTk5OTk5OTk5OX0.signature" tests := []struct { + corruptionScenario func(*SessionData) name string tokenSize int iterations int expectConsistent bool - corruptionScenario func(*SessionData) // Function to simulate corruption }{ { name: "Small token - multiple retrievals", @@ -207,8 +207,8 @@ func TestChunkReassemblyEdgeCases(t *testing.T) { // Test various corruption scenarios corruptionTests := []struct { - name string corruption func(map[int]*sessions.Session) + name string expectEmpty bool }{ { @@ -367,8 +367,8 @@ func TestRaceConditionProtection(t *testing.T) { // TestMemoryExhaustionProtection tests protection against memory exhaustion attacks func TestMemoryExhaustionProtection(t *testing.T) { tests := []struct { - name string setupCorruption func() string + name string expectRejection bool }{ { diff --git a/token_handling_test.go b/token_handling_test.go index 4f97c9a..f2a30f2 100644 --- a/token_handling_test.go +++ b/token_handling_test.go @@ -19,10 +19,10 @@ import ( func TestTokenTypeDistinction(t *testing.T) { // Define test data where AccessToken and IdToken are deliberately different type templateData struct { + Claims map[string]any AccessToken string IdToken string RefreshToken string - Claims map[string]any } testData := templateData{ @@ -323,12 +323,12 @@ func TestTokenCorruptionIntegrationFlows(t *testing.T) { } tests := []struct { + corruptAction func(*SessionData) name string accessToken string refreshToken string idToken string expectSuccess bool - corruptAction func(*SessionData) // Function to corrupt session after storage }{ { name: "Normal flow - small tokens",