Major changes: logic add to panel, local variables, panel histor, users management...
This commit is contained in:
@@ -5,12 +5,15 @@ import (
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/uopi/uopi/internal/access"
|
||||
"github.com/uopi/uopi/internal/api"
|
||||
"github.com/uopi/uopi/internal/broker"
|
||||
"github.com/uopi/uopi/internal/datasource/synthetic"
|
||||
"github.com/uopi/uopi/internal/metrics"
|
||||
"github.com/uopi/uopi/internal/panelacl"
|
||||
"github.com/uopi/uopi/internal/storage"
|
||||
)
|
||||
|
||||
@@ -23,7 +26,7 @@ type Server struct {
|
||||
|
||||
// New creates the HTTP server, registers all routes, and returns a ready-to-start Server.
|
||||
// synth may be nil if the synthetic data source is not enabled.
|
||||
func New(addr string, webFS fs.FS, brk *broker.Broker, synth *synthetic.Synthetic, store *storage.Store, channelFinderURL, archiverURL string, log *slog.Logger) *Server {
|
||||
func New(addr string, webFS fs.FS, brk *broker.Broker, synth *synthetic.Synthetic, store *storage.Store, policy *access.Policy, acl *panelacl.Store, channelFinderURL, archiverURL, trustedUserHeader string, log *slog.Logger) *Server {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
// Health check
|
||||
@@ -33,13 +36,16 @@ func New(addr string, webFS fs.FS, brk *broker.Broker, synth *synthetic.Syntheti
|
||||
})
|
||||
|
||||
// WebSocket endpoint
|
||||
mux.Handle("/ws", &wsHandler{broker: brk, log: log})
|
||||
mux.Handle("/ws", &wsHandler{broker: brk, log: log, userHeader: trustedUserHeader, policy: policy})
|
||||
|
||||
// Prometheus-format metrics
|
||||
mux.HandleFunc("/metrics", metrics.Handler(brk.ActiveSubscriptions))
|
||||
|
||||
// REST API
|
||||
api.New(brk, synth, store, channelFinderURL, archiverURL, log).Register(mux, apiPrefix)
|
||||
// REST API — registered on a dedicated mux so it can be wrapped with the
|
||||
// access-control middleware (identity resolution + global level enforcement).
|
||||
apiMux := http.NewServeMux()
|
||||
api.New(brk, synth, store, policy, acl, channelFinderURL, archiverURL, log).Register(apiMux, apiPrefix)
|
||||
mux.Handle(apiPrefix+"/", accessMiddleware(policy, trustedUserHeader, apiMux))
|
||||
|
||||
// Embedded frontend — must be last (catch-all)
|
||||
mux.Handle("/", http.FileServerFS(webFS))
|
||||
@@ -56,6 +62,44 @@ func New(addr string, webFS fs.FS, brk *broker.Broker, synth *synthetic.Syntheti
|
||||
}
|
||||
}
|
||||
|
||||
// accessMiddleware resolves the end-user identity from the trusted proxy header
|
||||
// (falling back to the configured default_user), stores it on the request
|
||||
// context, and enforces the user's global access level on every API request:
|
||||
// - LevelNone → 403 for all requests.
|
||||
// - LevelRead → 403 for any mutating request (non GET/HEAD).
|
||||
// - LevelWrite → no restriction.
|
||||
//
|
||||
// The /me endpoint is always reachable so the frontend can discover the
|
||||
// caller's identity and level even when otherwise restricted.
|
||||
func accessMiddleware(policy *access.Policy, userHeader string, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var raw string
|
||||
if userHeader != "" {
|
||||
raw = r.Header.Get(userHeader)
|
||||
}
|
||||
user := policy.ResolveUser(raw)
|
||||
r = r.WithContext(access.WithUser(r.Context(), user))
|
||||
|
||||
// Always allow identity discovery.
|
||||
if strings.HasSuffix(r.URL.Path, apiPrefix+"/me") {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
switch policy.Level(user) {
|
||||
case access.LevelNone:
|
||||
http.Error(w, "access denied", http.StatusForbidden)
|
||||
return
|
||||
case access.LevelRead:
|
||||
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||
http.Error(w, "read-only access", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// Start listens and serves until ctx is cancelled.
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
s.log.Info("listening", "addr", s.httpServer.Addr)
|
||||
|
||||
Reference in New Issue
Block a user