package sessions import ( "crypto/rand" "encoding/base64" "sync" "time" "github.com/VictoriaMetrics/fastcache" "github.com/goccy/go-json" ) const ( // Default settings DefaultMaxSize = 100 * 1024 * 1024 // 100MB default cache size DefaultCookieName = "MSESSID" DefaultCookiePath = "/" DefaultMaxAge = 86400 // 1 day in seconds ) // SessionManager handles multiple sessions using fastcache for storage type SessionManager struct { cache *fastcache.Cache cookieName string cookiePath string cookieDomain string cookieSecure bool cookieHTTPOnly bool cookieMaxAge int mu sync.RWMutex // Only for cookie settings } // NewSessionManager creates a new session manager with optional cache size func NewSessionManager(maxSize ...int) *SessionManager { size := DefaultMaxSize if len(maxSize) > 0 && maxSize[0] > 0 { size = maxSize[0] } return &SessionManager{ cache: fastcache.New(size), cookieName: DefaultCookieName, cookiePath: DefaultCookiePath, cookieHTTPOnly: true, cookieMaxAge: DefaultMaxAge, } } // generateSessionID creates a cryptographically secure random session ID func 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 { // Check if session exists data := sm.cache.Get(nil, []byte(id)) if len(data) > 0 { // Session exists, unmarshal it session := &Session{} if err := json.Unmarshal(data, session); err == nil { // Update last accessed time session.UpdatedAt = time.Now() // Store back with updated timestamp updatedData, _ := json.Marshal(session) sm.cache.Set([]byte(id), updatedData) return session } } // Create new session session := NewSession(id) data, _ = json.Marshal(session) sm.cache.Set([]byte(id), data) return session } // CreateSession generates a new session with a unique ID func (sm *SessionManager) CreateSession() *Session { id := generateSessionID() session := NewSession(id) data, _ := json.Marshal(session) sm.cache.Set([]byte(id), data) return session } // SaveSession persists a session back to the cache func (sm *SessionManager) SaveSession(session *Session) { data, _ := json.Marshal(session) sm.cache.Set([]byte(session.ID), data) } // DestroySession removes a session func (sm *SessionManager) DestroySession(id string) { sm.cache.Del([]byte(id)) } // CookieOptions returns the cookie options for this session manager func (sm *SessionManager) CookieOptions() map[string]any { sm.mu.RLock() defer sm.mu.RUnlock() 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()