package auth import ( "sync" "time" ) // cacheEntry holds a cached Claims value and its expiration time. type cacheEntry struct { claims *Claims expiresAt time.Time } // validationCache provides a concurrency-safe, TTL-based cache for token // validation results. Tokens are keyed by their SHA-256 hex digest. type validationCache struct { mu sync.RWMutex entries map[string]cacheEntry ttl time.Duration now func() time.Time // injectable clock for testing } // newCache creates a validationCache with the given TTL. func newCache(ttl time.Duration) *validationCache { return &validationCache{ entries: make(map[string]cacheEntry), ttl: ttl, now: time.Now, } } // get returns cached claims for the given token hash, or false if the // entry is missing or expired. Expired entries are lazily evicted. func (c *validationCache) get(tokenHash string) (*Claims, bool) { c.mu.RLock() entry, ok := c.entries[tokenHash] c.mu.RUnlock() if !ok { return nil, false } if c.now().After(entry.expiresAt) { // Lazy evict the expired entry. c.mu.Lock() // Re-check under write lock in case another goroutine already evicted. if e, exists := c.entries[tokenHash]; exists && c.now().After(e.expiresAt) { delete(c.entries, tokenHash) } c.mu.Unlock() return nil, false } return entry.claims, true } // put stores claims in the cache with an expiration of now + TTL. func (c *validationCache) put(tokenHash string, claims *Claims) { c.mu.Lock() c.entries[tokenHash] = cacheEntry{ claims: claims, expiresAt: c.now().Add(c.ttl), } c.mu.Unlock() }