Pulse/internal/api/demo_middleware.go
2026-04-11 17:20:58 +01:00

85 lines
2.3 KiB
Go

package api
import (
"encoding/json"
"net/http"
"strings"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rs/zerolog/log"
)
// DemoModeMiddleware blocks all modification requests in demo mode
func DemoModeMiddleware(cfg *config.Config, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if cfg == nil || !cfg.DemoMode {
next.ServeHTTP(w, r)
return
}
// Add header so frontend knows we're in demo mode
w.Header().Set("X-Demo-Mode", "true")
if exposure, ok := publicDemoCommercialPolicyForRequest(r); ok {
if exposure == publicDemoCommercialExposureHidden {
http.NotFound(w, r)
return
}
}
if exposure, ok := publicDemoAdminOperationsPolicyForRequest(r); ok {
if exposure == publicDemoCommercialExposureHidden {
http.NotFound(w, r)
return
}
}
// Allow GET and HEAD requests (read-only)
if r.Method == http.MethodGet || r.Method == http.MethodHead || r.Method == http.MethodOptions {
next.ServeHTTP(w, r)
return
}
// Allow WebSocket upgrades
if strings.ToLower(r.Header.Get("Upgrade")) == "websocket" {
next.ServeHTTP(w, r)
return
}
// Allow authentication endpoints (login is read-only - verifies credentials)
authPaths := []string{
"/api/login",
"/api/logout",
// Allow AI chat interaction (mocked in backend if key missing)
"/api/ai/execute",
}
for _, path := range authPaths {
if r.URL.Path == path {
next.ServeHTTP(w, r)
return
}
}
// Allow per-provider OIDC auth flow endpoints:
// /api/oidc/{providerID}/login and /api/oidc/{providerID}/callback
if strings.HasPrefix(r.URL.Path, "/api/oidc/") {
parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/"), "/")
if len(parts) >= 4 && (parts[3] == "login" || parts[3] == "callback") {
next.ServeHTTP(w, r)
return
}
}
// Block all modification requests (POST, PUT, DELETE, PATCH)
log.Warn().
Str("method", r.Method).
Str("path", r.URL.Path).
Str("remote", r.RemoteAddr).
Msg("Demo mode: blocked modification request")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusForbidden)
json.NewEncoder(w).Encode(map[string]string{
"error": "Demo mode enabled",
"message": "This is a read-only demo instance. Modifications are disabled.",
})
})
}