208 lines
4.6 KiB
Go
208 lines
4.6 KiB
Go
package sessions
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
const (
|
|
DefaultMaxSessions = 10000
|
|
DefaultCookieName = "MoonsharkSID"
|
|
DefaultCookiePath = "/"
|
|
DefaultMaxAge = 86400 // 1 day in seconds
|
|
)
|
|
|
|
// SessionManager handles multiple sessions
|
|
type SessionManager struct {
|
|
sessions map[string]*Session
|
|
maxSessions int
|
|
cookieName string
|
|
cookiePath string
|
|
cookieDomain string
|
|
cookieSecure bool
|
|
cookieHTTPOnly bool
|
|
cookieMaxAge int
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewSessionManager creates a new session manager
|
|
func NewSessionManager(maxSessions int) *SessionManager {
|
|
if maxSessions <= 0 {
|
|
maxSessions = DefaultMaxSessions
|
|
}
|
|
|
|
return &SessionManager{
|
|
sessions: make(map[string]*Session, maxSessions),
|
|
maxSessions: maxSessions,
|
|
cookieName: DefaultCookieName,
|
|
cookiePath: DefaultCookiePath,
|
|
cookieHTTPOnly: true,
|
|
cookieMaxAge: DefaultMaxAge,
|
|
}
|
|
}
|
|
|
|
// generateSessionID creates a 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 {
|
|
// Try to get an existing session
|
|
if id != "" {
|
|
sm.mu.RLock()
|
|
session, exists := sm.sessions[id]
|
|
sm.mu.RUnlock()
|
|
|
|
if exists {
|
|
// Check if session is expired
|
|
if session.IsExpired() {
|
|
sm.mu.Lock()
|
|
delete(sm.sessions, id)
|
|
sm.mu.Unlock()
|
|
} else {
|
|
// Update last used time
|
|
session.UpdateLastUsed()
|
|
return session
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create a new session
|
|
return sm.CreateSession()
|
|
}
|
|
|
|
// CreateSession generates a new session with a unique ID
|
|
func (sm *SessionManager) CreateSession() *Session {
|
|
id := generateSessionID()
|
|
session := NewSession(id, sm.cookieMaxAge)
|
|
|
|
sm.mu.Lock()
|
|
// Enforce session limit - evict LRU if needed
|
|
if len(sm.sessions) >= sm.maxSessions {
|
|
sm.evictLRU()
|
|
}
|
|
|
|
sm.sessions[id] = session
|
|
sm.mu.Unlock()
|
|
|
|
return session
|
|
}
|
|
|
|
// evictLRU removes the least recently used session
|
|
func (sm *SessionManager) evictLRU() {
|
|
// Called with mutex already held
|
|
if len(sm.sessions) == 0 {
|
|
return
|
|
}
|
|
|
|
var oldestID string
|
|
var oldestTime time.Time
|
|
|
|
// Find oldest session
|
|
for id, session := range sm.sessions {
|
|
if oldestID == "" || session.LastUsed.Before(oldestTime) {
|
|
oldestID = id
|
|
oldestTime = session.LastUsed
|
|
}
|
|
}
|
|
|
|
if oldestID != "" {
|
|
delete(sm.sessions, oldestID)
|
|
}
|
|
}
|
|
|
|
// DestroySession removes a session
|
|
func (sm *SessionManager) DestroySession(id string) {
|
|
sm.mu.Lock()
|
|
defer sm.mu.Unlock()
|
|
delete(sm.sessions, id)
|
|
}
|
|
|
|
// CleanupExpired removes all expired sessions
|
|
func (sm *SessionManager) CleanupExpired() int {
|
|
removed := 0
|
|
now := time.Now()
|
|
|
|
sm.mu.Lock()
|
|
defer sm.mu.Unlock()
|
|
|
|
for id, session := range sm.sessions {
|
|
if now.After(session.Expiry) {
|
|
delete(sm.sessions, id)
|
|
removed++
|
|
}
|
|
}
|
|
|
|
return removed
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// GetSessionFromRequest extracts the session from a request
|
|
func (sm *SessionManager) GetSessionFromRequest(ctx *fasthttp.RequestCtx) *Session {
|
|
cookie := ctx.Request.Header.Cookie(sm.cookieName)
|
|
if len(cookie) == 0 {
|
|
return sm.CreateSession()
|
|
}
|
|
|
|
return sm.GetSession(string(cookie))
|
|
}
|
|
|
|
// ApplySessionCookie adds the session cookie to the response
|
|
func (sm *SessionManager) ApplySessionCookie(ctx *fasthttp.RequestCtx, session *Session) {
|
|
cookie := fasthttp.AcquireCookie()
|
|
defer fasthttp.ReleaseCookie(cookie)
|
|
|
|
cookie.SetKey(sm.cookieName)
|
|
cookie.SetValue(session.ID)
|
|
cookie.SetPath(sm.cookiePath)
|
|
cookie.SetHTTPOnly(sm.cookieHTTPOnly)
|
|
cookie.SetMaxAge(sm.cookieMaxAge)
|
|
|
|
if sm.cookieDomain != "" {
|
|
cookie.SetDomain(sm.cookieDomain)
|
|
}
|
|
|
|
cookie.SetSecure(sm.cookieSecure)
|
|
|
|
ctx.Response.Header.SetCookie(cookie)
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
|
|
// GlobalSessionManager is the default session manager instance
|
|
var GlobalSessionManager = NewSessionManager(DefaultMaxSessions)
|