// ==================== EMAIL PROVIDER SERVICE ==================== import { PrismaClient } from '@prisma/client'; import { decrypt } from '../../utils/encryption.js'; import { IEmailProvider, EmailProviderConfig, EmailExistsResult, EmailOperationResult, CreateEmailParams, MailEncryption, } from './types.js'; import { PleskEmailProvider } from './pleskProvider.js'; const prisma = new PrismaClient(); // 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; isActive?: boolean; isDefault?: boolean; } export async function createProviderConfig(data: CreateProviderConfigData) { // Falls isDefault=true, alle anderen auf false setzen if (data.isDefault) { await prisma.emailProviderConfig.updateMany({ where: { isDefault: true }, data: { isDefault: false }, }); } // Passwort verschlüsseln falls vorhanden const { encrypt } = await import('../../utils/encryption.js'); const passwordEncrypted = data.password ? encrypt(data.password) : 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: data.domain, defaultForwardEmail: data.defaultForwardEmail || null, imapEncryption: data.imapEncryption ?? 'SSL', smtpEncryption: data.smtpEncryption ?? 'SSL', allowSelfSignedCerts: data.allowSelfSignedCerts ?? false, isActive: data.isActive ?? true, isDefault: data.isDefault ?? false, }, }); } export async function updateProviderConfig( id: number, data: Partial ) { // 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 = {}; 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 = 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.isActive !== undefined) updateData.isActive = data.isActive; if (data.isDefault !== undefined) updateData.isDefault = data.isDefault; // 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) { const { encrypt } = await import('../../utils/encryption.js'); updateData.passwordEncrypted = encrypt(data.password); } else if (data.username !== undefined && !data.username) { // Benutzername wird gelöscht → Passwort auch löschen updateData.passwordEncrypted = 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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, }; } } // E-Mail umbenennen export async function renameProvisionedEmail( oldLocalPart: string, newLocalPart: string ): Promise { 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 { const config = await getActiveProviderConfig(); return config?.domain || null; } // 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 { 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 { 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, }; } }