package helps import ( "crypto/sha256" "encoding/hex" "sync" "time" "github.com/google/uuid" ) type sessionIDCacheEntry struct { value string expire time.Time } var ( sessionIDCache = make(map[string]sessionIDCacheEntry) sessionIDCacheMu sync.RWMutex sessionIDCacheCleanupOnce sync.Once ) const ( sessionIDTTL = time.Hour sessionIDCacheCleanupPeriod = 15 * time.Minute ) func startSessionIDCacheCleanup() { go func() { ticker := time.NewTicker(sessionIDCacheCleanupPeriod) defer ticker.Stop() for range ticker.C { purgeExpiredSessionIDs() } }() } func purgeExpiredSessionIDs() { now := time.Now() sessionIDCacheMu.Lock() for key, entry := range sessionIDCache { if !entry.expire.After(now) { delete(sessionIDCache, key) } } sessionIDCacheMu.Unlock() } func sessionIDCacheKey(apiKey string) string { sum := sha256.Sum256([]byte(apiKey)) return hex.EncodeToString(sum[:]) } // CachedSessionID returns a stable session UUID per apiKey, refreshing the TTL on each access. func CachedSessionID(apiKey string) string { if apiKey == "" { return uuid.New().String() } sessionIDCacheCleanupOnce.Do(startSessionIDCacheCleanup) key := sessionIDCacheKey(apiKey) now := time.Now() sessionIDCacheMu.RLock() entry, ok := sessionIDCache[key] valid := ok && entry.value != "" && entry.expire.After(now) sessionIDCacheMu.RUnlock() if valid { sessionIDCacheMu.Lock() entry = sessionIDCache[key] if entry.value != "" && entry.expire.After(now) { entry.expire = now.Add(sessionIDTTL) sessionIDCache[key] = entry sessionIDCacheMu.Unlock() return entry.value } sessionIDCacheMu.Unlock() } newID := uuid.New().String() sessionIDCacheMu.Lock() entry, ok = sessionIDCache[key] if !ok || entry.value == "" || !entry.expire.After(now) { entry.value = newID } entry.expire = now.Add(sessionIDTTL) sessionIDCache[key] = entry sessionIDCacheMu.Unlock() return entry.value }