package sessions import ( "crypto/rand" "encoding/base64" "sync" "time" ) // SessionManager handles multiple sessions type SessionManager struct { sessions map[string]*Session mu sync.RWMutex cookieName string cookiePath string cookieDomain string cookieSecure bool cookieHTTPOnly bool cookieMaxAge int gcInterval time.Duration } // NewSessionManager creates a new session manager func NewSessionManager() *SessionManager { sm := &SessionManager{ sessions: make(map[string]*Session), cookieName: "MSESSID", cookiePath: "/", cookieHTTPOnly: true, cookieMaxAge: 86400, // 1 day gcInterval: time.Hour, } // Start the garbage collector go sm.startGC() return sm } // generateSessionID creates a cryptographically secure random session ID func (sm *SessionManager) generateSessionID() string { b := make([]byte, 32) if _, err := rand.Read(b); err != nil { return time.Now().String() // Fallback } return base64.URLEncoding.EncodeToString(b) } // GetSession retrieves a session by ID, or creates a new one if it doesn't exist func (sm *SessionManager) GetSession(id string) *Session { sm.mu.RLock() session, exists := sm.sessions[id] sm.mu.RUnlock() if exists { return session } // Create new session if it doesn't exist sm.mu.Lock() defer sm.mu.Unlock() // Double check to avoid race conditions if session, exists = sm.sessions[id]; exists { return session } session = NewSession(id) sm.sessions[id] = session return session } // CreateSession generates a new session with a unique ID func (sm *SessionManager) CreateSession() *Session { id := sm.generateSessionID() sm.mu.Lock() defer sm.mu.Unlock() session := NewSession(id) sm.sessions[id] = session return session } // DestroySession removes a session func (sm *SessionManager) DestroySession(id string) { sm.mu.Lock() defer sm.mu.Unlock() delete(sm.sessions, id) } // startGC starts the garbage collector to clean up expired sessions func (sm *SessionManager) startGC() { ticker := time.NewTicker(sm.gcInterval) defer ticker.Stop() for range ticker.C { sm.gc() } } // gc removes expired sessions (inactive for 24 hours) func (sm *SessionManager) gc() { expiry := time.Now().Add(-24 * time.Hour) sm.mu.Lock() defer sm.mu.Unlock() for id, session := range sm.sessions { session.mu.RLock() lastUpdated := session.UpdatedAt session.mu.RUnlock() if lastUpdated.Before(expiry) { delete(sm.sessions, id) } } } // GetSessionCount returns the number of active sessions func (sm *SessionManager) GetSessionCount() int { sm.mu.RLock() defer sm.mu.RUnlock() return len(sm.sessions) } // CookieOptions returns the cookie options for this session manager func (sm *SessionManager) CookieOptions() map[string]any { return map[string]any{ "name": sm.cookieName, "path": sm.cookiePath, "domain": sm.cookieDomain, "secure": sm.cookieSecure, "http_only": sm.cookieHTTPOnly, "max_age": sm.cookieMaxAge, } } // SetCookieOptions configures cookie parameters func (sm *SessionManager) SetCookieOptions(name, path, domain string, secure, httpOnly bool, maxAge int) { sm.mu.Lock() defer sm.mu.Unlock() sm.cookieName = name sm.cookiePath = path sm.cookieDomain = domain sm.cookieSecure = secure sm.cookieHTTPOnly = httpOnly sm.cookieMaxAge = maxAge } // GlobalSessionManager is the default session manager instance var GlobalSessionManager = NewSessionManager()