E-Mail-Zugang Test (IMAP + SMTP) in Provider-Einstellungen
Das bestehende „Verbindung testen" prüft nur den API-Zugang (Plesk/cPanel), nicht den eigentlichen IMAP/SMTP-Zugang der System-E-Mail. Das führte dazu, dass Anhang-Downloads scheiterten obwohl der API-Test grün war. Neuer Button im EmailProviders-Modal: „E-Mail-Zugang testen (IMAP + SMTP)" - Testet IMAP-Empfang und SMTP-Versand separat - Zeigt pro Protokoll Erfolg oder Fehlermeldung mit Server/Port/Verschlüsselung - Nutzt die hinterlegte System-E-Mail-Adresse + Passwort - Funktioniert auch vor dem ersten Speichern (mit Formulardaten) Außerdem im Anhang-Download: - Retry-Mechanismus bei transienten TLS/Netzwerk-Fehlern (3 Versuche) - Socket-Timeout 30s gegen hängende Verbindungen - Sprechende Fehlermeldungen (z.B. Hinweis auf selbstsigniertes Zertifikat) - Debug-Logging mit Host/Port/User/Folder/UID Backend: - Neuer Endpoint POST /api/email-providers/test-mail-access - fetchAttachment in imapService: Retry-Wrapper + fetchAttachmentInner - Besseres Error-Handling in downloadAttachment (Cert-Hinweis, Auth, Timeout) Frontend: - emailProviderApi.testMailAccess() - EmailProviders-Modal: neuer Button + zweispaltige Ergebnis-Anzeige für IMAP+SMTP Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -508,9 +508,24 @@ export async function downloadAttachment(req: Request, res: Response): Promise<v
|
||||
res.send(attachment.content);
|
||||
} catch (error) {
|
||||
console.error('downloadAttachment error:', error);
|
||||
const rawMsg = error instanceof Error ? error.message : 'Unbekannter Fehler';
|
||||
const lower = rawMsg.toLowerCase();
|
||||
|
||||
let friendly = rawMsg;
|
||||
if (lower.includes('socket disconnected') && lower.includes('tls')) {
|
||||
friendly =
|
||||
'IMAP-Server hat die TLS-Verbindung abgelehnt. Mögliche Ursache: selbstsigniertes Zertifikat. Bitte in den E-Mail-Provider-Einstellungen "Selbstsignierte Zertifikate erlauben" aktivieren.';
|
||||
} else if (lower.includes('econnrefused')) {
|
||||
friendly = 'IMAP-Server ist nicht erreichbar (Verbindung verweigert). Bitte Server/Port prüfen.';
|
||||
} else if (lower.includes('etimedout')) {
|
||||
friendly = 'Zeitüberschreitung beim Verbinden zum IMAP-Server. Bitte später erneut versuchen.';
|
||||
} else if (lower.includes('authentication') || lower.includes('auth')) {
|
||||
friendly = 'IMAP-Authentifizierung fehlgeschlagen. Bitte Zugangsdaten prüfen.';
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Fehler beim Herunterladen des Anhangs',
|
||||
error: `Fehler beim Herunterladen des Anhangs: ${friendly}`,
|
||||
} as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,12 @@ import { Request, Response } from 'express';
|
||||
import * as emailProviderService from '../services/emailProvider/emailProviderService.js';
|
||||
import { logChange } from '../services/audit.service.js';
|
||||
import { ApiResponse } from '../types/index.js';
|
||||
import { testImapConnection, ImapCredentials } from '../services/imapService.js';
|
||||
import { testSmtpConnection, SmtpCredentials } from '../services/smtpService.js';
|
||||
import { decrypt } from '../utils/encryption.js';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// ==================== CONFIG CRUD ====================
|
||||
|
||||
@@ -122,6 +128,156 @@ export async function testConnection(req: Request, res: Response): Promise<void>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Testet IMAP + SMTP-Zugang für die System-E-Mail eines Providers.
|
||||
* - Option A: Provider-ID + optional überschreibendes Passwort aus Body (Modal)
|
||||
* - Option B: Testdaten komplett aus Body (beim Anlegen, noch nicht gespeichert)
|
||||
*/
|
||||
export async function testMailAccess(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const id = req.body?.id ? parseInt(req.body.id) : undefined;
|
||||
const bodyEmail = typeof req.body?.systemEmailAddress === 'string' ? req.body.systemEmailAddress : undefined;
|
||||
const bodyPassword = typeof req.body?.systemEmailPassword === 'string' ? req.body.systemEmailPassword : undefined;
|
||||
|
||||
let emailAddress: string | undefined;
|
||||
let password: string | undefined;
|
||||
let smtpServer: string;
|
||||
let smtpPort: number;
|
||||
let imapServer: string;
|
||||
let imapPort: number;
|
||||
let smtpEncryption: 'SSL' | 'STARTTLS' | 'NONE';
|
||||
let imapEncryption: 'SSL' | 'STARTTLS' | 'NONE';
|
||||
let allowSelfSignedCerts: boolean;
|
||||
|
||||
if (id) {
|
||||
// Gespeicherten Provider laden
|
||||
const config = await prisma.emailProviderConfig.findUnique({ where: { id } });
|
||||
if (!config) {
|
||||
res.status(404).json({ success: false, error: 'Provider nicht gefunden' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
emailAddress = bodyEmail || config.systemEmailAddress || undefined;
|
||||
if (bodyPassword) {
|
||||
password = bodyPassword;
|
||||
} else if (config.systemEmailPasswordEncrypted) {
|
||||
try {
|
||||
password = decrypt(config.systemEmailPasswordEncrypted);
|
||||
} catch {
|
||||
password = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// IMAP/SMTP-Settings vom Provider ableiten
|
||||
const settings = await emailProviderService.getImapSmtpSettings();
|
||||
if (!settings) {
|
||||
res.status(400).json({ success: false, error: 'Keine IMAP/SMTP-Einstellungen verfügbar' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
smtpServer = settings.smtpServer;
|
||||
smtpPort = settings.smtpPort;
|
||||
imapServer = settings.imapServer;
|
||||
imapPort = settings.imapPort;
|
||||
smtpEncryption = settings.smtpEncryption;
|
||||
imapEncryption = settings.imapEncryption;
|
||||
allowSelfSignedCerts = settings.allowSelfSignedCerts;
|
||||
} else if (req.body?.apiUrl) {
|
||||
// Formulardaten ohne gespeicherten Provider
|
||||
emailAddress = bodyEmail;
|
||||
password = bodyPassword;
|
||||
|
||||
try {
|
||||
const url = new URL(req.body.apiUrl);
|
||||
smtpServer = url.hostname;
|
||||
imapServer = url.hostname;
|
||||
} catch {
|
||||
smtpServer = `mail.${req.body.domain || ''}`;
|
||||
imapServer = smtpServer;
|
||||
}
|
||||
|
||||
imapEncryption = (req.body.imapEncryption || 'SSL') as 'SSL' | 'STARTTLS' | 'NONE';
|
||||
smtpEncryption = (req.body.smtpEncryption || 'SSL') as 'SSL' | 'STARTTLS' | 'NONE';
|
||||
allowSelfSignedCerts = !!req.body.allowSelfSignedCerts;
|
||||
|
||||
imapPort = imapEncryption === 'SSL' ? 993 : 143;
|
||||
smtpPort = smtpEncryption === 'SSL' ? 465 : smtpEncryption === 'STARTTLS' ? 587 : 25;
|
||||
} else {
|
||||
res.status(400).json({ success: false, error: 'Provider-ID oder Testdaten erforderlich' } as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emailAddress || !password) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'System-E-Mail-Adresse und Passwort sind erforderlich',
|
||||
} as ApiResponse);
|
||||
return;
|
||||
}
|
||||
|
||||
// IMAP testen
|
||||
const imapCredentials: ImapCredentials = {
|
||||
host: imapServer,
|
||||
port: imapPort,
|
||||
user: emailAddress,
|
||||
password,
|
||||
encryption: imapEncryption,
|
||||
allowSelfSignedCerts,
|
||||
};
|
||||
|
||||
// SMTP testen
|
||||
const smtpCredentials: SmtpCredentials = {
|
||||
host: smtpServer,
|
||||
port: smtpPort,
|
||||
user: emailAddress,
|
||||
password,
|
||||
encryption: smtpEncryption,
|
||||
allowSelfSignedCerts,
|
||||
};
|
||||
|
||||
let imapResult: { success: boolean; error?: string } = { success: false };
|
||||
let smtpResult: { success: boolean; error?: string } = { success: false };
|
||||
|
||||
try {
|
||||
await testImapConnection(imapCredentials);
|
||||
imapResult = { success: true };
|
||||
} catch (e) {
|
||||
imapResult = { success: false, error: e instanceof Error ? e.message : 'Unbekannter Fehler' };
|
||||
}
|
||||
|
||||
try {
|
||||
await testSmtpConnection(smtpCredentials);
|
||||
smtpResult = { success: true };
|
||||
} catch (e) {
|
||||
smtpResult = { success: false, error: e instanceof Error ? e.message : 'Unbekannter Fehler' };
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: imapResult.success && smtpResult.success,
|
||||
data: {
|
||||
imap: {
|
||||
...imapResult,
|
||||
server: imapServer,
|
||||
port: imapPort,
|
||||
encryption: imapEncryption,
|
||||
},
|
||||
smtp: {
|
||||
...smtpResult,
|
||||
server: smtpServer,
|
||||
port: smtpPort,
|
||||
encryption: smtpEncryption,
|
||||
},
|
||||
user: emailAddress,
|
||||
},
|
||||
} as ApiResponse);
|
||||
} catch (error) {
|
||||
console.error('testMailAccess error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Fehler beim Test',
|
||||
} as ApiResponse);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkEmailExists(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { localPart } = req.params;
|
||||
|
||||
Reference in New Issue
Block a user