Fix: Provider-Domain greift sofort + Domain-Validierung
Problem: Nach dem Ändern der Provider-Domain blieb die alte Domain (stressfrei-wechseln.de) im Adress-Hinzufügen-Dialog bestehen, weil der Frontend-Hook useProviderSettings() einen 5-Minuten staleTime hat und nicht invalidiert wurde. Fix: - In allen Provider-Mutations (create/update/delete) wird jetzt auch 'email-provider-public-settings' invalidiert → Domain & Label greifen sofort in allen Komponenten Zusätzlich Domain-Validierung eingebaut: - Frontend: pattern am Input + Live-Fehlermeldung Format: name.tld (mit Subdomains erlaubt, z.B. mail.meine-firma.de) Input auto-lowercase + trim - Backend: validateDomain() in createProviderConfig/updateProviderConfig Wirft Error mit sprechender Meldung bei ungültigem Format - Schützt vor Versehen im UI + direkten API-Aufrufen Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cdde7b4ab7
commit
b7d3654b72
|
|
@ -80,7 +80,24 @@ export interface CreateProviderConfigData {
|
||||||
isDefault?: boolean;
|
isDefault?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validiert Domain-Format (z.B. stressfrei-wechseln.de, mail.beispiel.com)
|
||||||
|
const DOMAIN_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
||||||
|
|
||||||
|
function validateDomain(domain: string | undefined): string {
|
||||||
|
if (!domain || !domain.trim()) {
|
||||||
|
throw new Error('Domain ist erforderlich');
|
||||||
|
}
|
||||||
|
const normalized = domain.trim().toLowerCase();
|
||||||
|
if (!DOMAIN_REGEX.test(normalized)) {
|
||||||
|
throw new Error(`Ungültige Domain: "${domain}". Format: name.tld (z.B. meine-firma.de)`);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
export async function createProviderConfig(data: CreateProviderConfigData) {
|
export async function createProviderConfig(data: CreateProviderConfigData) {
|
||||||
|
// Domain validieren
|
||||||
|
const validatedDomain = validateDomain(data.domain);
|
||||||
|
|
||||||
// Falls isDefault=true, alle anderen auf false setzen
|
// Falls isDefault=true, alle anderen auf false setzen
|
||||||
if (data.isDefault) {
|
if (data.isDefault) {
|
||||||
await prisma.emailProviderConfig.updateMany({
|
await prisma.emailProviderConfig.updateMany({
|
||||||
|
|
@ -102,7 +119,7 @@ export async function createProviderConfig(data: CreateProviderConfigData) {
|
||||||
apiKey: data.apiKey || null,
|
apiKey: data.apiKey || null,
|
||||||
username: data.username || null,
|
username: data.username || null,
|
||||||
passwordEncrypted,
|
passwordEncrypted,
|
||||||
domain: data.domain,
|
domain: validatedDomain,
|
||||||
defaultForwardEmail: data.defaultForwardEmail || null,
|
defaultForwardEmail: data.defaultForwardEmail || null,
|
||||||
imapEncryption: data.imapEncryption ?? 'SSL',
|
imapEncryption: data.imapEncryption ?? 'SSL',
|
||||||
smtpEncryption: data.smtpEncryption ?? 'SSL',
|
smtpEncryption: data.smtpEncryption ?? 'SSL',
|
||||||
|
|
@ -135,7 +152,7 @@ export async function updateProviderConfig(
|
||||||
if (data.apiUrl !== undefined) updateData.apiUrl = data.apiUrl;
|
if (data.apiUrl !== undefined) updateData.apiUrl = data.apiUrl;
|
||||||
if (data.apiKey !== undefined) updateData.apiKey = data.apiKey || null;
|
if (data.apiKey !== undefined) updateData.apiKey = data.apiKey || null;
|
||||||
if (data.username !== undefined) updateData.username = data.username || null;
|
if (data.username !== undefined) updateData.username = data.username || null;
|
||||||
if (data.domain !== undefined) updateData.domain = data.domain;
|
if (data.domain !== undefined) updateData.domain = validateDomain(data.domain);
|
||||||
if (data.defaultForwardEmail !== undefined)
|
if (data.defaultForwardEmail !== undefined)
|
||||||
updateData.defaultForwardEmail = data.defaultForwardEmail || null;
|
updateData.defaultForwardEmail = data.defaultForwardEmail || null;
|
||||||
if (data.imapEncryption !== undefined) updateData.imapEncryption = data.imapEncryption;
|
if (data.imapEncryption !== undefined) updateData.imapEncryption = data.imapEncryption;
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ export default function EmailProviders() {
|
||||||
emailProviderApi.createConfig(data),
|
emailProviderApi.createConfig(data),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['email-provider-configs'] });
|
queryClient.invalidateQueries({ queryKey: ['email-provider-configs'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['email-provider-public-settings'] });
|
||||||
closeModal();
|
closeModal();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -110,6 +111,7 @@ export default function EmailProviders() {
|
||||||
emailProviderApi.updateConfig(id, data),
|
emailProviderApi.updateConfig(id, data),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['email-provider-configs'] });
|
queryClient.invalidateQueries({ queryKey: ['email-provider-configs'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['email-provider-public-settings'] });
|
||||||
closeModal();
|
closeModal();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -118,6 +120,7 @@ export default function EmailProviders() {
|
||||||
mutationFn: (id: number) => emailProviderApi.deleteConfig(id),
|
mutationFn: (id: number) => emailProviderApi.deleteConfig(id),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['email-provider-configs'] });
|
queryClient.invalidateQueries({ queryKey: ['email-provider-configs'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['email-provider-public-settings'] });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -547,13 +550,27 @@ export default function EmailProviders() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Input
|
<div>
|
||||||
label="Domain *"
|
<Input
|
||||||
value={formData.domain}
|
label="Domain *"
|
||||||
onChange={(e) => setFormData({ ...formData, domain: e.target.value })}
|
value={formData.domain}
|
||||||
placeholder="stressfrei-wechseln.de"
|
onChange={(e) =>
|
||||||
required
|
setFormData({ ...formData, domain: e.target.value.toLowerCase().trim() })
|
||||||
/>
|
}
|
||||||
|
placeholder="stressfrei-wechseln.de"
|
||||||
|
required
|
||||||
|
pattern="^[a-z0-9]([a-z0-9\-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]*[a-z0-9])?)+$"
|
||||||
|
title="Gültige Domain erforderlich, z.B. meine-firma.de oder mail.beispiel.com"
|
||||||
|
/>
|
||||||
|
{formData.domain &&
|
||||||
|
!/^[a-z0-9]([a-z0-9\-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9\-]*[a-z0-9])?)+$/.test(
|
||||||
|
formData.domain,
|
||||||
|
) && (
|
||||||
|
<p className="text-xs text-red-600 mt-1">
|
||||||
|
Keine gültige Domain – Format: name.tld (z.B. meine-firma.de)
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
label="Bezeichnung für Kunden-E-Mails (UI-Label)"
|
label="Bezeichnung für Kunden-E-Mails (UI-Label)"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue