mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-22 18:07:55 +00:00
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:
@ -142,14 +142,15 @@ type Forward struct {
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
}
|
||||
|
||||
// User is an authenticated account. Role gates UI/API access (admin = full,
|
||||
// viewer = read-only + exports, for auditors). System users are synced from
|
||||
// the -user/-password flags and are read-only in the account-management UI.
|
||||
// User is an authenticated account. Role gates UI/API access (superadmin =
|
||||
// full + account management, admin = full except account management, viewer =
|
||||
// read-only + exports, for auditors). System users are synced from the
|
||||
// -user/-password flags and are read-only in the account-management UI.
|
||||
type User struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
PasswordHash string `json:"-"` // never serialized to clients
|
||||
Role string `json:"role"` // "admin" | "viewer"
|
||||
Role string `json:"role"` // "admin" | "viewer" | "superadmin"
|
||||
Enabled bool `json:"enabled"`
|
||||
System bool `json:"system"` // true = flag-synced, UI read-only
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
@ -229,7 +230,7 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'admin', -- 'admin' | 'viewer'
|
||||
role TEXT NOT NULL DEFAULT 'admin', -- 'admin' | 'viewer' | 'superadmin'
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
system INTEGER NOT NULL DEFAULT 0, -- 1 = synced from -user/-password flags, UI read-only
|
||||
created_at INTEGER NOT NULL DEFAULT 0,
|
||||
@ -773,7 +774,7 @@ func (s *Store) CreateUser(username, plainPassword, role string) (User, error) {
|
||||
if username == "" || plainPassword == "" {
|
||||
return User{}, ErrInvalid
|
||||
}
|
||||
if role != "admin" && role != "viewer" {
|
||||
if role != "admin" && role != "viewer" && role != "superadmin" {
|
||||
return User{}, ErrInvalid
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(plainPassword), bcrypt.DefaultCost)
|
||||
@ -802,7 +803,7 @@ func (s *Store) UpdateUser(id int64, role string, enabled bool, plainPassword st
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
if role != "admin" && role != "viewer" {
|
||||
if role != "admin" && role != "viewer" && role != "superadmin" {
|
||||
return ErrInvalid
|
||||
}
|
||||
if u.System && plainPassword != "" {
|
||||
@ -840,10 +841,11 @@ func (s *Store) DeleteUser(id int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// CountAdmins returns the count of enabled admin users (for the last-admin guard).
|
||||
// CountAdmins returns the count of enabled admin/superadmin users (for the
|
||||
// last-admin guard; both roles can manage others and must never be wiped out).
|
||||
func (s *Store) CountAdmins() (int, error) {
|
||||
var n int
|
||||
err := s.db.QueryRow("SELECT COUNT(*) FROM users WHERE role = 'admin' AND enabled = 1").Scan(&n)
|
||||
err := s.db.QueryRow("SELECT COUNT(*) FROM users WHERE role IN ('admin','superadmin') AND enabled = 1").Scan(&n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user