Add LDAP authentication

- LDAP bind + search auth with auto-create local user
- Falls back to local auth if LDAP not configured or fails
- Configurable via MH_LDAP_* environment variables
- Supports ldap:// and ldaps:// with optional TLS skip
- go-ldap/ldap/v3 dependency added
This commit is contained in:
2026-05-27 00:00:12 +02:00
parent bf655c6bc5
commit f58ac04069
5 changed files with 215 additions and 1 deletions
+33 -1
View File
@@ -90,7 +90,39 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) {
err := s.db.QueryRow(
"SELECT id, password_hash, is_admin, totp_secret FROM users WHERE email = ?", req.Email,
).Scan(&id, &hash, &isAdmin, &totpSecret)
if err != nil || !auth.CheckPassword(hash, req.Password) {
localAuthOK := err == nil && auth.CheckPassword(hash, req.Password)
// Try LDAP if local auth failed
if !localAuthOK {
ldapCfg := auth.LDAPConfigFromEnv()
if ldapCfg != nil {
email, displayName, ldapErr := auth.LDAPAuth(ldapCfg, req.Email, req.Password)
if ldapErr == nil {
// LDAP success — find or create local user
err2 := s.db.QueryRow(
"SELECT id, password_hash, is_admin, totp_secret FROM users WHERE email = ?", email,
).Scan(&id, &hash, &isAdmin, &totpSecret)
if err2 != nil {
// Auto-create user from LDAP
id = uuid.New().String()
username := displayName
if username == "" {
username = req.Email
}
s.db.Exec(
"INSERT INTO users (id, username, email, password_hash, is_admin) VALUES (?, ?, ?, ?, 0)",
id, username, email, "ldap",
)
files.EnsureUserDir(s.dataDir, id)
totpSecret = nil
}
localAuthOK = true
}
}
}
if !localAuthOK {
recordLoginAttempt(ip)
writeJSON(w, 401, map[string]string{"error": "invalid credentials"})
return