mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-12 04:04:26 +00:00
- 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
72 lines
2.6 KiB
Go
72 lines
2.6 KiB
Go
// Package traefikoidc provides OIDC authentication middleware for Traefik.
|
|
// requestState bundles read-mostly fields for a single ServeHTTP call.
|
|
package traefikoidc
|
|
|
|
import "net/http"
|
|
|
|
// requestState is a per-request context object allocated at the top of
|
|
// ServeHTTP and threaded through to downstream handlers. It caches values
|
|
// that would otherwise require a Yaegi-dispatched lock acquisition each time
|
|
// they're read:
|
|
//
|
|
// - The metadata snapshot (atomic.Value.Load once, not per-handler).
|
|
// - SessionData getter results (one RLock on sd.sessionMutex covers all
|
|
// fields, instead of 5-7 separate RLock/RUnlock pairs scattered through
|
|
// the handler chain).
|
|
//
|
|
// The struct is alloc'd at request entry, populated under at most one RLock
|
|
// of sd.sessionMutex, and discarded at request exit. It is NOT shared across
|
|
// requests and never written from another goroutine, so no synchronization
|
|
// on its fields is required.
|
|
//
|
|
// Cross-request global caches (tokenCache, JWKCache, sessionEntries,
|
|
// sessionInvalidationCache) remain — they're orthogonal. requestState's job
|
|
// is to eliminate redundant per-handler reads of values that don't change
|
|
// within a single request.
|
|
type requestState struct {
|
|
// Globals snapshotted once.
|
|
metadata *MetadataSnapshot
|
|
|
|
// SessionData fields snapshotted under one RLock. The pointer to the
|
|
// SessionData is retained so handlers that genuinely need to mutate
|
|
// (Save, Clear, etc.) still have access.
|
|
session *SessionData
|
|
|
|
authenticated bool
|
|
accessToken string
|
|
idToken string
|
|
refreshToken string
|
|
userIdentifier string
|
|
createdAtUnixSec int64
|
|
|
|
// Output: scheme/host/redirect path determined at top of ServeHTTP.
|
|
scheme string
|
|
host string
|
|
redirectURL string
|
|
|
|
// Carry the next handler so forwardAuthorized doesn't need to close over t.
|
|
next http.Handler
|
|
}
|
|
|
|
// captureSession populates requestState's SessionData-derived fields under a
|
|
// single RLock of sd.sessionMutex. Returns the populated rs for chaining.
|
|
//
|
|
// Replaces a sequence of SessionData.GetX() calls each of which acquires
|
|
// sd.sessionMutex.RLock(). Under Yaegi each RLock costs ~1-5ms of
|
|
// interpreter dispatch; batching saves the rest.
|
|
func (rs *requestState) captureSession(sd *SessionData) *requestState {
|
|
if sd == nil {
|
|
return rs
|
|
}
|
|
rs.session = sd
|
|
sd.sessionMutex.RLock()
|
|
rs.authenticated = sd.getAuthenticatedUnsafe()
|
|
rs.accessToken = sd.getAccessTokenUnsafe()
|
|
rs.idToken = sd.getIDTokenUnsafe()
|
|
rs.refreshToken = sd.getRefreshTokenUnsafe()
|
|
rs.userIdentifier = sd.getUserIdentifierUnsafe()
|
|
rs.createdAtUnixSec = sd.getCreatedAtUnsafe()
|
|
sd.sessionMutex.RUnlock()
|
|
return rs
|
|
}
|