webui: re-read console/netbrowse shortcuts on full page load

These two shortcuts are toggleable in the configuration but were only
detected once at login and baked into the session, so disabling them
didn't drop the topbar icon / user-menu entry until re-login.

Refresh just those two from running-config on full page loads (htmx
swaps and pollers, which never re-draw them, are skipped), so toggling
takes effect on the next reload without logging out.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-06-17 11:11:29 +02:00
parent 322811d712
commit 93db076d72
4 changed files with 28 additions and 6 deletions
+1 -3
View File
@@ -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()
@@ -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 {
+18 -2
View File
@@ -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))
})
+1 -1
View File
@@ -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