mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-17 04:37:48 +00:00
fix(auth): harden OIDC flow, token validation, session, and bearer handling
- replay entry covers exp + clock-skew window; callback GET-only (RFC 6749) - callback rate-limit returns 429 + Retry-After; panic-recovery sets no-store - cross-chunk integrity for chunked tokens; bounded chunked-token read scan - HTTPError classification so 5xx/429 token-endpoint responses retry - redaction of token/code/state in logs; per-source external auth gate
This commit is contained in:
+21
-11
@@ -118,18 +118,15 @@ func (trm *TokenResilienceManager) ExecuteTokenOperation(ctx context.Context, op
|
||||
return fn()
|
||||
}
|
||||
|
||||
// Compose resilience mechanisms
|
||||
// Compose resilience mechanisms. Retry is the INNER layer (around the
|
||||
// operation) and the circuit breaker is the OUTER layer, so the breaker
|
||||
// observes one aggregate result per logical operation rather than one
|
||||
// failure per retry attempt (which would trip it prematurely — e.g.
|
||||
// maxFailures=3 + 3 retries opening the breaker on a single brief
|
||||
// outage, failing the very next genuine request). R106.
|
||||
var finalOperation func() error = fn
|
||||
|
||||
// Wrap with circuit breaker if enabled
|
||||
if trm.config.CircuitBreakerEnabled && trm.circuitBreaker != nil {
|
||||
originalOp := finalOperation
|
||||
finalOperation = func() error {
|
||||
return trm.circuitBreaker.ExecuteWithContext(ctx, originalOp)
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap with retry if enabled
|
||||
// Wrap with retry if enabled (innermost layer)
|
||||
if trm.config.RetryEnabled && trm.retryExecutor != nil {
|
||||
originalOp := finalOperation
|
||||
if singleUse {
|
||||
@@ -143,6 +140,14 @@ func (trm *TokenResilienceManager) ExecuteTokenOperation(ctx context.Context, op
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap with circuit breaker if enabled (outermost layer)
|
||||
if trm.config.CircuitBreakerEnabled && trm.circuitBreaker != nil {
|
||||
originalOp := finalOperation
|
||||
finalOperation = func() error {
|
||||
return trm.circuitBreaker.ExecuteWithContext(ctx, originalOp)
|
||||
}
|
||||
}
|
||||
|
||||
err := finalOperation()
|
||||
|
||||
if err != nil && trm.logger != nil {
|
||||
@@ -174,7 +179,12 @@ func (trm *TokenResilienceManager) ExecuteTokenRefresh(ctx context.Context, t *T
|
||||
var result *TokenResponse
|
||||
var err error
|
||||
|
||||
err = trm.ExecuteTokenOperation(ctx, "token_refresh", false, func() error {
|
||||
// Refresh is single-use (like the auth-code exchange): a client-side timeout
|
||||
// may mean the provider already processed AND rotated the refresh token, so
|
||||
// re-presenting it on a retry surfaces invalid_grant and the session is
|
||||
// cleared — escalating a transient timeout into a full logout. Single-use
|
||||
// retries only on "never sent" errors (connect/dial), never on timeout.
|
||||
err = trm.ExecuteTokenOperation(ctx, "token_refresh", true, func() error {
|
||||
// Call exchangeTokens directly to avoid recursion back to getNewTokenWithRefreshToken
|
||||
// which would call ExecuteTokenRefresh again, causing infinite loop (issue #67)
|
||||
result, err = t.exchangeTokens(ctx, "refresh_token", refreshToken, "", "")
|
||||
|
||||
Reference in New Issue
Block a user