feat(PROJ-22): LDAP Web-GUI + feat(PROJ-21): Multi-Tenancy Phase 1

PROJ-22 – LDAP Web-GUI Konfiguration & Test:
- internal/ldapconfig/store.go: AES-256-GCM Passwortspeicherung, CRUD Upsert (id=1)
- internal/ldapauth/client.go: TestConnection (RootDSE, UserCount) + Authenticate (2-step bind)
- internal/auth/auth.go: LDAP-Fallback in Login(), Gruppen-Rollenzuordnung, issueToken helper
- internal/api/ldap_tenants.go: GET/PUT/DELETE/POST-test /api/admin/ldap mit Audit-Log
- go.mod: github.com/go-ldap/ldap/v3 v3.4.8 hinzugefügt
- Frontend: LDAPConfig/LDAPTestResult Typen, LDAP-Tab mit Gruppen-Mappings + Testergebnis

PROJ-21 Phase 1+6+7 – Multi-Tenancy Grundstruktur:
- internal/tenantstore/store.go: tenants, tenant_domains, tenant_ldap Schema; Migration users/audit_log
- API: 8 Tenant-Routen (CRUD + Domain-Management) via SetTenants()
- cmd/archivmail/main.go: ldapSt + tenantSt initialisiert
- Frontend: Mandanten-Tab mit Tabelle, Domain-Dialog, Deaktivieren/Löschen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-17 20:27:56 +01:00
parent 4f0670d94c
commit ac91dceac2
13 changed files with 2063 additions and 11 deletions
+129
View File
@@ -599,3 +599,132 @@ export async function fixSecurityIssue(action: string): Promise<{ message: strin
body: JSON.stringify({ action }),
});
}
// ── LDAP ──────────────────────────────────────────────────────────────────
export interface LDAPGroupMapping {
group_dn: string;
role: string;
}
export interface LDAPConfig {
id?: number;
enabled: boolean;
url: string;
bind_dn: string;
bind_password: string;
base_dn: string;
user_filter: string;
tls: boolean;
tls_skip_verify: boolean;
default_role: string;
group_mappings: LDAPGroupMapping[];
updated_at?: string;
updated_by?: string;
}
export interface LDAPTestResult {
ok: boolean;
message: string;
latency_ms: number;
server_info: string;
users_found: number;
error_detail: string;
}
export async function getLDAPConfig(): Promise<LDAPConfig | null> {
try {
return await request<LDAPConfig>("/api/admin/ldap");
} catch (e: unknown) {
if (e instanceof Error && e.message.includes("404")) return null;
if (e instanceof Error && e.message.includes("no ldap config")) return null;
throw e;
}
}
export async function saveLDAPConfig(cfg: Partial<LDAPConfig>): Promise<void> {
await request<void>("/api/admin/ldap", {
method: "PUT",
body: JSON.stringify(cfg),
});
}
export async function deleteLDAPConfig(): Promise<void> {
await request<void>("/api/admin/ldap", { method: "DELETE" });
}
export async function testLDAPConfig(
payload: { use_saved: boolean } & Partial<LDAPConfig>
): Promise<LDAPTestResult> {
return request<LDAPTestResult>("/api/admin/ldap/test", {
method: "POST",
body: JSON.stringify(payload),
});
}
// ── Tenants ────────────────────────────────────────────────────────────────
export interface Tenant {
id: number;
name: string;
slug: string;
active: boolean;
created_at: string;
domain_count?: number;
user_count?: number;
}
export interface TenantDomain {
id: number;
tenant_id: number;
domain: string;
created_at: string;
}
export async function getTenants(): Promise<Tenant[]> {
return request<Tenant[]>("/api/tenants");
}
export async function createTenant(name: string, slug: string): Promise<Tenant> {
return request<Tenant>("/api/tenants", {
method: "POST",
body: JSON.stringify({ name, slug }),
});
}
export async function updateTenant(
id: number,
data: { name?: string; active?: boolean }
): Promise<Tenant> {
return request<Tenant>(`/api/tenants/${id}`, {
method: "PATCH",
body: JSON.stringify(data),
});
}
export async function deleteTenant(id: number): Promise<void> {
await request<void>(`/api/tenants/${id}`, { method: "DELETE" });
}
export async function getTenantDomains(id: number): Promise<TenantDomain[]> {
return request<TenantDomain[]>(`/api/tenants/${id}/domains`);
}
export async function addTenantDomain(
tenantId: number,
domain: string
): Promise<TenantDomain> {
return request<TenantDomain>(`/api/tenants/${tenantId}/domains`, {
method: "POST",
body: JSON.stringify({ domain }),
});
}
export async function removeTenantDomain(
tenantId: number,
domainId: number
): Promise<void> {
await request<void>(`/api/tenants/${tenantId}/domains/${domainId}`, {
method: "DELETE",
});
}