From 21559bd6b7131458adf58616932638058a53a49e Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Sat, 29 Mar 2025 14:01:10 -0500 Subject: [PATCH] sessions 1 --- core/sessions/Session.go | 74 +++++++++++++++ core/sessions/SessionManager.go | 153 ++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 core/sessions/Session.go create mode 100644 core/sessions/SessionManager.go diff --git a/core/sessions/Session.go b/core/sessions/Session.go new file mode 100644 index 0000000..e7ea9a5 --- /dev/null +++ b/core/sessions/Session.go @@ -0,0 +1,74 @@ +package sessions + +import ( + "sync" + "time" + + "git.sharkk.net/Sky/Moonshark/core/logger" +) + +// Session stores data for a single user session +type Session struct { + ID string + Data map[string]any + CreatedAt time.Time + UpdatedAt time.Time + mu sync.RWMutex // Protect concurrent access to Data +} + +// NewSession creates a new session with the given ID +func NewSession(id string) *Session { + now := time.Now() + return &Session{ + ID: id, + Data: make(map[string]any), + CreatedAt: now, + UpdatedAt: now, + } +} + +// Get retrieves a value from the session +func (s *Session) Get(key string) any { + s.mu.RLock() + defer s.mu.RUnlock() + return s.Data[key] +} + +// Set stores a value in the session +func (s *Session) Set(key string, value any) { + s.mu.Lock() + defer s.mu.Unlock() + s.Data[key] = value + s.UpdatedAt = time.Now() +} + +// Delete removes a value from the session +func (s *Session) Delete(key string) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.Data, key) + s.UpdatedAt = time.Now() +} + +// Clear removes all data from the session +func (s *Session) Clear() { + s.mu.Lock() + defer s.mu.Unlock() + s.Data = make(map[string]any) + s.UpdatedAt = time.Now() +} + +// GetAll returns a copy of all session data +func (s *Session) GetAll() map[string]any { + s.mu.RLock() + defer s.mu.RUnlock() + + // Create a copy to avoid concurrent map access issues + copy := make(map[string]any, len(s.Data)) + for k, v := range s.Data { + logger.Debug("Session Key: %s, Has Data: %s", k, v) + copy[k] = v + } + + return copy +} diff --git a/core/sessions/SessionManager.go b/core/sessions/SessionManager.go new file mode 100644 index 0000000..d75453c --- /dev/null +++ b/core/sessions/SessionManager.go @@ -0,0 +1,153 @@ +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()