Release OIDC discovery waiters when discovery panics
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

This commit is contained in:
2026-10-14 22:36:00 +11:00
parent 98ad145b6f
commit 4d9f2bb693
2 changed files with 52 additions and 13 deletions
+27 -13
View File
@@ -21,6 +21,7 @@ type BearerOIDC struct {
audiences []string
timeout time.Duration
backoff time.Duration
discover func(context.Context, string) (*oidc.Provider, error)
mu sync.Mutex
verifier *oidc.IDTokenVerifier
@@ -35,7 +36,7 @@ func NewBearerOIDC(issuer string, audiences []string) *BearerOIDC {
if issuer == "" {
return nil
}
return &BearerOIDC{issuer: issuer, audiences: audiences, timeout: 10 * time.Second, backoff: 15 * time.Second}
return &BearerOIDC{issuer: issuer, audiences: audiences, timeout: 10 * time.Second, backoff: 15 * time.Second, discover: oidc.NewProvider}
}
// getVerifier runs at most one discovery at a time without holding the lock
@@ -69,22 +70,35 @@ func (b *BearerOIDC) getVerifier(ctx context.Context) (*oidc.IDTokenVerifier, er
b.inflight = done
b.mu.Unlock()
// The provider keeps this context and client for JWKS refreshes, so the
// deadline lives on the client rather than the context.
client := &http.Client{Timeout: b.timeout}
p, err := oidc.NewProvider(oidc.ClientContext(context.Background(), client), b.issuer)
b.mu.Lock()
defer b.mu.Unlock()
b.inflight = nil
defer close(done)
v, err := b.discoverOnce(done)
if err != nil {
b.failedAt = time.Now()
slog.Error("oidc discovery failed", "issuer", b.issuer, "error", err)
return nil, err
}
b.verifier = p.Verifier(&oidc.Config{SkipClientIDCheck: true})
return b.verifier, nil
return v, nil
}
// discoverOnce releases inflight and waiters on every exit, a panic included,
// recording failedAt unless discovery succeeded.
func (b *BearerOIDC) discoverOnce(done chan struct{}) (v *oidc.IDTokenVerifier, err error) {
defer func() {
b.mu.Lock()
b.inflight = nil
if v == nil {
b.failedAt = time.Now()
}
b.verifier = v
b.mu.Unlock()
close(done)
}()
// The provider keeps this context and client for JWKS refreshes, so the
// deadline lives on the client rather than the context.
client := &http.Client{Timeout: b.timeout}
p, err := b.discover(oidc.ClientContext(context.Background(), client), b.issuer)
if err != nil {
return nil, err
}
return p.Verifier(&oidc.Config{SkipClientIDCheck: true}), nil
}
// Require gates every request through next on a valid token. A nil receiver