82ec287ba1
Sessions move from an in-memory map (absolute 12h TTL) to a `sessions` table (migration 0002), storing only the SHA-256 of the token. Expiry is now a sliding idle window (PANEL_SESSION_IDLE_DAYS, default 7, no absolute cap), extended at most once an hour and never by the monitoring screens' background polling (GET + HX-Request), so a forgotten open tab doesn't keep a session alive indefinitely. A login now survives a container restart or redeploy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
55 lines
1.7 KiB
Go
55 lines
1.7 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
)
|
|
|
|
type ctxKey int
|
|
|
|
const usernameKey ctxKey = 0
|
|
|
|
// requireAuth wraps a handler so only requests with a valid session cookie
|
|
// reach it; everyone else is redirected to the login page. The authenticated
|
|
// username is stashed in the request context for downstream handlers.
|
|
//
|
|
// It also extends the sliding session (plan B.1) on activity, defined as
|
|
// everything except a GET request carrying HX-Request: the four monitoring
|
|
// fragments (/status/fragment, /queue/body, /logtail/body, /sendlog/rows)
|
|
// poll every 5s regardless of whether anyone is looking at the tab, so
|
|
// counting those as activity would make "N days idle" mean "N days since a
|
|
// browser tab was last open" instead.
|
|
func (s *Server) requireAuth(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
token, ok := s.sessionToken(r)
|
|
if !ok {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
username, ok := s.sessions.Lookup(token)
|
|
if !ok {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
if isSessionActivity(r) && s.sessions.Touch(token) {
|
|
s.setSessionCookie(w, token)
|
|
}
|
|
ctx := context.WithValue(r.Context(), usernameKey, username)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
// isSessionActivity reports whether a request counts as administrator
|
|
// activity for the sliding session timeout, per requireAuth's doc comment.
|
|
func isSessionActivity(r *http.Request) bool {
|
|
return !(r.Method == http.MethodGet && r.Header.Get("HX-Request") != "")
|
|
}
|
|
|
|
// currentUser returns the authenticated username from the request context.
|
|
func currentUser(r *http.Request) string {
|
|
if v, ok := r.Context().Value(usernameKey).(string); ok {
|
|
return v
|
|
}
|
|
return ""
|
|
}
|