chore: weitere Code-Aufteilung (api.ts, hooks, ldap_sync)

- src/lib/api.ts (1085 Zeilen) → 5 thematische Module unter src/lib/api/
  (core, users, ldap, tenants, mail, system) + index.ts Re-Export
- useLDAPConfig / useTenantLDAPConfig / useTenantUsers Hooks extrahiert;
  admin/page.tsx nutzt diese statt roher useState-Blöcke
- handleSyncTenantLDAP, handleAdminSyncTenantLDAP, doSyncTenantLDAP,
  buildTenantTestConfig, syncResult aus ldap_tenants.go in ldap_sync.go verschoben

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-20 13:05:19 +01:00
parent bc4a98de0d
commit a6a66beaa8
14 changed files with 1797 additions and 1466 deletions
+134
View File
@@ -0,0 +1,134 @@
package api
import (
"fmt"
"net/http"
"github.com/archivmail/internal/audit"
"github.com/archivmail/internal/ldapauth"
ldapcfg "github.com/archivmail/internal/ldapconfig"
)
// syncResult is the JSON response returned by LDAP sync endpoints.
type syncResult struct {
Synced int `json:"synced"`
Errors []string `json:"errors"`
}
// handleSyncTenantLDAP imports all LDAP users into the tenant (domain_admin).
func (s *Server) handleSyncTenantLDAP(w http.ResponseWriter, r *http.Request) {
if s.tenantLdapStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
return
}
sess := sessionFromCtx(r.Context())
if sess.TenantID == nil {
writeError(w, http.StatusBadRequest, "no tenant context")
return
}
res := s.doSyncTenantLDAP(r, *sess.TenantID)
s.audlog.Log(audit.Entry{
EventType: "tenant_ldap_sync",
Username: sess.Username,
IPAddress: s.remoteIP(r),
Success: len(res.Errors) == 0,
Detail: fmt.Sprintf("%d Benutzer synchronisiert", res.Synced),
})
writeJSON(w, http.StatusOK, res)
}
// handleAdminSyncTenantLDAP imports all LDAP users into any tenant (superadmin).
func (s *Server) handleAdminSyncTenantLDAP(w http.ResponseWriter, r *http.Request) {
if s.tenantLdapStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
return
}
id, err := parseTenantID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid tenant id")
return
}
res := s.doSyncTenantLDAP(r, id)
sess := sessionFromCtx(r.Context())
s.audlog.Log(audit.Entry{
EventType: "tenant_ldap_sync",
Username: sess.Username,
IPAddress: s.remoteIP(r),
Success: len(res.Errors) == 0,
Detail: fmt.Sprintf("%d Benutzer synchronisiert (tenant %d)", res.Synced, id),
})
writeJSON(w, http.StatusOK, res)
}
// doSyncTenantLDAP is the shared sync logic: fetch all LDAP users and upsert
// them into the local userstore with source='ldap'.
func (s *Server) doSyncTenantLDAP(r *http.Request, tenantID int64) syncResult {
saved, err := s.tenantLdapStore.GetWithPassword(r.Context(), tenantID)
if err != nil || saved == nil {
return syncResult{Errors: []string{"keine LDAP-Konfiguration vorhanden"}}
}
cfg := ldapauth.Config{
URL: saved.URL,
BindDN: saved.BindDN,
BindPassword: saved.BindPassword,
BaseDN: saved.BaseDN,
UserFilter: saved.UserFilter,
TLS: saved.TLS,
TLSSkipVerify: saved.TLSSkipVerify,
}
ldapUsers, err := ldapauth.FetchUsers(cfg)
if err != nil {
return syncResult{Errors: []string{err.Error()}}
}
defaultRole := saved.DefaultRole
if defaultRole == "" {
defaultRole = "user"
}
var res syncResult
res.Errors = []string{}
for _, u := range ldapUsers {
mail := u.Mail
if mail == "" {
mail = u.UID + "@ldap.local"
}
if _, uErr := s.users.UpsertLDAPUser(u.UID, mail, defaultRole, &tenantID); uErr != nil {
res.Errors = append(res.Errors, fmt.Sprintf("%s: %s", u.UID, uErr.Error()))
} else {
res.Synced++
}
}
return res
}
// buildTenantTestConfig constructs an ldapauth.Config for testing, either from
// the saved tenant config or from the provided request body.
func (s *Server) buildTenantTestConfig(r *http.Request, useSaved bool, tenantID int64, provided ldapcfg.TenantLDAPConfig) *ldapauth.Config {
if useSaved {
saved, err := s.tenantLdapStore.GetWithPassword(r.Context(), tenantID)
if err != nil || saved == nil {
return nil
}
return &ldapauth.Config{
URL: saved.URL,
BindDN: saved.BindDN,
BindPassword: saved.BindPassword,
BaseDN: saved.BaseDN,
UserFilter: saved.UserFilter,
TLS: saved.TLS,
TLSSkipVerify: saved.TLSSkipVerify,
}
}
return &ldapauth.Config{
URL: provided.URL,
BindDN: provided.BindDN,
BindPassword: provided.BindPassword,
BaseDN: provided.BaseDN,
UserFilter: provided.UserFilter,
TLS: provided.TLS,
TLSSkipVerify: provided.TLSSkipVerify,
}
}
-125
View File
@@ -771,131 +771,6 @@ func (s *Server) handleAdminTestTenantLDAP(w http.ResponseWriter, r *http.Reques
writeJSON(w, http.StatusOK, result)
}
// ── LDAP Sync handlers ───────────────────────────────────────────────────────
type syncResult struct {
Synced int `json:"synced"`
Errors []string `json:"errors"`
}
// handleSyncTenantLDAP imports all LDAP users into the tenant (domain_admin).
func (s *Server) handleSyncTenantLDAP(w http.ResponseWriter, r *http.Request) {
if s.tenantLdapStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
return
}
sess := sessionFromCtx(r.Context())
if sess.TenantID == nil {
writeError(w, http.StatusBadRequest, "no tenant context")
return
}
res := s.doSyncTenantLDAP(r, *sess.TenantID)
s.audlog.Log(audit.Entry{
EventType: "tenant_ldap_sync",
Username: sess.Username,
IPAddress: s.remoteIP(r),
Success: len(res.Errors) == 0,
Detail: fmt.Sprintf("%d Benutzer synchronisiert", res.Synced),
})
writeJSON(w, http.StatusOK, res)
}
// handleAdminSyncTenantLDAP imports all LDAP users into any tenant (superadmin).
func (s *Server) handleAdminSyncTenantLDAP(w http.ResponseWriter, r *http.Request) {
if s.tenantLdapStore == nil {
writeError(w, http.StatusServiceUnavailable, "tenant ldap store not available")
return
}
id, err := parseTenantID(r)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid tenant id")
return
}
res := s.doSyncTenantLDAP(r, id)
sess := sessionFromCtx(r.Context())
s.audlog.Log(audit.Entry{
EventType: "tenant_ldap_sync",
Username: sess.Username,
IPAddress: s.remoteIP(r),
Success: len(res.Errors) == 0,
Detail: fmt.Sprintf("%d Benutzer synchronisiert (tenant %d)", res.Synced, id),
})
writeJSON(w, http.StatusOK, res)
}
// doSyncTenantLDAP is the shared sync logic: fetch all LDAP users and upsert
// them into the local userstore with source='ldap'.
func (s *Server) doSyncTenantLDAP(r *http.Request, tenantID int64) syncResult {
saved, err := s.tenantLdapStore.GetWithPassword(r.Context(), tenantID)
if err != nil || saved == nil {
return syncResult{Errors: []string{"keine LDAP-Konfiguration vorhanden"}}
}
cfg := ldapauth.Config{
URL: saved.URL,
BindDN: saved.BindDN,
BindPassword: saved.BindPassword,
BaseDN: saved.BaseDN,
UserFilter: saved.UserFilter,
TLS: saved.TLS,
TLSSkipVerify: saved.TLSSkipVerify,
}
ldapUsers, err := ldapauth.FetchUsers(cfg)
if err != nil {
return syncResult{Errors: []string{err.Error()}}
}
defaultRole := saved.DefaultRole
if defaultRole == "" {
defaultRole = "user"
}
var res syncResult
res.Errors = []string{}
for _, u := range ldapUsers {
mail := u.Mail
if mail == "" {
mail = u.UID + "@ldap.local"
}
if _, uErr := s.users.UpsertLDAPUser(u.UID, mail, defaultRole, &tenantID); uErr != nil {
res.Errors = append(res.Errors, fmt.Sprintf("%s: %s", u.UID, uErr.Error()))
} else {
res.Synced++
}
}
return res
}
// buildTenantTestConfig constructs an ldapauth.Config for testing, either from
// the saved tenant config or from the provided request body.
func (s *Server) buildTenantTestConfig(r *http.Request, useSaved bool, tenantID int64, provided ldapcfg.TenantLDAPConfig) *ldapauth.Config {
if useSaved {
saved, err := s.tenantLdapStore.GetWithPassword(r.Context(), tenantID)
if err != nil || saved == nil {
return nil
}
return &ldapauth.Config{
URL: saved.URL,
BindDN: saved.BindDN,
BindPassword: saved.BindPassword,
BaseDN: saved.BaseDN,
UserFilter: saved.UserFilter,
TLS: saved.TLS,
TLSSkipVerify: saved.TLSSkipVerify,
}
}
return &ldapauth.Config{
URL: provided.URL,
BindDN: provided.BindDN,
BindPassword: provided.BindPassword,
BaseDN: provided.BaseDN,
UserFilter: provided.UserFilter,
TLS: provided.TLS,
TLSSkipVerify: provided.TLSSkipVerify,
}
}
// ── helpers ──────────────────────────────────────────────────────────────────
func parseTenantID(r *http.Request) (int64, error) {