fix(contact): keep the contact email editable and protect it from scrapers
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m20s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m20s
An earlier change rejected obfuscated entries such as
"info [á] wabenhain.com" in Admin -> Inhalte and replaced them with the
default address. The obfuscation was intentional, so:
- The admin accepts the entry as written (plain or obfuscated with
[at], (at), {ät}, [á], <@>, [dot], [punkt]) and stores it unchanged;
only entries that yield no valid address are rejected.
- lib/contact-email.ts turns the entry into the real address for
mailto links and DHL labels.
- Public pages never contain the address in clear text: footer,
contact page and every email in legal texts (Impressum, Datenschutz,
Widerruf) render "info [at] wabenhain.com" plus an XOR-encoded payload
with a fresh random key; the browser builds the clickable mailto link
after load (components/protected-email.tsx).
Verified with a production build: the raw HTML of /, /kontakt,
/impressum, /datenschutz and /widerrufsbelehrung contains no plain
address, and after load each page shows mailto:[email protected].
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
12675f381a
commit
2943021fe9
9 files changed
+251
-35
No files matched your search
@@ -7,7 +7,7 @@ import { CONTENT_REGISTRY } from "@/lib/content-registry";
|
||||
import { isValidUploadPath, isValidVideoUploadPath } from "@/lib/uploads";
|
||||
import { requireAdmin } from "./_shared";
|
||||
import { fail, succeed } from "@/lib/flash";
|
||||
import { z } from "zod";
|
||||
import { normalizeContactEmail } from "@/lib/contact-email";
|
||||
|
||||
const COMPANY_EMAIL_KEY = "company.email";
|
||||
|
||||
@@ -51,13 +51,17 @@ export async function updateSiteContentAction(formData: FormData): Promise<void>
|
||||
if (def.type === "video" && valueDe !== "" && !isValidVideoUploadPath(valueDe)) {
|
||||
continue;
|
||||
}
|
||||
// Die Firmen-E-Mail steht im Impressum (§ 5 Abs. 1 Nr. 2 DDG verlangt
|
||||
// eine direkt nutzbare Adresse), in der Widerrufsbelehrung und in
|
||||
// mailto-Links. Eine verschleierte Schreibweise ("info [at] …") waere
|
||||
// dort rechtlich angreifbar und technisch unbrauchbar — daher wird der
|
||||
// ganze Speichervorgang abgelehnt statt still uebernommen.
|
||||
if (def.key === COMPANY_EMAIL_KEY && !z.string().trim().email().safeParse(valueDe).success) {
|
||||
return fail("/admin/inhalte", "Bitte gib als Firmen-E-Mail eine vollständige, gültige Adresse an (z. B. [email protected]). Sie steht im Impressum und muss direkt nutzbar sein.");
|
||||
// Die Firmen-E-Mail darf so eingetragen werden, wie sie gemeint ist —
|
||||
// auch verschleiert ("info [at] …", "info [á] …"). Gespeichert wird der
|
||||
// Eintrag unveraendert; die Website rechnet ihn in die echte Adresse um
|
||||
// und zeigt sie geschuetzt an (lib/contact-email.ts). Abgelehnt wird nur,
|
||||
// was sich in keine gueltige Adresse umrechnen laesst — sonst stuende im
|
||||
// Impressum ein Kontaktweg, der ins Leere fuehrt.
|
||||
if (def.key === COMPANY_EMAIL_KEY && normalizeContactEmail(valueDe) === null) {
|
||||
return fail(
|
||||
"/admin/inhalte",
|
||||
"Die Firmen-E-Mail ergibt keine gültige Adresse. Möglich sind z. B. [email protected], info [at] wabenhain.com oder info [á] wabenhain.com."
|
||||
);
|
||||
}
|
||||
updates.push({ key: def.key, valueDe: def.key === COMPANY_EMAIL_KEY ? valueDe.trim() : valueDe, valueEn: null });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user