feat(health): add admin_area_auth_available check

Sixth piece of #419. Visibility-only nudge, same spirit as
mgmt_default_credentials: surfaces on the Health tab that the admin-area
gate exists and is unset, for operators who dismissed the announcement
banner or never saw it on an older release. Does not gate anything.

Refs #419
This commit is contained in:
Tobias Gesellchen
2026-08-08 23:49:57 +02:00
parent 232adeb23f
commit 4bbcd4d178
3 changed files with 71 additions and 0 deletions
+1
View File
@@ -222,6 +222,7 @@ func NewServer(ds *datastore.DataStore, sm *setup.Manager, serverURL string, red
s.healthRegistry,
func() (string, string) { return s.mgmtUsername, s.mgmtPassword },
)
health.RegisterAdminAreaAuthCheck(s.healthRegistry, s.AdminAreaAuthMode)
// Health QuickFix executor for the empty-margeAccountUUID
// finding from RegisterSpeakerInfoReachable. Lives here (not in
@@ -0,0 +1,47 @@
package health
// CheckIDAdminAreaAuth is the registry id of the admin-area-gate
// availability check. It fires when AdminAreaAuth is unset — the tri-state
// setting that lets an operator opt in now to gating the entire admin area
// (not just /api/mgmt/*) behind Basic Auth, ahead of a future release
// flipping the default. See #419 and
// _/i419/design-admin-area-auth-gate.md.
//
// This is a visibility-only nudge, same spirit as
// CheckIDMgmtDefaultCredentials: it does not gate anything and does not
// change default behavior. It exists so operators who dismissed the
// in-app announcement banner (or never saw it, on an older release) can
// still discover the option via the Health tab.
const CheckIDAdminAreaAuth = "admin_area_auth_available"
// RegisterAdminAreaAuthCheck registers the check. getAdminAreaAuthMode
// returns the live AdminAreaAuth mode (typically Server.AdminAreaAuthMode),
// passed as a callback rather than importing the handlers package directly
// to avoid a circular import (handlers already imports health).
func RegisterAdminAreaAuthCheck(r *Registry, getAdminAreaAuthMode func() string) {
r.Register(Check{
ID: CheckIDAdminAreaAuth,
Title: "Admin area can require login for the entire admin console, not just Spotify/Amazon linking",
Run: func() []Finding {
return runAdminAreaAuthCheck(getAdminAreaAuthMode)
},
})
}
func runAdminAreaAuthCheck(getAdminAreaAuthMode func() string) []Finding {
if getAdminAreaAuthMode() != "" {
// Already decided (enabled or explicitly disabled) — nothing to nudge.
return nil
}
return []Finding{{
Severity: SeverityInfo,
Message: "Only Spotify/Amazon account linking and the Local Account tab currently require login. " +
"The rest of the admin area (Devices, Settings, Migration, Health, Logs, ...) is open to anyone " +
"on the network.",
Details: "Set admin_area_auth to \"enabled\" in Settings to require the Management API login " +
"(same credentials as MGMT_USERNAME/MGMT_PASSWORD) for the entire admin area. A future release " +
"is expected to make this the default; you can opt in now, or set it to \"disabled\" to keep " +
"today's behavior once that happens. See issue #419.",
}}
}
@@ -0,0 +1,23 @@
package health
import "testing"
func TestAdminAreaAuthCheck_NoFindingWhenDecided(t *testing.T) {
for _, mode := range []string{"enabled", "disabled"} {
got := runAdminAreaAuthCheck(func() string { return mode })
if len(got) != 0 {
t.Errorf("mode=%q: expected no findings once decided, got %+v", mode, got)
}
}
}
func TestAdminAreaAuthCheck_NudgesWhenUnset(t *testing.T) {
got := runAdminAreaAuthCheck(func() string { return "" })
if len(got) != 1 {
t.Fatalf("expected one finding for the unset default, got %+v", got)
}
if got[0].Severity != SeverityInfo {
t.Errorf("expected SeverityInfo (visibility-only nudge, not a gate), got %v", got[0].Severity)
}
}