feat: user roles - admins and users limited to the machines given to them, user editor with machines, password sign-in and its lock; direct address without Cloudflare as the agents' fallback and in the installer; version 0.1.2
This commit is contained in:
1 parent
36dc12b69d
commit
021da93229
29 files changed
+844
-97
No files matched your search
+129
-20
@@ -120,6 +120,12 @@ CREATE TABLE IF NOT EXISTS forward_rules (
|
||||
`ALTER TABLE admins ADD COLUMN password_off INTEGER NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE agents ADD COLUMN transport TEXT NOT NULL DEFAULT 'auto'`,
|
||||
`ALTER TABLE admins ADD COLUMN pin_hash TEXT NOT NULL DEFAULT ''`,
|
||||
`ALTER TABLE admins ADD COLUMN role TEXT NOT NULL DEFAULT 'admin'`,
|
||||
`ALTER TABLE admins ADD COLUMN pw_locked INTEGER NOT NULL DEFAULT 0`,
|
||||
`CREATE TABLE IF NOT EXISTS admin_agents (
|
||||
admin_id INTEGER NOT NULL REFERENCES admins(id) ON DELETE CASCADE,
|
||||
agent_id TEXT NOT NULL,
|
||||
PRIMARY KEY (admin_id, agent_id))`,
|
||||
} {
|
||||
if _, err := db.sql.Exec(alter); err != nil && !strings.Contains(err.Error(), "duplicate column") {
|
||||
return err
|
||||
@@ -155,24 +161,56 @@ func (db *DB) CountAdmins() (int, error) {
|
||||
}
|
||||
|
||||
func (db *DB) CreateAdmin(username, passHash string) error {
|
||||
_, err := db.sql.Exec(
|
||||
`INSERT INTO admins(username, pass_hash, created_at) VALUES(?, ?, ?)`,
|
||||
username, passHash, time.Now().Unix())
|
||||
_, err := db.CreateUser(username, passHash, RoleAdmin)
|
||||
return err
|
||||
}
|
||||
|
||||
// Admin is one row of the admins table.
|
||||
// CreateUser adds a panel user with the role given and returns its id.
|
||||
func (db *DB) CreateUser(username, passHash, role string) (int64, error) {
|
||||
res, err := db.sql.Exec(
|
||||
`INSERT INTO admins(username, pass_hash, created_at, role) VALUES(?, ?, ?, ?)`,
|
||||
username, passHash, time.Now().Unix(), role)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
// Roles of panel users: an admin sees and runs everything; a user only the
|
||||
// machines given to them, and none of the settings.
|
||||
const (
|
||||
RoleAdmin = "admin"
|
||||
RoleUser = "user"
|
||||
)
|
||||
|
||||
// Admin is one row of the admins table: a panel user, an admin or not.
|
||||
type Admin struct {
|
||||
ID int64
|
||||
Username string
|
||||
PassHash string
|
||||
Role string
|
||||
// PwLocked keeps the user from turning the password sign-in on or off
|
||||
// themselves: an admin set it.
|
||||
PwLocked bool
|
||||
}
|
||||
|
||||
// IsAdmin reports whether the user has the run of the whole panel.
|
||||
func (a *Admin) IsAdmin() bool { return a != nil && a.Role != RoleUser }
|
||||
|
||||
const adminCols = `id, username, pass_hash, role, pw_locked`
|
||||
|
||||
func scanAdmin(row interface{ Scan(...any) error }) (*Admin, error) {
|
||||
a := &Admin{}
|
||||
var locked int
|
||||
if err := row.Scan(&a.ID, &a.Username, &a.PassHash, &a.Role, &locked); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.PwLocked = locked == 1
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (db *DB) AdminByUsername(username string) (*Admin, error) {
|
||||
a := &Admin{}
|
||||
err := db.sql.QueryRow(
|
||||
`SELECT id, username, pass_hash FROM admins WHERE username = ?`, username).
|
||||
Scan(&a.ID, &a.Username, &a.PassHash)
|
||||
a, err := scanAdmin(db.sql.QueryRow(`SELECT `+adminCols+` FROM admins WHERE username = ?`, username))
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -180,22 +218,87 @@ func (db *DB) AdminByUsername(username string) (*Admin, error) {
|
||||
}
|
||||
|
||||
func (db *DB) ListAdmins() ([]Admin, error) {
|
||||
rows, err := db.sql.Query(`SELECT id, username FROM admins ORDER BY username`)
|
||||
rows, err := db.sql.Query(`SELECT ` + adminCols + ` FROM admins ORDER BY role, username`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []Admin
|
||||
for rows.Next() {
|
||||
var a Admin
|
||||
if err := rows.Scan(&a.ID, &a.Username); err != nil {
|
||||
a, err := scanAdmin(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, a)
|
||||
out = append(out, *a)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// CountRole is how many users have the role.
|
||||
func (db *DB) CountRole(role string) int {
|
||||
var n int
|
||||
_ = db.sql.QueryRow(`SELECT COUNT(*) FROM admins WHERE role = ?`, role).Scan(&n)
|
||||
return n
|
||||
}
|
||||
|
||||
func (db *DB) SetUserRole(id int64, role string) error {
|
||||
_, err := db.sql.Exec(`UPDATE admins SET role = ? WHERE id = ?`, role, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) SetPwLocked(id int64, locked bool) error {
|
||||
_, err := db.sql.Exec(`UPDATE admins SET pw_locked = ? WHERE id = ?`, boolInt(locked), id)
|
||||
return err
|
||||
}
|
||||
|
||||
// UserAgents are the machines a user may use.
|
||||
func (db *DB) UserAgents(adminID int64) map[string]bool {
|
||||
out := map[string]bool{}
|
||||
rows, err := db.sql.Query(`SELECT agent_id FROM admin_agents WHERE admin_id = ?`, adminID)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if rows.Scan(&id) == nil {
|
||||
out[id] = true
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// SetUserAgents gives the user exactly these machines.
|
||||
func (db *DB) SetUserAgents(adminID int64, agentIDs []string) error {
|
||||
tx, err := db.sql.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.Exec(`DELETE FROM admin_agents WHERE admin_id = ?`, adminID); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range agentIDs {
|
||||
if _, err := tx.Exec(`INSERT OR IGNORE INTO admin_agents(admin_id, agent_id) VALUES(?, ?)`, adminID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// CanUseAgent reports whether the user may use the machine.
|
||||
func (db *DB) CanUseAgent(a *Admin, agentID string) bool {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
if a.IsAdmin() {
|
||||
return true
|
||||
}
|
||||
var n int
|
||||
_ = db.sql.QueryRow(`SELECT COUNT(*) FROM admin_agents WHERE admin_id = ? AND agent_id = ?`, a.ID, agentID).Scan(&n)
|
||||
return n > 0
|
||||
}
|
||||
|
||||
func (db *DB) DeleteAdmin(id int64) error {
|
||||
_, err := db.sql.Exec(`DELETE FROM admins WHERE id = ?`, id)
|
||||
return err
|
||||
@@ -214,9 +317,7 @@ type AdminKey struct {
|
||||
}
|
||||
|
||||
func (db *DB) AdminByID(id int64) (*Admin, error) {
|
||||
a := &Admin{}
|
||||
err := db.sql.QueryRow(`SELECT id, username, pass_hash FROM admins WHERE id = ?`, id).
|
||||
Scan(&a.ID, &a.Username, &a.PassHash)
|
||||
a, err := scanAdmin(db.sql.QueryRow(`SELECT `+adminCols+` FROM admins WHERE id = ?`, id))
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -338,12 +439,10 @@ func (db *DB) CreateSession(id string, adminID int64, ttl time.Duration) error {
|
||||
|
||||
// SessionAdmin returns the admin for a live (non-expired) session, or nil.
|
||||
func (db *DB) SessionAdmin(sessionID string) (*Admin, error) {
|
||||
a := &Admin{}
|
||||
err := db.sql.QueryRow(
|
||||
`SELECT a.id, a.username, a.pass_hash
|
||||
a, err := scanAdmin(db.sql.QueryRow(
|
||||
`SELECT a.id, a.username, a.pass_hash, a.role, a.pw_locked
|
||||
FROM admin_sessions s JOIN admins a ON a.id = s.admin_id
|
||||
WHERE s.id = ? AND s.expires_at > ?`, sessionID, time.Now().Unix()).
|
||||
Scan(&a.ID, &a.Username, &a.PassHash)
|
||||
WHERE s.id = ? AND s.expires_at > ?`, sessionID, time.Now().Unix()))
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -455,6 +554,9 @@ func (db *DB) SetAgentLoginUser(id, user string) error {
|
||||
|
||||
func (db *DB) DeleteAgent(id string) error {
|
||||
_, err := db.sql.Exec(`DELETE FROM agents WHERE id = ?`, id)
|
||||
if err == nil {
|
||||
_, _ = db.sql.Exec(`DELETE FROM admin_agents WHERE agent_id = ?`, id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -631,3 +733,10 @@ func (db *DB) Audit(adminID int64, agentID, action string) {
|
||||
`INSERT INTO audit(admin_id, agent_id, action, at) VALUES(?, ?, ?, ?)`,
|
||||
adminID, agentID, action, time.Now().Unix())
|
||||
}
|
||||
|
||||
func boolInt(b bool) int {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in new issue
Block a user