From 0b1d329f7c7e46f8f1de8ce9ac3d458290d4fba7 Mon Sep 17 00:00:00 2001 From: ponzischeme89 Date: Fri, 28 Aug 2026 23:36:11 +1200 Subject: [PATCH] Admin console: show the signed-in operator's name, not "Administrator" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user switcher fell back to "Administrator" whenever /admin/api/status returned no currentUser. Fresh sign-ins already embed the verified Emby account name in the session cookie, but a session minted before that field existed (or one that has not been re-issued since) is valid yet anonymous, so the fallback showed for ever. handleAdminConsole now treats a valid-but-anonymous admin session as needing a fresh sign-in for the SPA shell only — a one-time prompt that fills the name in, since session renewal preserves whatever the cookie already held. Asset requests are unaffected, so nothing breaks mid-session. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011wS9Qz3Fkxeu9KPt26hhhx --- server/internal/api/admin_access_test.go | 31 ++++++++++++++++++++++++ server/internal/api/admin_spa.go | 8 ++++++ server/internal/api/installer_auth.go | 11 +++++++++ 3 files changed, 50 insertions(+) diff --git a/server/internal/api/admin_access_test.go b/server/internal/api/admin_access_test.go index 5a39a48..0a1b0ac 100644 --- a/server/internal/api/admin_access_test.go +++ b/server/internal/api/admin_access_test.go @@ -180,6 +180,37 @@ func TestAdminSignInAsksEmbyWhenTheAuthResponseCarriesNoPolicy(t *testing.T) { } } +// The user switcher names whoever is signed in, so an admin session must carry the +// verified Emby account name. A fresh sign-in does; a session predating the identity in +// the cookie is still valid but anonymous, and the console handler prompts those to sign +// in again rather than falling back to "Administrator" for ever. +func TestAdminSessionCarriesTheVerifiedName(t *testing.T) { + s := embyAccessLevel(t, `{"IsAdministrator":true}`, "") + + cookie := sessionCookie(signIn(s, "/admin/")) + if cookie == nil { + t.Fatal("admin sign-in issued no session") + } + named := httptest.NewRequest(http.MethodGet, "/admin/", nil) + named.AddCookie(cookie) + if !s.adminSessionNamed(named) { + t.Fatal("a fresh admin session is missing the verified name") + } + + anon, err := s.newBrowserSessionFor(adminSessionPurpose, adminSessionTTL, "") + if err != nil { + t.Fatalf("anonymous session: %v", err) + } + req := httptest.NewRequest(http.MethodGet, "/admin/", nil) + req.AddCookie(&http.Cookie{Name: installerCookieName, Value: anon}) + if !s.validAdminSession(req) { + t.Fatal("an anonymous admin session should still be valid") + } + if s.adminSessionNamed(req) { + t.Fatal("an anonymous admin session should not report a name") + } +} + // And an Emby that will not answer either way must not be guessed at in the permissive // direction: no answer means no session. func TestAdminSignInIsRefusedWhenTheAccessLevelCannotBeRead(t *testing.T) { diff --git a/server/internal/api/admin_spa.go b/server/internal/api/admin_spa.go index 36322d4..f325d34 100644 --- a/server/internal/api/admin_spa.go +++ b/server/internal/api/admin_spa.go @@ -109,6 +109,14 @@ func (s *Server) handleAdminConsole(w http.ResponseWriter, r *http.Request) { s.renderAccessLogin(w, r, "", http.StatusOK, r.URL.Path) return } + // A valid but anonymous session (minted before the cookie carried the operator's + // name) leaves the console unable to say who is signed in. Prompt for the shell only, + // so a fresh sign-in fills the name in without breaking asset requests mid-session. + if isAdminDocumentRequest(r) && !s.adminSessionNamed(r) { + s.renderAccessLogin(w, r, "Please sign in again to continue.", + http.StatusOK, r.URL.Path) + return + } // Opening a page is somebody at the keyboard, so it starts the clock again. s.renewAdminSession(w, r) s.setAdminTokenCookie(w, r) diff --git a/server/internal/api/installer_auth.go b/server/internal/api/installer_auth.go index c6136b7..584f553 100644 --- a/server/internal/api/installer_auth.go +++ b/server/internal/api/installer_auth.go @@ -164,6 +164,17 @@ func (s *Server) validAdminSession(r *http.Request) bool { return ok } +// adminSessionNamed reports whether the request's admin session carries the verified Emby +// account name. Every session minted since the identity was added to the cookie has one; +// a session predating it is valid but anonymous, which is what makes the console's user +// switcher fall back to "Administrator" instead of showing who is signed in. The console +// document handler treats an anonymous session as needing a fresh sign-in so the name is +// picked up — a one-time prompt, since renewal preserves whatever the cookie already held. +func (s *Server) adminSessionNamed(r *http.Request) bool { + _, username, ok := s.browserSession(r, adminSessionPurpose) + return ok && username != "" +} + // renewAdminSession slides a valid session's expiry forward. The TTL was absolute and // nothing extended it, so an operator working the admin console was signed out from under // themselves and the page's poll became a permanent "invalid admin