package main import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/hex" "net/http" "sync" "time" ) const ( sessionCookieName = "cwc_session" sessionMaxAge = 24 * time.Hour ) type Session struct { Token string Username string CreatedAt time.Time } type SessionManager struct { sessions map[string]*Session secret string mu sync.RWMutex } func NewSessionManager(secret string) *SessionManager { return &SessionManager{ sessions: make(map[string]*Session), secret: secret, } } func (sm *SessionManager) Create(username string) *Session { token := generateToken() sig := sm.sign(token) signedToken := token + "." + sig sess := &Session{ Token: signedToken, Username: username, CreatedAt: time.Now(), } sm.mu.Lock() sm.sessions[signedToken] = sess sm.mu.Unlock() return sess } func (sm *SessionManager) Get(token string) *Session { sm.mu.RLock() sess, ok := sm.sessions[token] sm.mu.RUnlock() if !ok { return nil } if time.Since(sess.CreatedAt) > sessionMaxAge { sm.Delete(token) return nil } return sess } func (sm *SessionManager) Delete(token string) { sm.mu.Lock() delete(sm.sessions, token) sm.mu.Unlock() } func (sm *SessionManager) sign(token string) string { h := hmac.New(sha256.New, []byte(sm.secret)) h.Write([]byte(token)) return hex.EncodeToString(h.Sum(nil)) } func generateToken() string { b := make([]byte, 32) rand.Read(b) return hex.EncodeToString(b) } // SetSessionCookie sets the session cookie on the response. func SetSessionCookie(w http.ResponseWriter, sess *Session) { http.SetCookie(w, &http.Cookie{ Name: sessionCookieName, Value: sess.Token, Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: int(sessionMaxAge.Seconds()), }) } // ClearSessionCookie removes the session cookie. func ClearSessionCookie(w http.ResponseWriter) { http.SetCookie(w, &http.Cookie{ Name: sessionCookieName, Value: "", Path: "/", HttpOnly: true, MaxAge: -1, }) } // AuthMiddleware protects routes that require authentication. func AuthMiddleware(sm *SessionManager, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(sessionCookieName) if err != nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } sess := sm.Get(cookie.Value) if sess == nil { ClearSessionCookie(w) http.Redirect(w, r, "/login", http.StatusSeeOther) return } next.ServeHTTP(w, r) }) }