package admin import ( "context" "net/http" ) type ctxKey string const ctxKeySession ctxKey = "admin_session" // SessionMiddleware loads and validates the session cookie for protected // routes. Requests without a valid session are redirected to the login page // (GET) or rejected with 401 (other methods). On success the session is stored // in the request context and its idle TTL is slid forward. type SessionMiddleware struct { sessions *SessionStore next http.Handler } // NewSessionMiddleware wraps next so it only runs with a valid session. func NewSessionMiddleware(sessions *SessionStore, next http.Handler) *SessionMiddleware { return &SessionMiddleware{sessions: sessions, next: next} } func (m *SessionMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { c, err := r.Cookie(SessionCookieName) if err != nil { m.deny(w, r) return } sess, serr := m.sessions.Get(r.Context(), c.Value) if serr != nil { http.Error(w, "internal error", http.StatusInternalServerError) return } if sess == nil { m.deny(w, r) return } ctx := context.WithValue(r.Context(), ctxKeySession, sess) m.next.ServeHTTP(w, r.WithContext(ctx)) } func (m *SessionMiddleware) deny(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { http.Redirect(w, r, "/login", http.StatusFound) return } http.Error(w, "unauthorized", http.StatusUnauthorized) } // SessionFromContext returns the authenticated session, or nil. func SessionFromContext(ctx context.Context) *Session { s, _ := ctx.Value(ctxKeySession).(*Session) return s }