- imap-providers: IONOS/1&1/1blu, msn.com, magenta.de, yahoo.co.uk, ymail.com, tutanota hinzugefügt - detectImapProviderAsync: MX-Lookup-Fallback für Custom-Domains (IONOS kundenserver.de/ionos.de Pattern) - connect.post.ts: nutzt jetzt detectImapProviderAsync statt sync-Variante - ConnectMailSheet: rohe Server-Errors werden via humanizeMailError() + t() übersetzt - useMailConnect: IONOS/t-online/freenet Domains in Client-Side-Detection ergänzt - Locale de/en: provider_other, app_password_guide_other, host_unreachable, unknown Text präzisiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
import { ImapFlow } from "imapflow";
|
|
import { getProfile } from "../../db/profile";
|
|
import { getPlanLimits } from "../../utils/plan-features";
|
|
import { countMailConnections, upsertMailConnection } from "../../db/mail";
|
|
import { detectImapProviderAsync } from "../../utils/imap-providers";
|
|
|
|
/**
|
|
* POST /api/mail/connect
|
|
* Body: { email, password }
|
|
* Testet IMAP-Verbindung und speichert Credentials verschlüsselt.
|
|
*/
|
|
export default defineEventHandler(async (event) => {
|
|
const user = await requireUser(event);
|
|
const {
|
|
email,
|
|
password,
|
|
// Custom-IMAP-Felder (optional, nur wenn User eigenen Server konfiguriert)
|
|
imapHost: customImapHost,
|
|
imapPort: customImapPort,
|
|
useTls,
|
|
rejectUnauthorized,
|
|
} = await readBody(event);
|
|
|
|
if (!email || !password) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
message: "Email und Passwort erforderlich",
|
|
});
|
|
}
|
|
|
|
// Plan-Limit prüfen
|
|
const profile = await getProfile(user.id);
|
|
const limits = getPlanLimits(profile?.plan ?? "free");
|
|
|
|
if (limits.mailAgents !== Infinity) {
|
|
const count = await countMailConnections(user.id);
|
|
if (count >= limits.mailAgents) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
message: `Dein Plan erlaubt maximal ${limits.mailAgents} Mail-Agent${limits.mailAgents !== 1 ? "en" : ""}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Custom-IMAP: wenn imapHost explizit gesetzt → Provider-Detection überspringen.
|
|
// Sonst: automatisch via Email-Domain erkennen (inkl. MX-Lookup-Fallback für Custom-Domains).
|
|
const provider = await detectImapProviderAsync(email);
|
|
const resolvedHost = customImapHost?.trim() || provider.host;
|
|
const resolvedPort = customImapPort ?? provider.port;
|
|
|
|
// TLS-Konfiguration ableiten
|
|
// useTls=false → STARTTLS (secure=false, requireTLS=true bei ImapFlow)
|
|
// useTls=true oder nicht gesetzt → implicit TLS (secure=true)
|
|
const useImplicitTls = useTls !== false; // default: true
|
|
const tlsRejectUnauthorized = rejectUnauthorized !== false; // default: true
|
|
// STARTTLS nur wenn explizit angefordert (useTls === false)
|
|
const useStarttls = useTls === false;
|
|
|
|
// IMAP-Verbindung testen
|
|
const client = new ImapFlow({
|
|
host: resolvedHost,
|
|
port: resolvedPort,
|
|
secure: useImplicitTls,
|
|
...(useStarttls ? { requireTLS: true } : {}),
|
|
auth: { user: email, pass: password },
|
|
logger: false,
|
|
tls: { rejectUnauthorized: tlsRejectUnauthorized },
|
|
});
|
|
|
|
try {
|
|
await client.connect();
|
|
await client.logout();
|
|
} catch (err: any) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
message: `Verbindung fehlgeschlagen: ${err.message ?? "Ungültige Zugangsdaten"}`,
|
|
});
|
|
}
|
|
|
|
// Credentials verschlüsselt speichern
|
|
await upsertMailConnection({
|
|
userId: user.id,
|
|
email,
|
|
provider: "imap",
|
|
// Bei Custom-Host: Host als providerName, sonst auto-erkannter Name
|
|
providerName: customImapHost ? resolvedHost : provider.name,
|
|
imapHost: resolvedHost,
|
|
imapPort: resolvedPort,
|
|
passwordEncrypted: encrypt(password),
|
|
rejectUnauthorized: tlsRejectUnauthorized,
|
|
useStarttls,
|
|
});
|
|
|
|
return {
|
|
connected: true,
|
|
email,
|
|
provider: customImapHost ? resolvedHost : provider.name,
|
|
custom: !!customImapHost,
|
|
};
|
|
});
|