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
+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"})
}