diff --git a/src/app/api/widerruf/route.ts b/src/app/api/widerruf/route.ts index 9014111..d089bec 100644 --- a/src/app/api/widerruf/route.ts +++ b/src/app/api/widerruf/route.ts @@ -5,7 +5,8 @@ import { rateLimit, getClientIp } from "@/lib/rate-limit"; import { isTrustedOrigin } from "@/lib/csrf"; import { sendMail } from "@/lib/mail"; import { withdrawalReceiptEmail } from "@/lib/email-templates"; -import { escapeHtml, formatReceivedAt, withdrawalDeclarationLines } from "@/lib/withdrawal"; +import { formatReceivedAt, withdrawalDeclarationLines } from "@/lib/withdrawal"; +import { escapeHtml } from "@/lib/escape-html"; import { getLocale } from "@/i18n/locale"; const WITHDRAWALS_PER_WINDOW = 10; diff --git a/src/lib/email-templates.test.ts b/src/lib/email-templates.test.ts new file mode 100644 index 0000000..8a78a02 --- /dev/null +++ b/src/lib/email-templates.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, it, vi } from "vitest"; + +vi.mock("@/lib/company-info", () => ({ + getCompanyInfo: async () => ({ + name: "WABENHAIN ", + address: { street: "Bühlweg 10", zip: "86450", city: "Altenmünster" }, + }), +})); +vi.mock("@/lib/url", () => ({ getSiteUrl: () => "https://wabenhain.com" })); + +import { + backInStockEmail, + orderConfirmationEmail, + pickupReadyEmail, + returnAcceptedEmail, + shipmentEmail, +} from "./email-templates"; + +const EVIL = `Hier klicken`; +const ESCAPED = "<a href="https://phish.example">Hier klicken</a>"; + +describe("email templates escape untrusted values", () => { + it("order confirmation escapes name, items, address and order number", async () => { + const mail = await orderConfirmationEmail({ + locale: "de", + orderNumber: `WH-1">`, + firstName: EVIL, + fulfillmentMethod: "VERSAND", + items: [{ productName: EVIL, quantity: 1, weightGrams: 500, totalCents: 1200 }], + subtotalCents: 1200, + shippingCents: 0, + totalCents: 1200, + street: EVIL, + zip: "86450", + city: "", + }); + expect(mail.html).not.toContain("https://phish.example\">"); + expect(mail.html).not.toContain("