LDAP admin GUI + group filter

- LDAP settings configurable from Admin panel (no restart needed)
- Required group filter: only users in specified group can login
- Supports both memberOf attribute and groupOfNames search
- Settings stored in DB (settings table), env vars as fallback
- SLDAP supported via ldaps:// URL
- Bind password masked in UI
This commit is contained in:
2026-05-27 00:08:00 +02:00
parent f58ac04069
commit 8a7b0e18ed
6 changed files with 161 additions and 18 deletions
+1 -1
View File
@@ -95,7 +95,7 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
// Try LDAP if local auth failed
if !localAuthOK {
ldapCfg := auth.LDAPConfigFromEnv()
ldapCfg := auth.LDAPConfigFromDB(s.db)
if ldapCfg != nil {
email, displayName, ldapErr := auth.LDAPAuth(ldapCfg, req.Email, req.Password)
if ldapErr == nil {
+2
View File
@@ -73,6 +73,8 @@ func NewRouter(db *sql.DB, dataDir, secret string) http.Handler {
mux.HandleFunc("POST /api/build/cancel", s.requireAuth(s.handleBuildCancel))
// Daemon endpoints (admin only)
mux.HandleFunc("POST /api/admin/settings/get", s.requireAdmin(s.handleGetSettings))
mux.HandleFunc("POST /api/admin/settings/save", s.requireAdmin(s.handleSaveSettings))
mux.HandleFunc("POST /api/daemon/poll", s.requireAdmin(s.handleDaemonPoll))
mux.HandleFunc("POST /api/daemon/heartbeat", s.requireAdmin(s.handleDaemonHeartbeat))
mux.HandleFunc("POST /api/daemon/report", s.requireAdmin(s.handleDaemonReport))
+55
View File
@@ -0,0 +1,55 @@
package api
import (
"net/http"
)
func (s *Server) handleGetSettings(w http.ResponseWriter, r *http.Request) {
keys := []string{
"ldap_url", "ldap_bind_dn", "ldap_bind_pass", "ldap_base_dn",
"ldap_user_filter", "ldap_group_filter", "ldap_skip_tls",
}
result := make(map[string]string)
for _, k := range keys {
var val string
s.db.QueryRow("SELECT value FROM settings WHERE key = ?", k).Scan(&val)
// Don't expose bind password
if k == "ldap_bind_pass" && val != "" {
result[k] = "••••••••"
} else {
result[k] = val
}
}
writeJSON(w, 200, result)
}
func (s *Server) handleSaveSettings(w http.ResponseWriter, r *http.Request) {
var req map[string]string
if err := decodeBody(r, &req); err != nil {
writeJSON(w, 400, map[string]string{"error": "invalid request"})
return
}
allowed := map[string]bool{
"ldap_url": true, "ldap_bind_dn": true, "ldap_bind_pass": true,
"ldap_base_dn": true, "ldap_user_filter": true, "ldap_group_filter": true,
"ldap_skip_tls": true,
}
for k, v := range req {
if !allowed[k] {
continue
}
// Don't overwrite password with masked value
if k == "ldap_bind_pass" && v == "••••••••" {
continue
}
s.db.Exec(
`INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = ?`,
k, v, v,
)
}
s.audit(getUserID(r), "save_settings", "ldap")
writeJSON(w, 200, map[string]string{"status": "saved"})
}