Pentest 81.1 (MEDIUM): Self-Forward erzeugte Mail-Loop am Provider
Bug: Die Stressfrei-Adresse selbst (max@stressfrei-wechseln.net) konnte als zusätzliches Weiterleitungsziel eingetragen werden, auch Plus-Varianten. Plesk leitet auf sich selbst um → Mail-Loop. Backend setAdditionalForwards: lädt zusätzlich meta.email, vergleicht canonicalEmailKey gegen canonicalEmailKey(meta.email). Bei Treffer hartes ApiError(400) mit klarer "zeigt auf die Adresse selbst – Mail-Loop"-Meldung statt silent dedup – der User soll merken, dass sein Eintrag bewusst abgelehnt wurde. Frontend AdditionalForwardsModal: zusätzliche proaktive Validierung im Sub-Modal mit identischem canonicalize-Helper. Neuer selfEmail- Prop, damit auch der Create-Modus (vor Persist) den Check fahren kann. Spart Roundtrip + sofort sprechende Meldung.
This commit is contained in:
@@ -4241,6 +4241,7 @@ function StressfreiEmailModal({
|
||||
onClose={() => setShowForwardsModal(false)}
|
||||
email={email ?? undefined}
|
||||
customerEmail={customerEmail}
|
||||
selfEmail={localPart ? localPart + domainSuffix : undefined}
|
||||
value={additionalForwards}
|
||||
onChange={setAdditionalForwards}
|
||||
/>
|
||||
@@ -4258,6 +4259,7 @@ function AdditionalForwardsModal({
|
||||
onClose,
|
||||
email,
|
||||
customerEmail,
|
||||
selfEmail,
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
@@ -4265,6 +4267,9 @@ function AdditionalForwardsModal({
|
||||
onClose: () => void;
|
||||
email?: StressfreiEmail;
|
||||
customerEmail?: string;
|
||||
/** Die Stressfrei-Adresse selbst (für Self-Forward-Check im Create-Modus,
|
||||
* wo es noch kein `email`-Prop gibt). Edit-Modus zieht's aus `email`. */
|
||||
selfEmail?: string;
|
||||
/** Aktuelle Liste – im Create-Modus controlled, im Edit-Modus initialer Wert. */
|
||||
value: string[];
|
||||
onChange: (next: string[]) => void;
|
||||
@@ -4304,6 +4309,17 @@ function AdditionalForwardsModal({
|
||||
}
|
||||
};
|
||||
|
||||
// Plus-Tag wegstrippen + lowercase, identisch zum Backend-canonicalEmailKey.
|
||||
// Dann landen `billing+x@y` und `billing@y` im selben Key.
|
||||
const canonicalize = (raw: string) => {
|
||||
const lower = raw.trim().toLowerCase();
|
||||
const at = lower.lastIndexOf('@');
|
||||
if (at < 1) return lower;
|
||||
const local = lower.slice(0, at);
|
||||
const plus = local.indexOf('+');
|
||||
return (plus === -1 ? local : local.slice(0, plus)) + '@' + lower.slice(at + 1);
|
||||
};
|
||||
|
||||
const handleAdd = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const candidate = newEmail.trim().toLowerCase();
|
||||
@@ -4312,11 +4328,17 @@ function AdditionalForwardsModal({
|
||||
setError('Bitte eine gültige E-Mail-Adresse eingeben.');
|
||||
return;
|
||||
}
|
||||
if (candidate === customerEmail?.toLowerCase()) {
|
||||
const candidateKey = canonicalize(candidate);
|
||||
if (customerEmail && candidateKey === canonicalize(customerEmail)) {
|
||||
setError('Die Stamm-E-Mail des Kunden ist bereits Weiterleitungsziel.');
|
||||
return;
|
||||
}
|
||||
if (value.some((f) => f.toLowerCase() === candidate)) {
|
||||
const ownAddress = email?.email ?? selfEmail;
|
||||
if (ownAddress && candidateKey === canonicalize(ownAddress)) {
|
||||
setError(`"${candidate}" zeigt auf die Adresse selbst – das würde einen Mail-Loop erzeugen.`);
|
||||
return;
|
||||
}
|
||||
if (value.some((f) => canonicalize(f) === candidateKey)) {
|
||||
setError('Diese Adresse ist schon in der Liste.');
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user