fix(security): remove lock contention, cache rules, stop leaked goroutines

Load column/row security without holding locks across provider calls,
honour pOverwrite with a 30s TTL and pruning, cap tokens per
Authorization header, detach session activity updates with a timeout,
stop OAuth2 cleanup goroutines via Close, move nil-map checks inside
locks, and drop O(n^2) string building. Update audit status.
This commit is contained in:
Hein
2026-09-30 13:31:03 +02:00
parent 97fe88b3a6
commit 164ba2b240
6 changed files with 315 additions and 53 deletions
+32 -1
View File
@@ -12,6 +12,8 @@ import (
"sync"
"time"
"github.com/bitechdev/ResolveSpec/pkg/logger"
"golang.org/x/oauth2"
)
@@ -39,6 +41,8 @@ type OAuth2Provider struct {
providerName string
states map[string]time.Time // state -> expiry time
statesMutex sync.RWMutex
stopCh chan struct{} // closed to stop cleanupStates
stopOnce sync.Once
}
// WithOAuth2 configures OAuth2 support for the DatabaseAuthenticator
@@ -68,6 +72,7 @@ func (a *DatabaseAuthenticator) WithOAuth2(cfg OAuth2Config) *DatabaseAuthentica
userInfoParser: cfg.UserInfoParser,
providerName: cfg.ProviderName,
states: make(map[string]time.Time),
stopCh: make(chan struct{}),
}
// Initialize providers map if needed
@@ -77,6 +82,9 @@ func (a *DatabaseAuthenticator) WithOAuth2(cfg OAuth2Config) *DatabaseAuthentica
}
// Register provider
if old := a.oauth2Providers[cfg.ProviderName]; old != nil {
old.stop() // replaced provider: stop its cleanup goroutine
}
a.oauth2Providers[cfg.ProviderName] = provider
a.oauth2ProvidersMutex.Unlock()
@@ -335,10 +343,16 @@ func (p *OAuth2Provider) validateState(state string) bool {
// cleanupStates removes expired states periodically
func (p *OAuth2Provider) cleanupStates() {
defer logger.CatchPanic("OAuth2Provider.cleanupStates")()
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for range ticker.C {
for {
select {
case <-p.stopCh:
return
case <-ticker.C:
}
p.statesMutex.Lock()
now := time.Now()
for state, expiry := range p.states {
@@ -350,6 +364,23 @@ func (p *OAuth2Provider) cleanupStates() {
}
}
// stop terminates the cleanup goroutine; safe to call more than once.
func (p *OAuth2Provider) stop() {
p.stopOnce.Do(func() { close(p.stopCh) })
}
// Close stops the background OAuth2 state cleanup goroutines and waits for
// in-flight session activity updates. It is safe to call more than once.
func (a *DatabaseAuthenticator) Close() error {
a.oauth2ProvidersMutex.RLock()
for _, p := range a.oauth2Providers {
p.stop()
}
a.oauth2ProvidersMutex.RUnlock()
a.activityWG.Wait()
return nil
}
// defaultOAuth2UserInfoParser parses standard OAuth2 user info claims
func defaultOAuth2UserInfoParser(userInfo map[string]any) (*UserContext, error) {
ctx := &UserContext{