diff --git a/src/webui/internal/auth/login.go b/src/webui/internal/auth/login.go index 75b61f67..d2928563 100644 --- a/src/webui/internal/auth/login.go +++ b/src/webui/internal/auth/login.go @@ -79,9 +79,7 @@ func (h *LoginHandler) DoLogin(w http.ResponseWriter, r *http.Request) { // The external web-app shortcuts (console/ttyd, netbrowse) are // config-gated; fold them into the same feature map so templates gate // on .Capabilities.Has "console" / "netbrowse". - console, netbrowse := handlers.DetectWebShortcuts(ctx, h.RC) - caps.Features()["console"] = console - caps.Features()["netbrowse"] = netbrowse + handlers.ApplyWebShortcuts(ctx, h.RC, caps.Features()) // The User's Guide is bundled at build time (a filesystem check, not // config); gate the Help entry on its presence. caps.Features()["docs"] = handlers.DetectDocs() diff --git a/src/webui/internal/handlers/capabilities.go b/src/webui/internal/handlers/capabilities.go index 403df1cc..655fb04b 100644 --- a/src/webui/internal/handlers/capabilities.go +++ b/src/webui/internal/handlers/capabilities.go @@ -109,6 +109,14 @@ func DetectWebShortcuts(ctx context.Context, rc restconf.Fetcher) (console, netb return cfg.Web.Console.Enabled, cfg.Web.Netbrowse.Enabled } +// ApplyWebShortcuts writes the console/netbrowse capability flags into features +// from running-config. These two are the only config-toggleable shortcuts, so +// keeping the keys in one place lets both login (baking into the session) and +// the per-page-load refresh share them. +func ApplyWebShortcuts(ctx context.Context, rc restconf.Fetcher, features map[string]bool) { + features["console"], features["netbrowse"] = DetectWebShortcuts(ctx, rc) +} + func DetectCapabilities(ctx context.Context, rc restconf.Fetcher) *Capabilities { var lib yangLibrary if err := rc.Get(ctx, "/data/ietf-yang-library:yang-library", &lib); err != nil { diff --git a/src/webui/internal/server/middleware.go b/src/webui/internal/server/middleware.go index 1db03dee..2d5175f0 100644 --- a/src/webui/internal/server/middleware.go +++ b/src/webui/internal/server/middleware.go @@ -3,6 +3,7 @@ package server import ( + "maps" "net/http" "net/url" "strings" @@ -19,7 +20,7 @@ const cookieName = "session" // the token up in the in-memory store, and attaches the session's // credentials to the context. Unauthenticated requests are // redirected to /login (or get a 401 if the request comes from HTMX). -func authMiddleware(store *auth.SessionStore, next http.Handler) http.Handler { +func authMiddleware(store *auth.SessionStore, rc restconf.Fetcher, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if isPublicPath(r.URL.Path) { next.ServeHTTP(w, r) @@ -38,11 +39,13 @@ func authMiddleware(store *auth.SessionStore, next http.Handler) http.Handler { return } + polling := isPollingPath(r.URL.Path) + // Sliding window: extend the session on user-driven requests. // Background polling endpoints are excluded so an idle user // (just leaving the dashboard open in a tab) still hits the // timeout. - if !isPollingPath(r.URL.Path) { + if !polling { store.Refresh(cookie.Value) } @@ -51,6 +54,19 @@ func authMiddleware(store *auth.SessionStore, next http.Handler) http.Handler { Password: password, }) ctx = security.WithToken(ctx, csrf) + + // The console / netbrowse shortcuts are toggleable in running-config at + // runtime, unlike the other (per-boot) capabilities baked into the + // session at login. Re-read them on full page loads — the only time + // the topbar/user-menu that host them re-render — so toggling takes + // effect on reload without re-login. htmx fragment swaps and pollers + // never re-draw them, so they're skipped (no read per request). + fullPageLoad := !polling && r.Header.Get("HX-Request") != "true" + if rc != nil && fullPageLoad { + features = maps.Clone(features) // don't mutate the shared session map + handlers.ApplyWebShortcuts(ctx, rc, features) + } + ctx = handlers.ContextWithCapabilities(ctx, handlers.NewCapabilities(features)) next.ServeHTTP(w, r.WithContext(ctx)) }) diff --git a/src/webui/internal/server/server.go b/src/webui/internal/server/server.go index 46ad20ee..c8f20ef3 100644 --- a/src/webui/internal/server/server.go +++ b/src/webui/internal/server/server.go @@ -480,7 +480,7 @@ func New( mux.HandleFunc("GET /status/tree/children", statusTreeH.TreeChildren) mux.HandleFunc("GET /status/tree/node", statusTreeH.TreeNode) - handler := authMiddleware(store, mux) + handler := authMiddleware(store, rc, mux) handler = csrfMiddleware(handler) handler = securityHeadersMiddleware(handler) return handler, nil