Plesk-Sync: Auto-Import bei User-Remove deaktivieren

Folge-Bug zu 194c864: User löscht Adresse im Modal → DB-Liste
wird kürzer → Plesk-Sync läuft → Auto-Import sieht "c ist in
Plesk aber nicht in DB" → schreibt c zurück in
additionalForwardingEmails → Diff sagt nichts zu entfernen.

Ursache: Auto-Import (Pentest 83.x) lief für alle Sync-Pfade.
Beim Sync-Button ist Plesk→DB-Übernahme gewollt (Bestands-
Migration). Beim User-Add/Remove ist die DB-Liste die explizite
Intent – Auto-Import macht das User-Delete kaputt.

syncForwardingForEmail(id, opts?: { autoImportPleskMembers? })
mit Default true (Sync-Button-Verhalten). setAdditionalForwards
ruft mit false – entfernte Adressen verschwinden jetzt sauber
auch beim Provider.
This commit is contained in:
2026-06-18 18:24:44 +02:00
parent 194c86409f
commit dfe2a4b241
2 changed files with 77 additions and 49 deletions
+62 -49
View File
@@ -350,8 +350,10 @@ export async function setAdditionalForwards(
}); });
// Provider unmittelbar nachziehen, sonst läuft das Plesk-Mail-Konto // Provider unmittelbar nachziehen, sonst läuft das Plesk-Mail-Konto
// mit der alten Liste weiter. // mit der alten Liste weiter. autoImport=false, weil unsere DB-Liste
const syncResult = await syncForwardingForEmail(id); // hier die explizite User-Intent ist kein Plesk-Member-Auto-Pull,
// sonst landen gerade entfernte Adressen zurück in der Liste.
const syncResult = await syncForwardingForEmail(id, { autoImportPleskMembers: false });
// 71.4: Rollback wenn Plesk den Sync abgelehnt hat. DB darf nicht // 71.4: Rollback wenn Plesk den Sync abgelehnt hat. DB darf nicht
// den optimistischen Stand zeigen, wenn der Provider noch auf dem // den optimistischen Stand zeigen, wenn der Provider noch auf dem
@@ -506,6 +508,7 @@ export async function getDecryptedPassword(id: number): Promise<string | null> {
// Einträge, bei denen das Flag nie gesetzt wurde, werden so geheilt). // Einträge, bei denen das Flag nie gesetzt wurde, werden so geheilt).
export async function syncForwardingForEmail( export async function syncForwardingForEmail(
id: number, id: number,
options: { autoImportPleskMembers?: boolean } = {},
): Promise<{ ): Promise<{
success: boolean; success: boolean;
forwardTargets?: string[]; forwardTargets?: string[];
@@ -513,6 +516,14 @@ export async function syncForwardingForEmail(
passwordReset?: boolean; passwordReset?: boolean;
error?: string; error?: string;
}> { }> {
// Auto-Import übernimmt unbekannte Plesk-Members in unsere DB. Macht
// beim Sync-Button Sinn (Bestands-Migration), aber NICHT beim
// User-getriggerten Add/Remove dort ist die DB-Liste die Wahrheit.
// Sonst kreisen entfernte Adressen zurück in die Liste:
// 1. User entfernt c → DB=[a,b], Plesk=[a,b,c]
// 2. Auto-Import: "c ist in Plesk aber nicht in DB → in DB schreiben"
// 3. → DB=[a,b,c], Diff sagt nichts zu löschen, Plesk bleibt [a,b,c].
const autoImport = options.autoImportPleskMembers ?? true;
const stressfreiEmail = await prisma.stressfreiEmail.findUnique({ const stressfreiEmail = await prisma.stressfreiEmail.findUnique({
where: { id }, where: { id },
select: { select: {
@@ -572,55 +583,57 @@ export async function syncForwardingForEmail(
// als Forwarding-Target auf sich selbst (Mail-Loop). // als Forwarding-Target auf sich selbst (Mail-Loop).
seenKeys.add(canonicalEmailKey(stressfreiEmail.email)); seenKeys.add(canonicalEmailKey(stressfreiEmail.email));
try { if (autoImport) {
const pleskState = await checkEmailExists(localPart); try {
const existingMembers = [ const pleskState = await checkEmailExists(localPart);
...(pleskState.mailgroupMembers ?? []), const existingMembers = [
...(pleskState.forwardingTargets ?? []), ...(pleskState.mailgroupMembers ?? []),
]; ...(pleskState.forwardingTargets ?? []),
const newImports: string[] = [];
for (const member of existingMembers) {
// Pentest 83.1: importierte Adressen aus Plesk müssen denselben
// Filter passieren wie User-Eingaben (TLD-Blocklist, Format).
// Sonst rutschen reservierte TLDs wie `.internal` ohne Check
// in unsere DB, falls ein Plesk-Admin sie dort manuell gepflegt
// hat. Ungültige werden silent gedroppt Log informiert.
let validated: string;
try {
validated = assertValidForwardingEmail(member);
} catch (validationErr) {
const reason = validationErr instanceof Error ? validationErr.message : 'unbekannt';
console.debug(
`[syncForwardingForEmail] Plesk-Member "${member}" verworfen: ${reason}`,
);
continue;
}
const key = canonicalEmailKey(validated);
if (!seenKeys.has(key)) {
seenKeys.add(key);
forwardTargets.push(validated);
newImports.push(validated);
}
}
if (newImports.length > 0) {
const mergedAdditional = [
...parseAdditionalForwards(stressfreiEmail.additionalForwardingEmails),
...newImports,
]; ];
await prisma.stressfreiEmail.update({ const newImports: string[] = [];
where: { id }, for (const member of existingMembers) {
data: { additionalForwardingEmails: serializeAdditionalForwards(mergedAdditional) }, // Pentest 83.1: importierte Adressen aus Plesk müssen denselben
}); // Filter passieren wie User-Eingaben (TLD-Blocklist, Format).
// Pentest 83.3: PII-Logs auf debug-Level statt log-Level. // Sonst rutschen reservierte TLDs wie `.internal` ohne Check
console.debug( // in unsere DB, falls ein Plesk-Admin sie dort manuell gepflegt
`[syncForwardingForEmail] Importiert aus Plesk-Mailgroup für ${stressfreiEmail.email}:`, // hat. Ungültige werden silent gedroppt Log informiert.
newImports, let validated: string;
); try {
validated = assertValidForwardingEmail(member);
} catch (validationErr) {
const reason = validationErr instanceof Error ? validationErr.message : 'unbekannt';
console.debug(
`[syncForwardingForEmail] Plesk-Member "${member}" verworfen: ${reason}`,
);
continue;
}
const key = canonicalEmailKey(validated);
if (!seenKeys.has(key)) {
seenKeys.add(key);
forwardTargets.push(validated);
newImports.push(validated);
}
}
if (newImports.length > 0) {
const mergedAdditional = [
...parseAdditionalForwards(stressfreiEmail.additionalForwardingEmails),
...newImports,
];
await prisma.stressfreiEmail.update({
where: { id },
data: { additionalForwardingEmails: serializeAdditionalForwards(mergedAdditional) },
});
// Pentest 83.3: PII-Logs auf debug-Level statt log-Level.
console.debug(
`[syncForwardingForEmail] Importiert aus Plesk-Mailgroup für ${stressfreiEmail.email}:`,
newImports,
);
}
} catch (importErr) {
// Nicht hart fehlschlagen im schlimmsten Fall fehlen ein paar
// alte Empfänger, aber der eigentliche Sync soll trotzdem laufen.
console.error('[syncForwardingForEmail] Mailgroup-Import fehlgeschlagen:', importErr);
} }
} catch (importErr) {
// Nicht hart fehlschlagen im schlimmsten Fall fehlen ein paar
// alte Empfänger, aber der eigentliche Sync soll trotzdem laufen.
console.error('[syncForwardingForEmail] Mailgroup-Import fehlgeschlagen:', importErr);
} }
// 1) Forwards neu setzen (deaktiviert intern Mailgroup). // 1) Forwards neu setzen (deaktiviert intern Mailgroup).
+15
View File
@@ -97,6 +97,21 @@ isolierte Instanz (keine Multi-Tenancy im Code), Provisioning + Abrechnung
## ✅ Erledigt ## ✅ Erledigt
- [x] **🐞 Entfernte Weiterleitungen kamen via Auto-Import zurück**
- Folge-Bug: User löscht Adresse im Modal → DB-Liste wird kürzer →
Plesk-Sync läuft → Auto-Import (`Pentest 83.x`) sieht „c ist in
Plesk aber nicht in DB" → schreibt `c` zurück in
`additionalForwardingEmails` → Diff sagt nichts zu entfernen.
- Ursache: Auto-Import war für **alle** Sync-Aufrufe aktiv. Beim
Sync-Button-Klick will der User Plesk-Bestand übernehmen (Import
sinnvoll), beim Add/Remove im Modal ist die DB-Liste die
explizite Intent (Import schädlich).
- Fix: `syncForwardingForEmail(id, { autoImportPleskMembers? })`
mit Default `true`. `setAdditionalForwards` ruft mit
`false` auf → entfernte Adressen verschwinden jetzt sauber bei
Plesk. Sync-Button-Pfad bleibt unverändert (importiert weiterhin
alte Bestands-Members).
- [x] **🐞 Plesk-Sync: `-forwarding-addresses set:` existiert gar nicht** - [x] **🐞 Plesk-Sync: `-forwarding-addresses set:` existiert gar nicht**
- Folge-Bug nach `a83358b`/`24e152b`: Sync verändert Plesk weiterhin - Folge-Bug nach `a83358b`/`24e152b`: Sync verändert Plesk weiterhin
nicht. `plesk bin mail --help` zeigt: `-forwarding-addresses` nicht. `plesk bin mail --help` zeigt: `-forwarding-addresses`