b4be3cebfb
Nach Änderung der Kunden-Stamm-E-Mail (oder der defaultForwardEmail in den Provider-Settings) müssen die Plesk-Forwards der Stressfrei-Adressen des Kunden auf den neuen Wert umgestellt werden. Bisher ging das nur manuell pro Adresse im Plesk-UI – jetzt mit einem Klick pro Adresse im CRM. Backend: - emailProviderService.setEmailForwardTargets(localPart, targets[]): dünner Wrapper um die schon vorhandene IEmailProvider-Methode updateForwardTargets (`set:email1,email2` ersetzt komplett, idempotent) - stressfreiEmail.service.syncForwardingForEmail(id): lädt Kunde + Provider-Config, baut [customer.email, defaultForwardEmail] und ruft den Provider auf - POST /api/stressfrei-emails/:id/sync-forwarding, customers:update, Audit-Log mit den neuen Forward-Targets im Label Frontend: - Refresh-Icon-Button in der Action-Reihe jeder Stressfrei-Adresse, sichtbar nur wenn isProvisioned (sonst sinnlos). Confirm-Dialog zeigt die Ziele, Tooltip erklärt den Vorgang. - ExternalLink-Icon neben der E-Mail in der Kundenakte (Stammdaten → Kontakt) öffnet den Stressfrei-Tab des Kunden in neuem Tab. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
693 lines
21 KiB
TypeScript
693 lines
21 KiB
TypeScript
// ==================== EMAIL PROVIDER SERVICE ====================
|
||
|
||
import prisma from '../../lib/prisma.js';
|
||
import { decrypt } from '../../utils/encryption.js';
|
||
import {
|
||
IEmailProvider,
|
||
EmailProviderConfig,
|
||
EmailExistsResult,
|
||
EmailOperationResult,
|
||
CreateEmailParams,
|
||
MailEncryption,
|
||
} from './types.js';
|
||
import { PleskEmailProvider } from './pleskProvider.js';
|
||
|
||
// Factory-Funktion um den richtigen Provider zu erstellen
|
||
function createProvider(config: EmailProviderConfig): IEmailProvider {
|
||
switch (config.type) {
|
||
case 'PLESK':
|
||
return new PleskEmailProvider(config);
|
||
case 'CPANEL':
|
||
// TODO: cPanel Provider implementieren
|
||
throw new Error('cPanel Provider noch nicht implementiert');
|
||
case 'DIRECTADMIN':
|
||
// TODO: DirectAdmin Provider implementieren
|
||
throw new Error('DirectAdmin Provider noch nicht implementiert');
|
||
default:
|
||
throw new Error(`Unbekannter Provider-Typ: ${config.type}`);
|
||
}
|
||
}
|
||
|
||
// ==================== CONFIG CRUD ====================
|
||
|
||
export async function getAllProviderConfigs() {
|
||
return prisma.emailProviderConfig.findMany({
|
||
orderBy: [{ isDefault: 'desc' }, { name: 'asc' }],
|
||
});
|
||
}
|
||
|
||
export async function getProviderConfigById(id: number) {
|
||
return prisma.emailProviderConfig.findUnique({
|
||
where: { id },
|
||
});
|
||
}
|
||
|
||
export async function getDefaultProviderConfig() {
|
||
return prisma.emailProviderConfig.findFirst({
|
||
where: { isActive: true, isDefault: true },
|
||
});
|
||
}
|
||
|
||
export async function getActiveProviderConfig() {
|
||
// Erst Default-Provider versuchen, dann irgendeinen aktiven
|
||
const defaultProvider = await getDefaultProviderConfig();
|
||
if (defaultProvider) return defaultProvider;
|
||
|
||
return prisma.emailProviderConfig.findFirst({
|
||
where: { isActive: true },
|
||
});
|
||
}
|
||
|
||
export interface CreateProviderConfigData {
|
||
name: string;
|
||
type: 'PLESK' | 'CPANEL' | 'DIRECTADMIN';
|
||
apiUrl: string;
|
||
apiKey?: string;
|
||
username?: string;
|
||
password?: string;
|
||
domain: string;
|
||
defaultForwardEmail?: string;
|
||
// Verschlüsselungs-Einstellungen
|
||
imapEncryption?: MailEncryption;
|
||
smtpEncryption?: MailEncryption;
|
||
allowSelfSignedCerts?: boolean;
|
||
// System-E-Mail
|
||
systemEmailAddress?: string;
|
||
systemEmailPassword?: string;
|
||
// UI-Label für Kunden-E-Mail-Adressen (z.B. "Stressfrei-Wechseln", "Meine-Firma")
|
||
customerEmailLabel?: string;
|
||
isActive?: boolean;
|
||
isDefault?: boolean;
|
||
}
|
||
|
||
// Validiert Domain-Format (z.B. stressfrei-wechseln.de, mail.beispiel.com)
|
||
const DOMAIN_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
||
|
||
function validateDomain(domain: string | undefined): string {
|
||
if (!domain || !domain.trim()) {
|
||
throw new Error('Domain ist erforderlich');
|
||
}
|
||
const normalized = domain.trim().toLowerCase();
|
||
if (!DOMAIN_REGEX.test(normalized)) {
|
||
throw new Error(`Ungültige Domain: "${domain}". Format: name.tld (z.B. meine-firma.de)`);
|
||
}
|
||
return normalized;
|
||
}
|
||
|
||
export async function createProviderConfig(data: CreateProviderConfigData) {
|
||
// Domain validieren
|
||
const validatedDomain = validateDomain(data.domain);
|
||
|
||
// Falls isDefault=true, alle anderen auf false setzen
|
||
if (data.isDefault) {
|
||
await prisma.emailProviderConfig.updateMany({
|
||
where: { isDefault: true },
|
||
data: { isDefault: false },
|
||
});
|
||
}
|
||
|
||
// Passwörter verschlüsseln falls vorhanden
|
||
const { encrypt } = await import('../../utils/encryption.js');
|
||
const passwordEncrypted = data.password ? encrypt(data.password) : null;
|
||
const systemEmailPasswordEncrypted = data.systemEmailPassword ? encrypt(data.systemEmailPassword) : null;
|
||
|
||
return prisma.emailProviderConfig.create({
|
||
data: {
|
||
name: data.name,
|
||
type: data.type,
|
||
apiUrl: data.apiUrl,
|
||
apiKey: data.apiKey || null,
|
||
username: data.username || null,
|
||
passwordEncrypted,
|
||
domain: validatedDomain,
|
||
defaultForwardEmail: data.defaultForwardEmail || null,
|
||
imapEncryption: data.imapEncryption ?? 'SSL',
|
||
smtpEncryption: data.smtpEncryption ?? 'SSL',
|
||
allowSelfSignedCerts: data.allowSelfSignedCerts ?? false,
|
||
systemEmailAddress: data.systemEmailAddress || null,
|
||
systemEmailPasswordEncrypted,
|
||
customerEmailLabel: data.customerEmailLabel || null,
|
||
isActive: data.isActive ?? true,
|
||
isDefault: data.isDefault ?? false,
|
||
},
|
||
});
|
||
}
|
||
|
||
export async function updateProviderConfig(
|
||
id: number,
|
||
data: Partial<CreateProviderConfigData>
|
||
) {
|
||
// Falls isDefault=true, alle anderen auf false setzen
|
||
if (data.isDefault) {
|
||
await prisma.emailProviderConfig.updateMany({
|
||
where: { isDefault: true, id: { not: id } },
|
||
data: { isDefault: false },
|
||
});
|
||
}
|
||
|
||
const updateData: Record<string, unknown> = {};
|
||
|
||
if (data.name !== undefined) updateData.name = data.name;
|
||
if (data.type !== undefined) updateData.type = data.type;
|
||
if (data.apiUrl !== undefined) updateData.apiUrl = data.apiUrl;
|
||
if (data.apiKey !== undefined) updateData.apiKey = data.apiKey || null;
|
||
if (data.username !== undefined) updateData.username = data.username || null;
|
||
if (data.domain !== undefined) updateData.domain = validateDomain(data.domain);
|
||
if (data.defaultForwardEmail !== undefined)
|
||
updateData.defaultForwardEmail = data.defaultForwardEmail || null;
|
||
if (data.imapEncryption !== undefined) updateData.imapEncryption = data.imapEncryption;
|
||
if (data.smtpEncryption !== undefined) updateData.smtpEncryption = data.smtpEncryption;
|
||
if (data.allowSelfSignedCerts !== undefined) updateData.allowSelfSignedCerts = data.allowSelfSignedCerts;
|
||
if (data.systemEmailAddress !== undefined) updateData.systemEmailAddress = data.systemEmailAddress || null;
|
||
if (data.customerEmailLabel !== undefined) updateData.customerEmailLabel = data.customerEmailLabel?.trim() || null;
|
||
if (data.isActive !== undefined) updateData.isActive = data.isActive;
|
||
if (data.isDefault !== undefined) updateData.isDefault = data.isDefault;
|
||
|
||
const { encrypt } = await import('../../utils/encryption.js');
|
||
|
||
// Passwort-Logik:
|
||
// - Wenn neues Passwort übergeben → verschlüsseln und speichern
|
||
// - Wenn Benutzername gelöscht wird → Passwort auch löschen (gehören zusammen)
|
||
if (data.password) {
|
||
updateData.passwordEncrypted = encrypt(data.password);
|
||
} else if (data.username !== undefined && !data.username) {
|
||
// Benutzername wird gelöscht → Passwort auch löschen
|
||
updateData.passwordEncrypted = null;
|
||
}
|
||
|
||
// System-E-Mail-Passwort
|
||
if (data.systemEmailPassword) {
|
||
updateData.systemEmailPasswordEncrypted = encrypt(data.systemEmailPassword);
|
||
} else if (data.systemEmailAddress !== undefined && !data.systemEmailAddress) {
|
||
// System-E-Mail wird gelöscht → Passwort auch löschen
|
||
updateData.systemEmailPasswordEncrypted = null;
|
||
}
|
||
|
||
return prisma.emailProviderConfig.update({
|
||
where: { id },
|
||
data: updateData,
|
||
});
|
||
}
|
||
|
||
export async function deleteProviderConfig(id: number) {
|
||
return prisma.emailProviderConfig.delete({
|
||
where: { id },
|
||
});
|
||
}
|
||
|
||
// ==================== EMAIL OPERATIONS ====================
|
||
|
||
// Provider-Instanz aus DB-Config erstellen
|
||
async function getProviderInstance(): Promise<IEmailProvider> {
|
||
const dbConfig = await getActiveProviderConfig();
|
||
|
||
if (!dbConfig) {
|
||
throw new Error('Kein aktiver Email-Provider konfiguriert');
|
||
}
|
||
|
||
// Passwort entschlüsseln
|
||
let password: string | undefined;
|
||
if (dbConfig.passwordEncrypted) {
|
||
try {
|
||
password = decrypt(dbConfig.passwordEncrypted);
|
||
} catch {
|
||
console.error('Konnte Passwort nicht entschlüsseln');
|
||
}
|
||
}
|
||
|
||
const config: EmailProviderConfig = {
|
||
id: dbConfig.id,
|
||
name: dbConfig.name,
|
||
type: dbConfig.type as 'PLESK' | 'CPANEL' | 'DIRECTADMIN',
|
||
apiUrl: dbConfig.apiUrl,
|
||
apiKey: dbConfig.apiKey || undefined,
|
||
username: dbConfig.username || undefined,
|
||
password,
|
||
domain: dbConfig.domain,
|
||
defaultForwardEmail: dbConfig.defaultForwardEmail || undefined,
|
||
imapServer: dbConfig.imapServer || undefined,
|
||
imapPort: dbConfig.imapPort || undefined,
|
||
smtpServer: dbConfig.smtpServer || undefined,
|
||
smtpPort: dbConfig.smtpPort || undefined,
|
||
imapEncryption: dbConfig.imapEncryption as MailEncryption,
|
||
smtpEncryption: dbConfig.smtpEncryption as MailEncryption,
|
||
allowSelfSignedCerts: dbConfig.allowSelfSignedCerts,
|
||
isActive: dbConfig.isActive,
|
||
isDefault: dbConfig.isDefault,
|
||
};
|
||
|
||
return createProvider(config);
|
||
}
|
||
|
||
// Prüfen ob eine E-Mail existiert
|
||
export async function checkEmailExists(localPart: string): Promise<EmailExistsResult> {
|
||
try {
|
||
const provider = await getProviderInstance();
|
||
return provider.emailExists(localPart);
|
||
} catch (error) {
|
||
console.error('checkEmailExists error:', error);
|
||
return { exists: false };
|
||
}
|
||
}
|
||
|
||
// E-Mail erstellen mit Weiterleitungen
|
||
export async function provisionEmail(
|
||
localPart: string,
|
||
customerEmail: string
|
||
): Promise<EmailOperationResult> {
|
||
try {
|
||
const provider = await getProviderInstance();
|
||
const config = await getActiveProviderConfig();
|
||
|
||
// Weiterleitungsziele zusammenstellen
|
||
const forwardTargets: string[] = [customerEmail];
|
||
|
||
// Unsere eigene Weiterleitungsadresse hinzufügen falls konfiguriert
|
||
if (config?.defaultForwardEmail) {
|
||
forwardTargets.push(config.defaultForwardEmail);
|
||
}
|
||
|
||
// Prüfen ob existiert
|
||
const exists = await provider.emailExists(localPart);
|
||
if (exists.exists) {
|
||
return {
|
||
success: true,
|
||
message: `E-Mail ${exists.email} existiert bereits`,
|
||
};
|
||
}
|
||
|
||
// Erstellen
|
||
const result = await provider.createEmail({
|
||
localPart,
|
||
forwardTargets,
|
||
});
|
||
|
||
return result;
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
return {
|
||
success: false,
|
||
error: errorMessage,
|
||
};
|
||
}
|
||
}
|
||
|
||
// E-Mail mit echter Mailbox erstellen (IMAP/SMTP-Zugang)
|
||
export async function provisionEmailWithMailbox(
|
||
localPart: string,
|
||
customerEmail: string,
|
||
password: string
|
||
): Promise<EmailOperationResult & { email?: string }> {
|
||
try {
|
||
const provider = await getProviderInstance();
|
||
const config = await getActiveProviderConfig();
|
||
|
||
// Weiterleitungsziele zusammenstellen
|
||
const forwardTargets: string[] = [customerEmail];
|
||
|
||
// Unsere eigene Weiterleitungsadresse hinzufügen falls konfiguriert
|
||
if (config?.defaultForwardEmail) {
|
||
forwardTargets.push(config.defaultForwardEmail);
|
||
}
|
||
|
||
// Prüfen ob existiert
|
||
const exists = await provider.emailExists(localPart);
|
||
if (exists.exists) {
|
||
return {
|
||
success: true,
|
||
message: `E-Mail ${exists.email} existiert bereits`,
|
||
email: exists.email,
|
||
};
|
||
}
|
||
|
||
// Mit Mailbox erstellen
|
||
const result = await provider.createEmailWithMailbox({
|
||
localPart,
|
||
forwardTargets,
|
||
password,
|
||
});
|
||
|
||
return result;
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
return {
|
||
success: false,
|
||
error: errorMessage,
|
||
};
|
||
}
|
||
}
|
||
|
||
// Mailbox für existierende E-Mail-Weiterleitung aktivieren
|
||
export async function enableMailboxForExistingEmail(
|
||
localPart: string,
|
||
password: string
|
||
): Promise<EmailOperationResult> {
|
||
try {
|
||
const provider = await getProviderInstance();
|
||
|
||
const result = await provider.enableMailboxForExisting({
|
||
localPart,
|
||
password,
|
||
});
|
||
|
||
return result;
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
return {
|
||
success: false,
|
||
error: errorMessage,
|
||
};
|
||
}
|
||
}
|
||
|
||
// Mailbox-Passwort beim Provider aktualisieren
|
||
export async function updateMailboxPassword(
|
||
localPart: string,
|
||
password: string
|
||
): Promise<EmailOperationResult> {
|
||
try {
|
||
const provider = await getProviderInstance();
|
||
|
||
const result = await provider.updateMailboxPassword({
|
||
localPart,
|
||
password,
|
||
});
|
||
|
||
return result;
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
return {
|
||
success: false,
|
||
error: errorMessage,
|
||
};
|
||
}
|
||
}
|
||
|
||
// IMAP/SMTP-Einstellungen vom aktiven Provider holen
|
||
export interface ImapSmtpSettings {
|
||
imapServer: string;
|
||
imapPort: number;
|
||
imapEncryption: MailEncryption; // SSL, STARTTLS oder NONE
|
||
smtpServer: string;
|
||
smtpPort: number;
|
||
smtpEncryption: MailEncryption; // SSL, STARTTLS oder NONE
|
||
allowSelfSignedCerts: boolean; // Selbstsignierte Zertifikate erlauben
|
||
domain: string;
|
||
}
|
||
|
||
export async function getImapSmtpSettings(): Promise<ImapSmtpSettings | null> {
|
||
const config = await getActiveProviderConfig();
|
||
if (!config) return null;
|
||
|
||
// Default-Server: Hostname aus der apiUrl extrahieren (z.B. rs001871.fastrootserver.de aus https://rs001871.fastrootserver.de:8443)
|
||
// Der Plesk-Server ist gleichzeitig der Mail-Server
|
||
let defaultServer: string;
|
||
try {
|
||
const url = new URL(config.apiUrl);
|
||
defaultServer = url.hostname;
|
||
} catch {
|
||
// Fallback falls apiUrl ungültig
|
||
defaultServer = `mail.${config.domain}`;
|
||
}
|
||
|
||
// Verschlüsselungs-Einstellungen
|
||
const imapEncryption = (config.imapEncryption ?? 'SSL') as MailEncryption;
|
||
const smtpEncryption = (config.smtpEncryption ?? 'SSL') as MailEncryption;
|
||
|
||
// Ports basierend auf Verschlüsselung berechnen:
|
||
// SSL: IMAP 993, SMTP 465
|
||
// STARTTLS: IMAP 143, SMTP 587
|
||
// NONE: IMAP 143, SMTP 25
|
||
//
|
||
// Standard-Ports werden IMMER basierend auf Verschlüsselung berechnet.
|
||
// Nur benutzerdefinierte Ports (nicht 993/143/465/587/25) werden aus der DB übernommen.
|
||
const getImapPort = (enc: MailEncryption, storedPort: number | null) => {
|
||
const standardPorts = [993, 143];
|
||
// Wenn ein nicht-standard Port gespeichert ist, diesen verwenden
|
||
if (storedPort && !standardPorts.includes(storedPort)) {
|
||
return storedPort;
|
||
}
|
||
// Sonst basierend auf Verschlüsselung
|
||
return enc === 'SSL' ? 993 : 143;
|
||
};
|
||
|
||
const getSmtpPort = (enc: MailEncryption, storedPort: number | null) => {
|
||
const standardPorts = [465, 587, 25];
|
||
// Wenn ein nicht-standard Port gespeichert ist, diesen verwenden
|
||
if (storedPort && !standardPorts.includes(storedPort)) {
|
||
return storedPort;
|
||
}
|
||
// Sonst basierend auf Verschlüsselung
|
||
if (enc === 'SSL') return 465;
|
||
if (enc === 'STARTTLS') return 587;
|
||
return 25; // NONE
|
||
};
|
||
|
||
return {
|
||
imapServer: config.imapServer || defaultServer,
|
||
imapPort: getImapPort(imapEncryption, config.imapPort),
|
||
imapEncryption,
|
||
smtpServer: config.smtpServer || defaultServer,
|
||
smtpPort: getSmtpPort(smtpEncryption, config.smtpPort),
|
||
smtpEncryption,
|
||
allowSelfSignedCerts: config.allowSelfSignedCerts ?? false,
|
||
domain: config.domain,
|
||
};
|
||
}
|
||
|
||
// E-Mail löschen
|
||
export async function deprovisionEmail(localPart: string): Promise<EmailOperationResult> {
|
||
try {
|
||
const provider = await getProviderInstance();
|
||
return provider.deleteEmail(localPart);
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
return {
|
||
success: false,
|
||
error: errorMessage,
|
||
};
|
||
}
|
||
}
|
||
|
||
// Weiterleitungsziele ersetzen (set:, nicht add:) – nutzen wir, um nach einer
|
||
// Kunden-Email-Änderung die Forwards einer Stressfrei-Adresse auf den neuen
|
||
// Kunden-Inbox + unsere Service-Adresse zu setzen.
|
||
export async function setEmailForwardTargets(
|
||
localPart: string,
|
||
targets: string[],
|
||
): Promise<EmailOperationResult> {
|
||
try {
|
||
const provider = await getProviderInstance();
|
||
return provider.updateForwardTargets(localPart, targets);
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
return { success: false, error: errorMessage };
|
||
}
|
||
}
|
||
|
||
// E-Mail umbenennen
|
||
export async function renameProvisionedEmail(
|
||
oldLocalPart: string,
|
||
newLocalPart: string
|
||
): Promise<EmailOperationResult> {
|
||
try {
|
||
const provider = await getProviderInstance();
|
||
return provider.renameEmail({ oldLocalPart, newLocalPart });
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
return {
|
||
success: false,
|
||
error: errorMessage,
|
||
};
|
||
}
|
||
}
|
||
|
||
// Domain aus aktivem Provider holen
|
||
export async function getProviderDomain(): Promise<string | null> {
|
||
const config = await getActiveProviderConfig();
|
||
return config?.domain || null;
|
||
}
|
||
|
||
/**
|
||
* Label aus der Domain ableiten, z.B. "stressfrei-wechseln.de" → "Stressfrei-Wechseln".
|
||
* Nimmt den Hauptteil bis zum ersten Punkt, trennt an "-" und kapitalisiert jeden Teil.
|
||
*/
|
||
export function deriveLabelFromDomain(domain: string | null | undefined): string {
|
||
if (!domain) return 'Kunden-E-Mail';
|
||
const mainPart = domain.split('.')[0] || domain;
|
||
return mainPart
|
||
.split('-')
|
||
.map((s) => (s.length === 0 ? '' : s.charAt(0).toUpperCase() + s.slice(1)))
|
||
.join('-');
|
||
}
|
||
|
||
/**
|
||
* Öffentliche Provider-Einstellungen (Domain + Label) für UI.
|
||
* Kein auth-geschütztes Geheimnis, nur damit die Frontend-Labels stimmen.
|
||
*/
|
||
export async function getProviderPublicSettings(): Promise<{
|
||
domain: string | null;
|
||
customerEmailLabel: string;
|
||
customerEmailLabelIsCustom: boolean;
|
||
}> {
|
||
const config = await getActiveProviderConfig();
|
||
const domain = config?.domain ?? null;
|
||
const customLabel = config?.customerEmailLabel?.trim();
|
||
|
||
return {
|
||
domain,
|
||
customerEmailLabel: customLabel && customLabel.length > 0 ? customLabel : deriveLabelFromDomain(domain),
|
||
customerEmailLabelIsCustom: !!(customLabel && customLabel.length > 0),
|
||
};
|
||
}
|
||
|
||
// Provider-Instanz aus übergebener Config erstellen (für Tests mit ungespeicherten Daten)
|
||
function createProviderFromFormData(data: {
|
||
type: 'PLESK' | 'CPANEL' | 'DIRECTADMIN';
|
||
apiUrl: string;
|
||
apiKey?: string;
|
||
username?: string;
|
||
password?: string;
|
||
domain: string;
|
||
}): IEmailProvider {
|
||
const config: EmailProviderConfig = {
|
||
id: 0,
|
||
name: 'Test',
|
||
type: data.type,
|
||
apiUrl: data.apiUrl,
|
||
apiKey: data.apiKey,
|
||
username: data.username,
|
||
password: data.password,
|
||
domain: data.domain,
|
||
isActive: true,
|
||
isDefault: false,
|
||
};
|
||
return createProvider(config);
|
||
}
|
||
|
||
// Provider-Instanz aus DB-Config per ID erstellen
|
||
async function getProviderInstanceById(id: number): Promise<IEmailProvider> {
|
||
const dbConfig = await getProviderConfigById(id);
|
||
|
||
if (!dbConfig) {
|
||
throw new Error('Email-Provider nicht gefunden');
|
||
}
|
||
|
||
// Passwort entschlüsseln
|
||
let password: string | undefined;
|
||
if (dbConfig.passwordEncrypted) {
|
||
try {
|
||
password = decrypt(dbConfig.passwordEncrypted);
|
||
} catch {
|
||
console.error('Konnte Passwort nicht entschlüsseln');
|
||
}
|
||
}
|
||
|
||
const config: EmailProviderConfig = {
|
||
id: dbConfig.id,
|
||
name: dbConfig.name,
|
||
type: dbConfig.type as 'PLESK' | 'CPANEL' | 'DIRECTADMIN',
|
||
apiUrl: dbConfig.apiUrl,
|
||
apiKey: dbConfig.apiKey || undefined,
|
||
username: dbConfig.username || undefined,
|
||
password,
|
||
domain: dbConfig.domain,
|
||
defaultForwardEmail: dbConfig.defaultForwardEmail || undefined,
|
||
imapServer: dbConfig.imapServer || undefined,
|
||
imapPort: dbConfig.imapPort || undefined,
|
||
smtpServer: dbConfig.smtpServer || undefined,
|
||
smtpPort: dbConfig.smtpPort || undefined,
|
||
imapEncryption: dbConfig.imapEncryption as MailEncryption,
|
||
smtpEncryption: dbConfig.smtpEncryption as MailEncryption,
|
||
allowSelfSignedCerts: dbConfig.allowSelfSignedCerts,
|
||
isActive: dbConfig.isActive,
|
||
isDefault: dbConfig.isDefault,
|
||
};
|
||
|
||
return createProvider(config);
|
||
}
|
||
|
||
// Provider-Verbindung testen (mit ID, Formulardaten oder Default-Provider)
|
||
export async function testProviderConnection(options?: {
|
||
id?: number;
|
||
testData?: {
|
||
type: 'PLESK' | 'CPANEL' | 'DIRECTADMIN';
|
||
apiUrl: string;
|
||
apiKey?: string;
|
||
username?: string;
|
||
password?: string;
|
||
domain: string;
|
||
};
|
||
}): Promise<EmailOperationResult> {
|
||
try {
|
||
let provider: IEmailProvider;
|
||
|
||
if (options?.testData) {
|
||
// Mit übergebenen Daten testen (z.B. aus Modal beim Neuanlegen)
|
||
provider = createProviderFromFormData(options.testData);
|
||
} else if (options?.id) {
|
||
// Gespeicherten Provider per ID testen
|
||
provider = await getProviderInstanceById(options.id);
|
||
} else {
|
||
// Default-Provider testen
|
||
provider = await getProviderInstance();
|
||
}
|
||
|
||
// Expliziter Verbindungstest (wirft Fehler bei Auth-Problemen)
|
||
await provider.testConnection();
|
||
|
||
return {
|
||
success: true,
|
||
message: 'Verbindung zum Email-Provider erfolgreich',
|
||
};
|
||
} catch (error) {
|
||
const errorMessage = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||
return {
|
||
success: false,
|
||
error: errorMessage,
|
||
};
|
||
}
|
||
}
|
||
|
||
// ==================== SYSTEM EMAIL ====================
|
||
|
||
export interface SystemEmailCredentials {
|
||
emailAddress: string;
|
||
password: string;
|
||
smtpServer: string;
|
||
smtpPort: number;
|
||
smtpEncryption: MailEncryption;
|
||
allowSelfSignedCerts: boolean;
|
||
}
|
||
|
||
/**
|
||
* System-E-Mail-Credentials vom aktiven Provider holen.
|
||
* Wird für automatisierten Versand (DSGVO, Benachrichtigungen etc.) verwendet.
|
||
*/
|
||
export async function getSystemEmailCredentials(): Promise<SystemEmailCredentials | null> {
|
||
const config = await getActiveProviderConfig();
|
||
if (!config?.systemEmailAddress || !config?.systemEmailPasswordEncrypted) {
|
||
return null;
|
||
}
|
||
|
||
let password: string;
|
||
try {
|
||
password = decrypt(config.systemEmailPasswordEncrypted);
|
||
} catch {
|
||
console.error('System-E-Mail-Passwort konnte nicht entschlüsselt werden');
|
||
return null;
|
||
}
|
||
|
||
const settings = await getImapSmtpSettings();
|
||
if (!settings) return null;
|
||
|
||
return {
|
||
emailAddress: config.systemEmailAddress,
|
||
password,
|
||
smtpServer: settings.smtpServer,
|
||
smtpPort: settings.smtpPort,
|
||
smtpEncryption: settings.smtpEncryption,
|
||
allowSelfSignedCerts: settings.allowSelfSignedCerts,
|
||
};
|
||
}
|