package middleware import ( "context" "crypto/rand" "encoding/hex" "net/http" "sync" "time" ) type contextKey string const sessionKey contextKey = "userID" // SessionStore is a simple in-memory session store. // Swap for Redis in production. type SessionStore struct { mu sync.RWMutex sessions map[string]sessionEntry } type sessionEntry struct { userID string expiresAt time.Time } func NewSessionStore() *SessionStore { s := &SessionStore{sessions: make(map[string]sessionEntry)} go s.cleanup() return s } func (s *SessionStore) Create(userID string) string { b := make([]byte, 32) rand.Read(b) token := hex.EncodeToString(b) s.mu.Lock() s.sessions[token] = sessionEntry{userID: userID, expiresAt: time.Now().Add(24 * time.Hour)} s.mu.Unlock() return token } func (s *SessionStore) Get(token string) (string, bool) { s.mu.RLock() entry, ok := s.sessions[token] s.mu.RUnlock() if !ok || time.Now().After(entry.expiresAt) { return "", false } return entry.userID, true } func (s *SessionStore) Delete(token string) { s.mu.Lock() delete(s.sessions, token) s.mu.Unlock() } func (s *SessionStore) cleanup() { for range time.Tick(time.Hour) { s.mu.Lock() for k, v := range s.sessions { if time.Now().After(v.expiresAt) { delete(s.sessions, k) } } s.mu.Unlock() } } // RequireAuth middleware redirects unauthenticated requests to /login. func (s *SessionStore) RequireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("session") if err != nil { http.Redirect(w, r, "/login", http.StatusSeeOther) return } userID, ok := s.Get(cookie.Value) if !ok { http.Redirect(w, r, "/login", http.StatusSeeOther) return } ctx := context.WithValue(r.Context(), sessionKey, userID) next.ServeHTTP(w, r.WithContext(ctx)) }) } // SetSession writes the session cookie. func SetSession(w http.ResponseWriter, token string) { http.SetCookie(w, &http.Cookie{ Name: "session", Value: token, Path: "/", HttpOnly: true, SameSite: http.SameSiteLaxMode, MaxAge: 86400, }) } // ClearSession removes the session cookie. func ClearSession(w http.ResponseWriter) { http.SetCookie(w, &http.Cookie{ Name: "session", Value: "", Path: "/", MaxAge: -1, }) }