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
@@ -121,15 +121,20 @@ func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
Online bool
|
||||
Seen string
|
||||
}
|
||||
me := s.currentAdmin(r)
|
||||
rows := make([]row, 0, len(agents))
|
||||
for _, a := range agents {
|
||||
if !s.db.CanUseAgent(me, a.ID) {
|
||||
continue
|
||||
}
|
||||
on := s.online(&a)
|
||||
rows = append(rows, row{Agent: a, Online: on, Seen: seenText(on, a.LastSeen)})
|
||||
}
|
||||
s.render(w, "dashboard.html", map[string]any{
|
||||
"Admin": s.currentAdmin(r),
|
||||
"Agents": rows,
|
||||
"CSRF": csrfToken(r),
|
||||
"Admin": me,
|
||||
"IsAdmin": me.IsAdmin(),
|
||||
"Agents": rows,
|
||||
"CSRF": csrfToken(r),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -158,8 +163,12 @@ func (s *Server) handlePresence(w http.ResponseWriter, r *http.Request) {
|
||||
Online bool `json:"online"`
|
||||
Seen string `json:"seen"`
|
||||
}
|
||||
me := s.currentAdmin(r)
|
||||
out := make([]item, 0, len(agents))
|
||||
for _, a := range agents {
|
||||
if !s.db.CanUseAgent(me, a.ID) {
|
||||
continue
|
||||
}
|
||||
on := s.online(&a)
|
||||
out = append(out, item{ID: a.ID, Name: a.Name, Hostname: a.Hostname, User: a.TerminalUser(), Online: on, Seen: seenText(on, a.LastSeen)})
|
||||
}
|
||||
@@ -207,6 +216,7 @@ func (s *Server) handleAgentEdit(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
data := map[string]any{
|
||||
"Agent": a,
|
||||
"IsAdmin": s.currentAdmin(r).IsAdmin(),
|
||||
"CSRF": csrfToken(r),
|
||||
"ServerVersion": Version,
|
||||
"Outdated": a.Version != "" && a.Version != Version,
|
||||
@@ -409,12 +419,15 @@ func (s *Server) handleAgentRename(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
admins, _ := s.db.ListAdmins()
|
||||
keyCounts, passOff := map[int64]int{}, map[int64]bool{}
|
||||
keyCounts, passOff, agentCounts := map[int64]int{}, map[int64]bool{}, map[int64]int{}
|
||||
for _, a := range admins {
|
||||
keyCounts[a.ID] = s.db.CountAdminKeys(a.ID)
|
||||
passOff[a.ID] = s.db.PasswordOff(a.ID)
|
||||
agentCounts[a.ID] = len(s.db.UserAgents(a.ID))
|
||||
}
|
||||
allAgents, _ := s.db.ListAgents()
|
||||
host := s.db.HostAddress()
|
||||
direct := s.db.DirectAddress()
|
||||
secret := s.db.EnrollSecret()
|
||||
code := s.db.InstallCode()
|
||||
ufwInstalled, ufwActive, ufwRunnable := s.ufw.status()
|
||||
@@ -422,10 +435,16 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
s.render(w, "settings.html", map[string]any{
|
||||
"Admin": s.currentAdmin(r),
|
||||
"Host": host,
|
||||
"Direct": direct,
|
||||
"DirectShort": fmt.Sprintf("wget -qO- %s/i/%s | sudo sh", direct, code),
|
||||
"DirectCurl": fmt.Sprintf("curl -fsSL %s/install.sh | sudo HOST=%s FALLBACK_HOST=%s ENROLL_SECRET=%s sh", direct, host, direct, secret),
|
||||
"Secret": secret,
|
||||
"CodeLen": s.db.InstallCodeLen(),
|
||||
"Admins": admins,
|
||||
"KeyCounts": keyCounts,
|
||||
"AgentCounts": agentCounts,
|
||||
"AllAgents": allAgents,
|
||||
"Me": s.currentAdmin(r),
|
||||
"PassOff": passOff,
|
||||
"CSRF": csrfToken(r),
|
||||
"InstallShort": fmt.Sprintf("wget -qO- %s/i/%s | sudo sh", host, code),
|
||||
@@ -444,6 +463,7 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
"TunnelCustom": s.db.TunnelPort() > 0,
|
||||
"Error": map[string]string{
|
||||
"tunnel_host": "Некорректный хост туннеля: нужен IP или имя без схемы и порта.",
|
||||
"host": "Некорректный адрес: нужен URL вида https://panel.example.com, без пути.",
|
||||
"tunnel_port": "Некорректный порт туннеля.",
|
||||
"ufw_port": "Некорректное правило ufw: нужен порт 1-65535 и протокол tcp или udp.",
|
||||
"ufw_off": "Правило ufw не добавлено: ufw выключен или интеграция с ним отключена.",
|
||||
@@ -517,9 +537,20 @@ func (s *Server) handleSetHost(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
host := strings.TrimRight(strings.TrimSpace(r.FormValue("host")), "/")
|
||||
direct := strings.TrimRight(strings.TrimSpace(r.FormValue("direct")), "/")
|
||||
for _, v := range []string{host, direct} {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if u, err := url.Parse(v); err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" || (u.Path != "" && u.Path != "/") {
|
||||
http.Redirect(w, r, "/settings?err=host", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
}
|
||||
if host != "" {
|
||||
_ = s.db.SetConfig(cfgHostAddress, host)
|
||||
}
|
||||
_ = s.db.SetConfig(cfgDirectAddr, direct)
|
||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
@@ -556,12 +587,21 @@ func (s *Server) handleAddAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
username := strings.TrimSpace(r.FormValue("username"))
|
||||
pw := r.FormValue("password")
|
||||
role := RoleAdmin
|
||||
if r.FormValue("role") == RoleUser {
|
||||
role = RoleUser
|
||||
}
|
||||
if username != "" && len(pw) >= 8 {
|
||||
if hash, err := hashPassword(pw); err == nil {
|
||||
_ = s.db.CreateAdmin(username, hash)
|
||||
if id, err := s.db.CreateUser(username, hash, role); err == nil && role == RoleUser {
|
||||
_ = s.db.SetUserAgents(id, r.Form["agents"])
|
||||
}
|
||||
if me := s.currentAdmin(r); me != nil {
|
||||
s.db.Audit(me.ID, "", "user_add:"+username+":"+role)
|
||||
}
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||
http.Redirect(w, r, "/settings#users", http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -571,7 +611,7 @@ func (s *Server) handleDeleteAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64)
|
||||
// Never delete the last admin.
|
||||
if n, _ := s.db.CountAdmins(); n > 1 {
|
||||
if u, _ := s.db.AdminByID(id); u != nil && (!u.IsAdmin() || s.db.CountRole(RoleAdmin) > 1) {
|
||||
_ = s.db.DeleteAdmin(id)
|
||||
}
|
||||
http.Redirect(w, r, "/settings", http.StatusSeeOther)
|
||||
@@ -654,6 +694,7 @@ func (s *Server) renderInstall(w http.ResponseWriter, secret string) {
|
||||
w.Header().Set("Content-Type", "text/x-shellscript; charset=utf-8")
|
||||
if err := s.installTmpl.Execute(w, map[string]any{
|
||||
"Host": s.db.HostAddress(),
|
||||
"Direct": s.db.DirectAddress(),
|
||||
"Secret": secret,
|
||||
}); err != nil {
|
||||
log.Printf("install.sh render: %v", err)
|
||||
|
||||
Reference in new issue
Block a user