dc13edf2f6
Backend gibt qr_code als base64-PNG zurück — Frontend war auf qr_code_svg ausgerichtet. Fix: getTOTPSetup-Typ angepasst, img-Tag statt dangerouslySetInnerHTML. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
843 lines
23 KiB
TypeScript
843 lines
23 KiB
TypeScript
import { clearAuthCache } from "@/lib/auth-cache";
|
|
|
|
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "";
|
|
|
|
async function request<T>(
|
|
path: string,
|
|
options: RequestInit = {}
|
|
): Promise<T> {
|
|
const headers: Record<string, string> = {
|
|
"Content-Type": "application/json",
|
|
...(options.headers as Record<string, string>),
|
|
};
|
|
|
|
const res = await fetch(`${API_BASE}${path}`, {
|
|
...options,
|
|
headers,
|
|
credentials: "include",
|
|
});
|
|
|
|
if (res.status === 401) {
|
|
clearAuthCache();
|
|
throw new Error("Unauthorized");
|
|
}
|
|
|
|
if (!res.ok) {
|
|
const body = await res.text();
|
|
throw new Error(body || `Request failed: ${res.status}`);
|
|
}
|
|
|
|
if (res.status === 204) return {} as T;
|
|
|
|
return res.json();
|
|
}
|
|
|
|
// Types
|
|
|
|
export interface LoginResponse {
|
|
user: {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
role: string;
|
|
};
|
|
}
|
|
|
|
export interface User {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
role: string;
|
|
active: boolean;
|
|
}
|
|
|
|
export interface MeResponse {
|
|
username: string;
|
|
role: string;
|
|
email: string;
|
|
}
|
|
|
|
export interface SMTPStatus {
|
|
// global daemon fields (superadmin)
|
|
running?: boolean;
|
|
enabled?: boolean;
|
|
bind?: string;
|
|
domain?: string;
|
|
tls?: boolean;
|
|
max_size_mb?: number;
|
|
allowed_ips?: string[];
|
|
received?: number;
|
|
rejected?: number;
|
|
last_mail_at?: string;
|
|
// tenant-scoped fields (domain_admin)
|
|
tenant_only?: boolean;
|
|
domains?: string[];
|
|
total_mails?: number;
|
|
total_bytes?: number;
|
|
}
|
|
|
|
export interface HealthResponse {
|
|
status: string;
|
|
}
|
|
|
|
export interface SearchHit {
|
|
id: string;
|
|
score: number;
|
|
from?: string;
|
|
to?: string;
|
|
subject?: string;
|
|
date?: string;
|
|
size?: number;
|
|
has_attachments?: boolean;
|
|
}
|
|
|
|
export interface SearchResponse {
|
|
total: number;
|
|
hits: SearchHit[];
|
|
}
|
|
|
|
export interface MailAttachment {
|
|
index: number;
|
|
filename: string;
|
|
content_type: string;
|
|
size: number;
|
|
}
|
|
|
|
export interface MailDetail {
|
|
id: string;
|
|
from: string;
|
|
to: string;
|
|
cc?: string;
|
|
subject: string;
|
|
date: string;
|
|
size: number;
|
|
body_html?: string;
|
|
body_plain?: string;
|
|
raw_headers: string;
|
|
attachments: MailAttachment[];
|
|
verify_ok: boolean | null;
|
|
verified_at: string | null;
|
|
}
|
|
|
|
export interface AuditEntry {
|
|
id: string;
|
|
timestamp: string;
|
|
event_type: string;
|
|
username: string;
|
|
detail: string;
|
|
}
|
|
|
|
export interface AuditResponse {
|
|
total: number;
|
|
entries: AuditEntry[];
|
|
}
|
|
|
|
export interface CreateUserRequest {
|
|
username: string;
|
|
email: string;
|
|
password: string;
|
|
role: string;
|
|
}
|
|
|
|
export interface UpdateUserRequest {
|
|
email?: string;
|
|
role?: string;
|
|
active?: boolean;
|
|
password?: string;
|
|
}
|
|
|
|
// API functions
|
|
|
|
export async function login(
|
|
username: string,
|
|
password: string
|
|
): Promise<LoginResponse> {
|
|
return request<LoginResponse>("/api/auth/login", {
|
|
method: "POST",
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
}
|
|
|
|
export async function getMe(): Promise<MeResponse> {
|
|
return request<MeResponse>("/api/auth/me");
|
|
}
|
|
|
|
export async function logout(): Promise<void> {
|
|
clearAuthCache();
|
|
await request<void>("/api/auth/logout", { method: "POST" });
|
|
}
|
|
|
|
export async function searchEmails(params: {
|
|
q?: string;
|
|
from?: string;
|
|
to?: string;
|
|
date_from?: string;
|
|
date_to?: string;
|
|
sort?: string;
|
|
has_attachment?: boolean;
|
|
page?: number;
|
|
page_size?: number;
|
|
}): Promise<SearchResponse> {
|
|
const sp = new URLSearchParams();
|
|
if (params.q) sp.set("q", params.q);
|
|
if (params.from) sp.set("from", params.from);
|
|
if (params.to) sp.set("to", params.to);
|
|
if (params.date_from) sp.set("date_from", params.date_from);
|
|
if (params.date_to) sp.set("date_to", params.date_to);
|
|
if (params.sort) sp.set("sort", params.sort);
|
|
if (params.has_attachment !== undefined) sp.set("has_attachment", String(params.has_attachment));
|
|
if (params.page) sp.set("page", String(params.page));
|
|
if (params.page_size) sp.set("page_size", String(params.page_size));
|
|
return request<SearchResponse>(`/api/search?${sp.toString()}`);
|
|
}
|
|
|
|
export async function getUsers(): Promise<User[]> {
|
|
return request<User[]>("/api/users");
|
|
}
|
|
|
|
export async function createUser(data: CreateUserRequest): Promise<User> {
|
|
return request<User>("/api/users", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function updateUser(id: number, data: UpdateUserRequest): Promise<User> {
|
|
return request<User>(`/api/users/${id}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function deleteUser(id: number): Promise<void> {
|
|
await request<void>(`/api/users/${id}`, { method: "DELETE" });
|
|
}
|
|
|
|
export interface StorageStats {
|
|
total_mails: number;
|
|
total_bytes: number;
|
|
}
|
|
|
|
export async function getStorageStats(): Promise<StorageStats> {
|
|
return request<StorageStats>("/api/admin/storage/stats");
|
|
}
|
|
|
|
export async function getSMTPStatus(): Promise<SMTPStatus> {
|
|
return request<SMTPStatus>("/api/admin/smtp/status");
|
|
}
|
|
|
|
export async function getHealth(): Promise<HealthResponse> {
|
|
return request<HealthResponse>("/api/health");
|
|
}
|
|
|
|
export async function getMail(id: string): Promise<MailDetail> {
|
|
return request<MailDetail>(`/api/mails/${id}`);
|
|
}
|
|
|
|
export async function downloadMailAttachment(
|
|
id: string,
|
|
index: number
|
|
): Promise<{ blob: Blob; filename: string }> {
|
|
const res = await fetch(`${API_BASE}/api/mails/${id}/attachments/${index}`, {
|
|
credentials: "include",
|
|
});
|
|
if (!res.ok) throw new Error(`Download fehlgeschlagen: ${res.status}`);
|
|
const disposition = res.headers.get("Content-Disposition") || "";
|
|
const match = disposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
|
const filename = match ? match[1].replace(/['"]/g, "") : `anhang-${index}`;
|
|
return { blob: await res.blob(), filename };
|
|
}
|
|
|
|
export async function downloadMailRaw(
|
|
id: string
|
|
): Promise<{ blob: Blob; filename: string }> {
|
|
const res = await fetch(`${API_BASE}/api/mails/${id}/raw`, {
|
|
credentials: "include",
|
|
});
|
|
if (!res.ok) throw new Error(`Download fehlgeschlagen: ${res.status}`);
|
|
return { blob: await res.blob(), filename: `${id}.eml` };
|
|
}
|
|
|
|
export interface ServiceStatus {
|
|
name: string;
|
|
display_name: string;
|
|
active: string;
|
|
sub: string;
|
|
enabled: string;
|
|
description: string;
|
|
external_blocked?: boolean;
|
|
}
|
|
|
|
export async function getServices(): Promise<ServiceStatus[]> {
|
|
return request<ServiceStatus[]>("/api/admin/services");
|
|
}
|
|
|
|
export async function serviceAction(
|
|
name: string,
|
|
action: "start" | "stop" | "restart" | "enable" | "disable" | "block_external" | "allow_external"
|
|
): Promise<ServiceStatus> {
|
|
return request<ServiceStatus>(`/api/admin/services/${encodeURIComponent(name)}/action`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ action }),
|
|
});
|
|
}
|
|
|
|
export async function getAuditLog(params: {
|
|
page?: number;
|
|
page_size?: number;
|
|
username?: string;
|
|
event_type?: string;
|
|
}): Promise<AuditResponse> {
|
|
const sp = new URLSearchParams();
|
|
if (params.page) sp.set("page", String(params.page));
|
|
if (params.page_size) sp.set("page_size", String(params.page_size));
|
|
if (params.username) sp.set("username", params.username);
|
|
if (params.event_type) sp.set("event_type", params.event_type);
|
|
return request<AuditResponse>(`/api/audit?${sp.toString()}`);
|
|
}
|
|
|
|
// ── IMAP ──────────────────────────────────────────────────────────────────
|
|
|
|
export interface ImapFolder {
|
|
name: string;
|
|
excluded: boolean;
|
|
reason?: string;
|
|
}
|
|
|
|
export interface ImapAccount {
|
|
id: number;
|
|
owner: string;
|
|
name: string;
|
|
host: string;
|
|
port: number;
|
|
tls: string;
|
|
username: string;
|
|
excluded_folders: string[];
|
|
status: string;
|
|
error_msg: string;
|
|
last_import_at?: string;
|
|
last_import_count: number;
|
|
progress_current: number;
|
|
progress_total: number;
|
|
created_at: string;
|
|
// PROJ-8: Auto-sync fields
|
|
sync_interval_min: number;
|
|
last_sync_at?: string;
|
|
last_sync_count: number;
|
|
sync_running: boolean;
|
|
sync_status: string;
|
|
sync_error_msg: string;
|
|
}
|
|
|
|
export interface ImapTestResult {
|
|
ok: boolean;
|
|
folders?: ImapFolder[];
|
|
error?: string;
|
|
}
|
|
|
|
export async function getImapAccounts(): Promise<ImapAccount[]> {
|
|
return request<ImapAccount[]>("/api/imap");
|
|
}
|
|
|
|
export async function createImapAccount(data: {
|
|
name: string;
|
|
host: string;
|
|
port: number;
|
|
tls: string;
|
|
username: string;
|
|
password: string;
|
|
excluded_folders: string[];
|
|
}): Promise<ImapAccount> {
|
|
return request<ImapAccount>("/api/imap", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function deleteImapAccount(id: number): Promise<void> {
|
|
await request<void>(`/api/imap/${id}`, { method: "DELETE" });
|
|
}
|
|
|
|
export async function testImapConnection(data: {
|
|
host: string;
|
|
port: number;
|
|
tls: string;
|
|
username: string;
|
|
password: string;
|
|
}): Promise<ImapTestResult> {
|
|
return request<ImapTestResult>("/api/imap/test", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function startImapImport(id: number): Promise<ImapAccount> {
|
|
return request<ImapAccount>(`/api/imap/${id}/import`, { method: "POST" });
|
|
}
|
|
|
|
export async function getImapProgress(id: number): Promise<ImapAccount> {
|
|
return request<ImapAccount>(`/api/imap/${id}/progress`);
|
|
}
|
|
|
|
export async function triggerImapSync(id: number): Promise<ImapAccount> {
|
|
return request<ImapAccount>(`/api/imap/${id}/sync`, { method: "POST" });
|
|
}
|
|
|
|
export async function updateImapInterval(id: number, intervalMin: number): Promise<ImapAccount> {
|
|
return request<ImapAccount>(`/api/imap/${id}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify({ sync_interval_min: intervalMin }),
|
|
});
|
|
}
|
|
|
|
// ── POP3 ──────────────────────────────────────────────────────────────────
|
|
|
|
export interface Pop3Account {
|
|
id: number;
|
|
owner: string;
|
|
name: string;
|
|
host: string;
|
|
port: number;
|
|
tls: string;
|
|
tls_skip_verify: boolean;
|
|
username: string;
|
|
status: string;
|
|
error_msg: string;
|
|
last_import_at?: string;
|
|
last_import_count: number;
|
|
progress_current: number;
|
|
progress_total: number;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface Pop3TestResult {
|
|
ok: boolean;
|
|
message: string;
|
|
message_count?: number;
|
|
total_size_bytes?: number;
|
|
}
|
|
|
|
export async function getPop3Accounts(): Promise<Pop3Account[]> {
|
|
return request<Pop3Account[]>("/api/pop3");
|
|
}
|
|
|
|
export async function createPop3Account(data: {
|
|
name: string;
|
|
host: string;
|
|
port: number;
|
|
tls: string;
|
|
tls_skip_verify: boolean;
|
|
username: string;
|
|
password: string;
|
|
}): Promise<Pop3Account> {
|
|
return request<Pop3Account>("/api/pop3", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function deletePop3Account(id: number): Promise<void> {
|
|
await request<void>(`/api/pop3/${id}`, { method: "DELETE" });
|
|
}
|
|
|
|
export async function testPop3Connection(data: {
|
|
host: string;
|
|
port: number;
|
|
tls: string;
|
|
tls_skip_verify: boolean;
|
|
username: string;
|
|
password: string;
|
|
}): Promise<Pop3TestResult> {
|
|
return request<Pop3TestResult>("/api/pop3/test", {
|
|
method: "POST",
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
export async function startPop3Import(id: number): Promise<Pop3Account> {
|
|
return request<Pop3Account>(`/api/pop3/${id}/import`, { method: "POST" });
|
|
}
|
|
|
|
export async function getPop3Progress(id: number): Promise<Pop3Account> {
|
|
return request<Pop3Account>(`/api/pop3/${id}/progress`);
|
|
}
|
|
|
|
// ── System Stats ──────────────────────────────────────────────────────────
|
|
|
|
export interface SystemStatsCPU {
|
|
load1: number;
|
|
load5: number;
|
|
load15: number;
|
|
num_cpu: number;
|
|
}
|
|
|
|
export interface SystemStatsRAM {
|
|
total_bytes: number;
|
|
used_bytes: number;
|
|
free_bytes: number;
|
|
used_pct: number;
|
|
}
|
|
|
|
export interface SystemStatsDisk {
|
|
mount: string;
|
|
total_bytes: number;
|
|
used_bytes: number;
|
|
free_bytes: number;
|
|
used_pct: number;
|
|
fstype: string;
|
|
}
|
|
|
|
export interface SystemStatsMailInfo {
|
|
id: string;
|
|
date: string;
|
|
from: string;
|
|
subject: string;
|
|
}
|
|
|
|
export interface SystemStats {
|
|
cpu: SystemStatsCPU;
|
|
ram: SystemStatsRAM;
|
|
disks: SystemStatsDisk[];
|
|
archive: {
|
|
first_mail: SystemStatsMailInfo | null;
|
|
last_mail: SystemStatsMailInfo | null;
|
|
};
|
|
}
|
|
|
|
export async function getSystemStats(): Promise<SystemStats> {
|
|
return request<SystemStats>("/api/admin/system/stats");
|
|
}
|
|
|
|
// ── Export ────────────────────────────────────────────────────────────────
|
|
|
|
export async function exportMailPDF(id: string): Promise<{ blob: Blob; filename: string }> {
|
|
const res = await fetch(`${API_BASE}/api/export/pdf/${id}`, {
|
|
credentials: "include",
|
|
});
|
|
if (!res.ok) throw new Error("PDF export failed");
|
|
const blob = await res.blob();
|
|
const cd = res.headers.get("Content-Disposition") || "";
|
|
const filename = cd.match(/filename="([^"]+)"/)?.[1] || `${id.slice(0, 16)}.pdf`;
|
|
return { blob, filename };
|
|
}
|
|
|
|
export async function exportMailsZIP(ids: string[], attachments: boolean): Promise<{ blob: Blob }> {
|
|
const res = await fetch(`${API_BASE}/api/export/zip`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify({ ids, attachments }),
|
|
});
|
|
if (!res.ok) throw new Error("ZIP export failed");
|
|
return { blob: await res.blob() };
|
|
}
|
|
|
|
// ── Upload ────────────────────────────────────────────────────────────────
|
|
|
|
export interface UploadJob {
|
|
id: string;
|
|
status: "running" | "done" | "error";
|
|
total: number;
|
|
imported: number;
|
|
skipped: number;
|
|
errors: number;
|
|
error_msg?: string;
|
|
}
|
|
|
|
export async function uploadMailFiles(files: File[]): Promise<{ job_id: string }> {
|
|
const form = new FormData();
|
|
for (const f of files) form.append("files", f);
|
|
const res = await fetch(`${API_BASE}/api/admin/upload`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
body: form,
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.text();
|
|
throw new Error(body || `Upload failed: ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function getUploadProgress(jobID: string): Promise<UploadJob> {
|
|
return request<UploadJob>(`/api/admin/upload/${jobID}/progress`);
|
|
}
|
|
|
|
export async function uploadMailFilesUser(files: File[]): Promise<{ job_id: string }> {
|
|
const form = new FormData();
|
|
for (const f of files) form.append("files", f);
|
|
const res = await fetch(`${API_BASE}/api/upload`, {
|
|
method: "POST",
|
|
credentials: "include",
|
|
body: form,
|
|
});
|
|
if (!res.ok) {
|
|
const body = await res.text();
|
|
throw new Error(body || `Upload failed: ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function getUploadProgressUser(jobID: string): Promise<UploadJob> {
|
|
return request<UploadJob>(`/api/upload/${jobID}/progress`);
|
|
}
|
|
|
|
// ── Security Audit ────────────────────────────────────────────────────────
|
|
|
|
export interface SecurityCheck {
|
|
name: string;
|
|
status: "ok" | "warning" | "error";
|
|
message: string;
|
|
}
|
|
|
|
export interface SecurityAuditResult {
|
|
checks: SecurityCheck[];
|
|
run_at: string;
|
|
}
|
|
|
|
export async function getSecurityAudit(): Promise<SecurityAuditResult> {
|
|
return request<SecurityAuditResult>("/api/admin/security/audit");
|
|
}
|
|
|
|
export async function fixSecurityIssue(action: string): Promise<{ message: string }> {
|
|
return request<{ message: string }>("/api/admin/security/fix", {
|
|
method: "POST",
|
|
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;
|
|
ldap_enabled?: boolean;
|
|
ldap_url?: string;
|
|
}
|
|
|
|
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",
|
|
});
|
|
}
|
|
|
|
// ── PROJ-23: Pro-Mandant LDAP (tenant_ldap) ──────────────────────────────
|
|
|
|
export interface TenantLDAPConfig extends LDAPConfig {
|
|
tenant_id?: number;
|
|
}
|
|
|
|
// domain_admin -- eigener Mandant
|
|
export async function getTenantLDAPConfig(): Promise<TenantLDAPConfig | null> {
|
|
try {
|
|
return await request<TenantLDAPConfig>("/api/tenant/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 saveTenantLDAPConfig(cfg: Partial<TenantLDAPConfig>): Promise<void> {
|
|
await request<void>("/api/tenant/ldap", { method: "PUT", body: JSON.stringify(cfg) });
|
|
}
|
|
|
|
export async function deleteTenantLDAPConfig(): Promise<void> {
|
|
await request<void>("/api/tenant/ldap", { method: "DELETE" });
|
|
}
|
|
|
|
export async function testTenantLDAPConfig(
|
|
payload: { use_saved: boolean } & Partial<TenantLDAPConfig>
|
|
): Promise<LDAPTestResult> {
|
|
return request<LDAPTestResult>("/api/tenant/ldap/test", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
// superadmin -- beliebiger Mandant per ID
|
|
export async function getAdminTenantLDAPConfig(tenantID: number): Promise<TenantLDAPConfig | null> {
|
|
try {
|
|
return await request<TenantLDAPConfig>(`/api/admin/tenants/${tenantID}/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 saveAdminTenantLDAPConfig(tenantID: number, cfg: Partial<TenantLDAPConfig>): Promise<void> {
|
|
await request<void>(`/api/admin/tenants/${tenantID}/ldap`, { method: "PUT", body: JSON.stringify(cfg) });
|
|
}
|
|
|
|
export async function deleteAdminTenantLDAPConfig(tenantID: number): Promise<void> {
|
|
await request<void>(`/api/admin/tenants/${tenantID}/ldap`, { method: "DELETE" });
|
|
}
|
|
|
|
export async function testAdminTenantLDAPConfig(
|
|
tenantID: number,
|
|
payload: { use_saved: boolean } & Partial<TenantLDAPConfig>
|
|
): Promise<LDAPTestResult> {
|
|
return request<LDAPTestResult>(`/api/admin/tenants/${tenantID}/ldap/test`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
}
|
|
|
|
// ── Profil-Einstellungen ──────────────────────────────────────────────────
|
|
|
|
export async function changePassword(
|
|
currentPassword: string,
|
|
newPassword: string
|
|
): Promise<{ ok: boolean }> {
|
|
return request<{ ok: boolean }>("/api/auth/password", {
|
|
method: "PATCH",
|
|
body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }),
|
|
});
|
|
}
|
|
|
|
export async function changeEmail(
|
|
email: string
|
|
): Promise<{ ok: boolean; email: string }> {
|
|
return request<{ ok: boolean; email: string }>("/api/auth/email", {
|
|
method: "PATCH",
|
|
body: JSON.stringify({ email }),
|
|
});
|
|
}
|
|
|
|
// ── TOTP / 2FA ────────────────────────────────────────────────────────────
|
|
|
|
export async function getTOTPSetup(): Promise<{ secret: string; otpauth_url: string; qr_code: string }> {
|
|
return request<{ secret: string; otpauth_url: string; qr_code_svg: string }>("/api/auth/totp/setup");
|
|
}
|
|
|
|
export async function confirmTOTPSetup(code: string): Promise<{ ok: boolean }> {
|
|
return request<{ ok: boolean }>("/api/auth/totp/setup", {
|
|
method: "POST",
|
|
body: JSON.stringify({ code }),
|
|
});
|
|
}
|
|
|
|
export async function disableTOTP(code: string): Promise<{ ok: boolean }> {
|
|
return request<{ ok: boolean }>("/api/auth/totp", {
|
|
method: "DELETE",
|
|
body: JSON.stringify({ code }),
|
|
});
|
|
}
|