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:
@@ -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).
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
Reference in New Issue
Block a user