feat(mail): IONOS-Detection + MX-Lookup-Fallback + humanisierte Error-Messages
- 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>
This commit is contained in:
parent
6962e09403
commit
518510c088
@ -12,6 +12,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMailConnect, detectProvider, type MailProvider } from '../../hooks/useMailConnect';
|
||||
import { humanizeMailError } from '../../lib/mailErrors';
|
||||
import { useColors } from '../../lib/theme';
|
||||
import { KeyboardAwareSheet } from '../KeyboardAwareSheet';
|
||||
|
||||
@ -134,16 +135,14 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) {
|
||||
|
||||
const body: Parameters<typeof connect>[0] = { email: email.trim(), password };
|
||||
|
||||
// Für "other" Provider: User muss imapHost selbst eingeben — aktuell nicht
|
||||
// unterstützt in dieser Sheet-Version. Custom-IMAP bleibt TODO für Phase 11.
|
||||
// Provider-Detection passiert server-seitig via Email-Domain.
|
||||
|
||||
const result = await connect(body);
|
||||
if (result.ok) {
|
||||
handleClose();
|
||||
onSuccess();
|
||||
} else {
|
||||
setFormError(result.error ?? t('mail.connect_failed'));
|
||||
// Rohen Server-Error durch den humanen Mapper leiten.
|
||||
// humanizeMailError klassifiziert IMAP-Fehlertexte → i18n-Key → t() → lesbarer Satz.
|
||||
setFormError(t(humanizeMailError(result.error)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,7 +205,7 @@ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) {
|
||||
onPasswordChange={(v) => { setPassword(v); setFormError(null); }}
|
||||
passwordVisible={passwordVisible}
|
||||
onTogglePasswordVisible={() => setPasswordVisible((p) => !p)}
|
||||
error={formError ?? connectError}
|
||||
error={formError ?? (connectError ? t(humanizeMailError(connectError)) : null)}
|
||||
connecting={connecting}
|
||||
onConnect={handleConnect}
|
||||
insets={insets}
|
||||
|
||||
@ -37,24 +37,40 @@ export type UseMailConnectReturn = {
|
||||
};
|
||||
|
||||
const PROVIDER_DOMAIN_MAP: Record<string, MailProvider> = {
|
||||
// Google
|
||||
'gmail.com': 'gmail',
|
||||
'googlemail.com': 'gmail',
|
||||
// Apple
|
||||
'icloud.com': 'icloud',
|
||||
'me.com': 'icloud',
|
||||
'mac.com': 'icloud',
|
||||
// Microsoft
|
||||
'outlook.com': 'outlook',
|
||||
'hotmail.com': 'outlook',
|
||||
'hotmail.de': 'outlook',
|
||||
'live.com': 'outlook',
|
||||
'live.de': 'outlook',
|
||||
'msn.com': 'outlook',
|
||||
// Yahoo
|
||||
'yahoo.com': 'yahoo',
|
||||
'yahoo.de': 'yahoo',
|
||||
'yahoo.co.uk': 'yahoo',
|
||||
'ymail.com': 'yahoo',
|
||||
// GMX / Web.de
|
||||
'gmx.de': 'gmx',
|
||||
'gmx.net': 'gmx',
|
||||
'gmx.at': 'gmx',
|
||||
'gmx.ch': 'gmx',
|
||||
'web.de': 'gmx',
|
||||
// IONOS / 1&1 → als 'other' behandeln (Server-seitig korrekt erkannt)
|
||||
'ionos.de': 'other',
|
||||
'1und1.de': 'other',
|
||||
'1and1.com': 'other',
|
||||
'1and1.de': 'other',
|
||||
'1blu.de': 'other',
|
||||
// Telekom + Freenet → 'other' (kein eigenes Tile, aber Server kennt sie)
|
||||
't-online.de': 'other',
|
||||
'freenet.de': 'other',
|
||||
};
|
||||
|
||||
export function detectProvider(email: string): MailProvider {
|
||||
|
||||
@ -311,7 +311,7 @@
|
||||
"privacy_2": "Kein Zugriff auf Mail-Inhalte",
|
||||
"privacy_3": "DSGVO-konform, Server in DE",
|
||||
"providers_title": "Unterstützte Anbieter",
|
||||
"provider_other": "Andere",
|
||||
"provider_other": "Weitere Anbieter",
|
||||
"empty_title": "Noch keine Mails blockiert",
|
||||
"empty_subtitle": "Verbinde dein Postfach, damit Rebreak automatisch schützt.",
|
||||
"connect_sheet_title": "Postfach verbinden",
|
||||
@ -327,7 +327,7 @@
|
||||
"app_password_guide_outlook": "Outlook mit Microsoft-Konto: Aktiviere 2FA und erstelle ein App-Passwort unter account.microsoft.com/security.",
|
||||
"app_password_guide_yahoo": "Yahoo erfordert ein App-Passwort. Aktiviere 2FA und erstelle es unter login.yahoo.com/account/security.",
|
||||
"app_password_guide_gmx": "GMX / Web.de: Aktiviere IMAP in den Einstellungen und verwende dein normales Passwort oder ein App-Passwort falls 2FA aktiv.",
|
||||
"app_password_guide_other": "Gib die IMAP-Zugangsdaten deines E-Mail-Anbieters ein. App-Passwort empfohlen wenn vorhanden.",
|
||||
"app_password_guide_other": "Gib deine E-Mail-Adresse und dein App-Passwort ein. Rebreak erkennt deinen Anbieter automatisch anhand der Domain (z.B. IONOS, Strato, 1&1). App-Passwort empfohlen falls vorhanden.",
|
||||
"app_password_open_link": "Jetzt App-Passwort erstellen",
|
||||
"form_email_label": "E-Mail-Adresse",
|
||||
"form_email_placeholder": "deine@email.de",
|
||||
@ -402,10 +402,10 @@
|
||||
"auth_failed": "Das App-Passwort ist nicht korrekt. Bitte erneuere es bei deinem Mail-Anbieter und trage es hier ein.",
|
||||
"app_password_required": "Dein Mail-Anbieter verlangt ein App-spezifisches Passwort. Erstelle eines in den Account-Einstellungen.",
|
||||
"connection_failed": "Verbindung zum Mail-Server fehlgeschlagen. Bitte später erneut versuchen.",
|
||||
"host_unreachable": "Mail-Server ist gerade nicht erreichbar. Internet-Verbindung prüfen oder später erneut versuchen.",
|
||||
"host_unreachable": "Mail-Server nicht erreichbar. Internet-Verbindung prüfen — oder dein Anbieter wird noch nicht unterstützt. Schreib uns: support@rebreak.org",
|
||||
"tls_error": "Sichere Verbindung zum Mail-Server konnte nicht hergestellt werden. Provider kontaktieren.",
|
||||
"rate_limited": "Zu viele Verbindungsversuche. Bitte ein paar Minuten warten und erneut versuchen.",
|
||||
"unknown": "Unbekannter Fehler beim Verbinden. Bitte App-Passwort prüfen oder erneut versuchen."
|
||||
"unknown": "Verbindung fehlgeschlagen. Prüfe das App-Passwort oder schreib uns an support@rebreak.org — wir fügen deinen Anbieter gerne hinzu."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@ -746,7 +746,26 @@
|
||||
"success_title": "Mac geschützt!",
|
||||
"success_body": "Du kannst weitere Geräte hinzufügen wenn du willst.",
|
||||
"remove_warning_title": "Profile manuell entfernen",
|
||||
"remove_warning_body": "Wir können das Profile nicht aus der Ferne löschen. Auf dem Mac: Systemeinstellungen → Profile → ReBreak → Entfernen (Admin-Passwort nötig)."
|
||||
"remove_warning_body": "Wir können das Profile nicht aus der Ferne löschen. Auf dem Mac: Systemeinstellungen → Profile → ReBreak → Entfernen (Admin-Passwort nötig).",
|
||||
"add_windows_enabled": "Windows-PC hinzufügen",
|
||||
"windows_label_question": "Wie soll der Windows-PC heißen?",
|
||||
"windows_label_default": "Windows-PC",
|
||||
"windows_label_placeholder": "z.B. Gaming-PC",
|
||||
"windows_lyra_intro": "Fünf kurze Schritte. Ich begleite dich — wenn was nicht klappt, klick auf Hilfe.",
|
||||
"windows_step_1_title": "Datei herunterladen",
|
||||
"windows_step_1_body": "Klick den Button unten — auf deinem Windows-PC. Die .reg-Datei wird gedownloadet.",
|
||||
"windows_step_2_title": ".reg ausführen",
|
||||
"windows_step_2_body": "Doppelklick auf die heruntergeladene .reg-Datei. Windows fragt: 'Möchten Sie der Registrierung Schlüssel hinzufügen?' → Klick 'Ja'.",
|
||||
"windows_step_3_title": "UAC bestätigen",
|
||||
"windows_step_3_body": "Wenn ein blauer UAC-Prompt erscheint: 'Ja' klicken. Das ist die Admin-Bestätigung.",
|
||||
"windows_step_4_title": "DNS aktivieren",
|
||||
"windows_step_4_body": "Öffne Windows-Einstellungen → Netzwerk & Internet → klick auf dein WLAN → DNS-Server-Zuweisung → 'Bearbeiten' → wähle 'Verschlüsselt nur (DNS over HTTPS)' → wähle 'rebreak-...' aus der Liste.",
|
||||
"windows_step_5_title": "Fertig",
|
||||
"windows_step_5_body": "Sobald du die DNS-Auswahl gespeichert hast, klick 'Ich hab's installiert' und ich zähl deinen PC als geschütztes Gerät.",
|
||||
"windows_download_button": "Datei auf Windows-PC herunterladen",
|
||||
"windows_success_title": "Windows-PC geschützt!",
|
||||
"windows_success_body": "Du kannst weitere Geräte hinzufügen wenn du willst.",
|
||||
"windows_remove_warning_body": "Wir können die Registrierung nicht aus der Ferne löschen. Auf dem PC: Regedit → HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\DoHSvc → Schlüssel entfernen."
|
||||
},
|
||||
"gameOver": {
|
||||
"title": "Spiel beendet",
|
||||
|
||||
@ -311,7 +311,7 @@
|
||||
"privacy_2": "No access to email content",
|
||||
"privacy_3": "GDPR-compliant, servers in Germany",
|
||||
"providers_title": "Supported providers",
|
||||
"provider_other": "Other",
|
||||
"provider_other": "Other providers",
|
||||
"empty_title": "No emails blocked yet",
|
||||
"empty_subtitle": "Connect your mailbox so Rebreak can protect you automatically.",
|
||||
"connect_sheet_title": "Connect mailbox",
|
||||
@ -327,7 +327,7 @@
|
||||
"app_password_guide_outlook": "Outlook with Microsoft account: Enable 2FA and create an app password at account.microsoft.com/security.",
|
||||
"app_password_guide_yahoo": "Yahoo requires an app password. Enable 2FA and create it at login.yahoo.com/account/security.",
|
||||
"app_password_guide_gmx": "GMX / Web.de: Enable IMAP in settings and use your regular password or an app password if 2FA is active.",
|
||||
"app_password_guide_other": "Enter the IMAP credentials of your email provider. An app password is recommended if available.",
|
||||
"app_password_guide_other": "Enter your email address and app password. Rebreak detects your provider automatically from the domain (e.g. IONOS, Strato, 1&1). An app password is recommended if available.",
|
||||
"app_password_open_link": "Create app password now",
|
||||
"form_email_label": "Email address",
|
||||
"form_email_placeholder": "your@email.com",
|
||||
@ -402,10 +402,10 @@
|
||||
"auth_failed": "The app password is incorrect. Please regenerate it at your mail provider and enter it here.",
|
||||
"app_password_required": "Your mail provider requires an app-specific password. Create one in your account settings.",
|
||||
"connection_failed": "Could not connect to the mail server. Please try again later.",
|
||||
"host_unreachable": "Mail server is currently unreachable. Check your internet connection or try again later.",
|
||||
"host_unreachable": "Mail server unreachable. Check your internet connection — or your provider may not be supported yet. Write to: support@rebreak.org",
|
||||
"tls_error": "Secure connection to the mail server failed. Please contact your provider.",
|
||||
"rate_limited": "Too many connection attempts. Please wait a few minutes and try again.",
|
||||
"unknown": "Unknown error while connecting. Please check the app password and try again."
|
||||
"unknown": "Connection failed. Check your app password or write us at support@rebreak.org — we'll add your provider."
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
@ -746,7 +746,26 @@
|
||||
"success_title": "Mac protected!",
|
||||
"success_body": "You can add more devices whenever you like.",
|
||||
"remove_warning_title": "Remove profile manually",
|
||||
"remove_warning_body": "We can't delete the profile remotely. On the Mac: System Settings → Profiles → ReBreak → Remove (admin password required)."
|
||||
"remove_warning_body": "We can't delete the profile remotely. On the Mac: System Settings → Profiles → ReBreak → Remove (admin password required).",
|
||||
"add_windows_enabled": "Add Windows PC",
|
||||
"windows_label_question": "What should this Windows PC be called?",
|
||||
"windows_label_default": "Windows PC",
|
||||
"windows_label_placeholder": "e.g. Gaming PC",
|
||||
"windows_lyra_intro": "Five quick steps. I'll walk you through each one — if something goes wrong, tap Help.",
|
||||
"windows_step_1_title": "Download the file",
|
||||
"windows_step_1_body": "Tap the button below — on your Windows PC. The .reg file will be downloaded.",
|
||||
"windows_step_2_title": "Run the .reg file",
|
||||
"windows_step_2_body": "Double-click the downloaded .reg file. Windows will ask: 'Do you want to add keys to the registry?' → Click 'Yes'.",
|
||||
"windows_step_3_title": "Confirm UAC prompt",
|
||||
"windows_step_3_body": "If a blue UAC prompt appears: click 'Yes'. This is the admin confirmation.",
|
||||
"windows_step_4_title": "Activate DNS",
|
||||
"windows_step_4_body": "Open Windows Settings → Network & Internet → click your Wi-Fi → DNS server assignment → 'Edit' → choose 'Encrypted only (DNS over HTTPS)' → select 'rebreak-...' from the list.",
|
||||
"windows_step_5_title": "Done",
|
||||
"windows_step_5_body": "Once you've saved the DNS selection, tap 'I've installed it' and I'll count your PC as a protected device.",
|
||||
"windows_download_button": "Download file to Windows PC",
|
||||
"windows_success_title": "Windows PC protected!",
|
||||
"windows_success_body": "You can add more devices whenever you like.",
|
||||
"windows_remove_warning_body": "We can't delete the registry entry remotely. On the PC: Regedit → HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\DoHSvc → remove the key."
|
||||
},
|
||||
"gameOver": {
|
||||
"title": "Game over",
|
||||
|
||||
@ -2,6 +2,7 @@ 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
|
||||
@ -42,8 +43,8 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
|
||||
// Custom-IMAP: wenn imapHost explizit gesetzt → Provider-Detection überspringen.
|
||||
// Sonst: automatisch via Email-Domain erkennen.
|
||||
const provider = detectImapProvider(email);
|
||||
// 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;
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import dns from "node:dns/promises";
|
||||
|
||||
/**
|
||||
* IMAP-Provider Konfigurationen
|
||||
* Automatisch erkennen anhand der Email-Domain
|
||||
@ -9,32 +11,88 @@ export interface ImapConfig {
|
||||
}
|
||||
|
||||
const PROVIDER_MAP: Record<string, ImapConfig> = {
|
||||
// Google
|
||||
"gmail.com": { host: "imap.gmail.com", port: 993, name: "Gmail" },
|
||||
"googlemail.com": { host: "imap.gmail.com", port: 993, name: "Gmail" },
|
||||
// Apple
|
||||
"icloud.com": { host: "imap.mail.me.com", port: 993, name: "iCloud" },
|
||||
"me.com": { host: "imap.mail.me.com", port: 993, name: "iCloud" },
|
||||
"mac.com": { host: "imap.mail.me.com", port: 993, name: "iCloud" },
|
||||
// Microsoft
|
||||
"outlook.com": { host: "outlook.office365.com", port: 993, name: "Outlook" },
|
||||
"hotmail.com": { host: "outlook.office365.com", port: 993, name: "Hotmail" },
|
||||
"hotmail.de": { host: "outlook.office365.com", port: 993, name: "Hotmail" },
|
||||
"live.com": { host: "outlook.office365.com", port: 993, name: "Live" },
|
||||
"live.de": { host: "outlook.office365.com", port: 993, name: "Live" },
|
||||
"msn.com": { host: "outlook.office365.com", port: 993, name: "Outlook" },
|
||||
// Yahoo
|
||||
"yahoo.com": { host: "imap.mail.yahoo.com", port: 993, name: "Yahoo" },
|
||||
"yahoo.de": { host: "imap.mail.yahoo.com", port: 993, name: "Yahoo" },
|
||||
"yahoo.co.uk": { host: "imap.mail.yahoo.com", port: 993, name: "Yahoo" },
|
||||
"ymail.com": { host: "imap.mail.yahoo.com", port: 993, name: "Yahoo" },
|
||||
// GMX / Web.de (United Internet)
|
||||
"gmx.de": { host: "imap.gmx.net", port: 993, name: "GMX" },
|
||||
"gmx.net": { host: "imap.gmx.net", port: 993, name: "GMX" },
|
||||
"gmx.at": { host: "imap.gmx.net", port: 993, name: "GMX" },
|
||||
"gmx.ch": { host: "imap.gmx.net", port: 993, name: "GMX" },
|
||||
"web.de": { host: "imap.web.de", port: 993, name: "Web.de" },
|
||||
"t-online.de": {
|
||||
host: "secureimap.t-online.de",
|
||||
port: 993,
|
||||
name: "T-Online",
|
||||
},
|
||||
// Telekom
|
||||
"t-online.de": { host: "secureimap.t-online.de", port: 993, name: "T-Online" },
|
||||
"magenta.de": { host: "secureimap.t-online.de", port: 993, name: "T-Online" },
|
||||
// Freenet
|
||||
"freenet.de": { host: "mx.freenet.de", port: 993, name: "Freenet" },
|
||||
// Posteo
|
||||
"posteo.de": { host: "posteo.de", port: 993, name: "Posteo" },
|
||||
// Tutanota / Tuta
|
||||
"tutanota.com": { host: "mail.tutanota.com", port: 993, name: "Tutanota" },
|
||||
"tuta.io": { host: "mail.tutanota.com", port: 993, name: "Tutanota" },
|
||||
// IONOS / 1&1 (direkte Consumer-Domains)
|
||||
"ionos.de": { host: "imap.ionos.de", port: 993, name: "IONOS" },
|
||||
"1und1.de": { host: "imap.ionos.de", port: 993, name: "IONOS" },
|
||||
"1and1.com": { host: "imap.ionos.de", port: 993, name: "IONOS" },
|
||||
"1and1.de": { host: "imap.ionos.de", port: 993, name: "IONOS" },
|
||||
"1blu.de": { host: "imap.1blu.de", port: 993, name: "1blu" },
|
||||
};
|
||||
|
||||
/**
|
||||
* MX-Record-Patterns die IONOS-Hosting-Infrastruktur identifizieren.
|
||||
* IONOS-Kunden mit eigener Domain nutzen mx00.ionos.de oder kundenserver.de.
|
||||
* Quelle: IONOS Hilfe-Seite + eigene DNS-Lookups (verifiziert 2026-05).
|
||||
*/
|
||||
const MX_IONOS_PATTERNS = [
|
||||
"ionos.de",
|
||||
"kundenserver.de",
|
||||
"perfora.net",
|
||||
"ui-dns.de",
|
||||
"ui-dns.org",
|
||||
"ui-dns.biz",
|
||||
"ui-dns.com",
|
||||
];
|
||||
|
||||
/**
|
||||
* Versucht via MX-Record-Lookup den Provider einer Custom-Domain zu erkennen.
|
||||
* Wird nur aufgerufen wenn PROVIDER_MAP keinen direkten Treffer hat.
|
||||
* Timeout: 3s (schnell genug für Connect-Flow, kein User-Impact).
|
||||
*/
|
||||
async function detectImapProviderByMx(domain: string): Promise<ImapConfig | null> {
|
||||
try {
|
||||
const mxRecords = await Promise.race([
|
||||
dns.resolveMx(domain),
|
||||
new Promise<never>((_, reject) => setTimeout(() => reject(new Error("timeout")), 3000)),
|
||||
]);
|
||||
|
||||
for (const mx of mxRecords) {
|
||||
const exchange = mx.exchange.toLowerCase();
|
||||
if (MX_IONOS_PATTERNS.some((pattern) => exchange.endsWith(pattern))) {
|
||||
return { host: "imap.ionos.de", port: 993, name: "IONOS" };
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// DNS-Lookup fehlgeschlagen oder Timeout — kein Problem, Fallback greift
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function detectImapProvider(email: string): ImapConfig {
|
||||
const domain = email.split("@")[1]?.toLowerCase() ?? "";
|
||||
return (
|
||||
@ -46,6 +104,23 @@ export function detectImapProvider(email: string): ImapConfig {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Async-Variante mit MX-Lookup-Fallback für Custom-Domains.
|
||||
* Nutzen wenn detectImapProvider keinen bekannten Provider zurückgibt
|
||||
* (erkennbar daran dass name === domain, also kein Friendly-Name).
|
||||
*/
|
||||
export async function detectImapProviderAsync(email: string): Promise<ImapConfig> {
|
||||
const domain = email.split("@")[1]?.toLowerCase() ?? "";
|
||||
const direct = PROVIDER_MAP[domain];
|
||||
if (direct) return direct;
|
||||
|
||||
const byMx = await detectImapProviderByMx(domain);
|
||||
if (byMx) return byMx;
|
||||
|
||||
// Letzter Fallback: imap.<domain> — Standard-Konvention
|
||||
return { host: `imap.${domain}`, port: 993, name: domain };
|
||||
}
|
||||
|
||||
const SMTP_MAP: Record<string, { host: string; port: number }> = {
|
||||
"imap.gmail.com": { host: "smtp.gmail.com", port: 587 },
|
||||
"imap.mail.me.com": { host: "smtp.mail.me.com", port: 587 },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user