IMAP-Fehler: zentraler Humanizer + Auth-Fehler explizit

Der User sah beim Postfach-Sync stumpf "Command failed" – realer
Grund war ein abgelaufenes Postfach-Passwort. Der Bug betraf
Anhang-Download UND Sync und war der gleiche wie 2 Commits zuvor:
imapflow wirft `new Error('Command failed')` und legt Details in
`.responseText`/`.responseStatus` ab, wir haben sie nirgends
gelesen.

Fix: humanizeImapError() als zentraler Helper in imapService:
- Extrahiert responseText/responseStatus aus imapflow-Errors
- Erkennt Auth-Fehler ("authentication failed", "invalid
  credentials", NO+auth) und liefert klare Meldung mit
  Handlungsanweisung: "Passwort stimmt nicht mehr, bitte
  Zugangsdaten synchronisieren".
- Deckt zusätzlich Netzwerk/TLS/Timeout/UID-Stale ab.

Angewendet auf:
- fetchAttachmentInner (Anhang-Fetch)
- downloadAttachment-Controller (Response an UI)
- syncEmailsForAccount (Sync-Ergebnis + Toast)

Damit sieht der User im Toast künftig statt "Command failed" die
tatsächliche Ursache – gleicher Mechanismus für Sync und
Anhang-Download.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 15:28:43 +02:00
co-authored by Claude Opus 4.7
parent be19e60a1b
commit f2703ed6b7
3 changed files with 77 additions and 37 deletions
@@ -5,7 +5,7 @@ import * as cachedEmailService from '../services/cachedEmail.service.js';
import * as stressfreiEmailService from '../services/stressfreiEmail.service.js';
import * as invoiceService from '../services/invoice.service.js';
import { sendEmail, SmtpCredentials, SendEmailParams, EmailAttachment } from '../services/smtpService.js';
import { fetchAttachment, appendToSent, ImapCredentials } from '../services/imapService.js';
import { fetchAttachment, appendToSent, ImapCredentials, humanizeImapError } from '../services/imapService.js';
import { getImapSmtpSettings } from '../services/emailProvider/emailProviderService.js';
import { decrypt } from '../utils/encryption.js';
import { sanitizeNotes, stripHtml, validateContractDocumentType, validateOptionalIsoDate, assertSafePdf } from '../utils/sanitize.js';
@@ -797,33 +797,9 @@ export async function downloadAttachment(req: AuthRequest, res: Response): Promi
res.send(attachment.content);
} catch (error) {
console.error('downloadAttachment error:', error);
// imapflow versteckt die eigentliche Server-Antwort in
// `.responseText`/`.responseStatus` und setzt `.message` immer nur auf
// `'Command failed'`. Wenn wir nur die `.message` ausgeben, sieht der
// Endnutzer im UI nichts Verwertbares.
const anyErr = error as { message?: string; responseText?: string; responseStatus?: string };
const rawMsg = anyErr?.responseText
? `${anyErr.responseStatus ? anyErr.responseStatus + ' ' : ''}${anyErr.responseText}`
: 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.';
} else if (lower.includes('no longer exist') || lower.includes('no such message') || lower.includes('unknown')) {
friendly = 'Nachricht existiert im IMAP-Postfach nicht mehr (wahrscheinlich verschoben oder gelöscht). Bitte E-Mail-Liste neu synchronisieren.';
}
res.status(500).json({
success: false,
error: `Fehler beim Herunterladen des Anhangs: ${friendly}`,
error: `Fehler beim Herunterladen des Anhangs: ${humanizeImapError(error)}`,
} as ApiResponse);
}
}