feat: session-cookie login/logout + three-tier roles (superadmin/admin/viewer) with read-only UI + remove pink theme

- cookie-based auth (/login /logout) replacing Basic Auth for UI, enabling logout
- roles: superadmin (account management only), admin (full except accounts), viewer/audit (read-only status+cluster, export logs)
- readonly accounts hide edit buttons (added remote node, group ops, canvas layout/save/import, cluster manage, install) instead of greying them
- auditors see status+cluster only; ordinary admins lose the accounts nav; last-admin guard covers superadmin
- remove pink theme entirely (switcher, [data-theme=pink], leftover localStorage), keep white/blue
This commit is contained in:
2026-08-20 09:08:07 +08:00
parent b39bd427fa
commit f29ec81e4a
27 changed files with 1195 additions and 724 deletions

View File

@ -31,6 +31,8 @@ type Handler struct {
BinDir string
User string
Password string
// Sessions issues the UI login cookies (see session.go).
Sessions *SessionStore
// InstallBinary downloads and activates a frpc binary. Set by the app to
// avoid an import cycle with the install package.
@ -120,10 +122,15 @@ func NewServeMux(h *Handler) (http.Handler, error) {
// Account & API key management (admin only).
mux.HandleFunc(apiPrefix+"/me", h.auth("read")(h.handleMe))
mux.HandleFunc(apiPrefix+"/users", h.auth("admin")(h.handleUsers))
mux.HandleFunc(apiPrefix+"/users/", h.auth("admin")(h.handleUserByName))
mux.HandleFunc(apiPrefix+"/apikeys", h.auth("admin")(h.handleApiKeys))
mux.HandleFunc(apiPrefix+"/apikeys/", h.auth("admin")(h.handleApiKeyByID))
// UI session login/logout. Deliberately NOT behind auth(): login must be
// reachable without credentials, and logout must work even when the
// session is already gone.
mux.HandleFunc(apiPrefix+"/login", h.handleLogin)
mux.HandleFunc(apiPrefix+"/logout", h.handleLogout)
mux.HandleFunc(apiPrefix+"/users", h.auth("superadmin")(h.handleUsers))
mux.HandleFunc(apiPrefix+"/users/", h.auth("superadmin")(h.handleUserByName))
mux.HandleFunc(apiPrefix+"/apikeys", h.auth("superadmin")(h.handleApiKeys))
mux.HandleFunc(apiPrefix+"/apikeys/", h.auth("superadmin")(h.handleApiKeyByID))
// M6: peer-to-peer binary exchange endpoint (Basic Auth, same creds).
// Not under /api so peers hit it directly; auth still applied. Peers