Customer input (first name, address, product names), order data, admin fields (company data, carrier, tracking number) and URLs were inserted into email HTML unescaped. A first name such as <a href="…">…</a> ended up as a working link in a genuine shop email, usable for phishing. All variable values in email HTML now go through escapeHtml (lib/escape-html.ts, shared with the withdrawal receipt). Button links only accept http(s), so a javascript: tracking URL is dropped. The plain text part stays unescaped. Tests cover order confirmation, shipment, status mails, layout company data and back-in-stock mails. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
74 lines
2.8 KiB
TypeScript
74 lines
2.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { formatReceivedAt, withdrawalDeclarationLines, withdrawalReceiptEmailContent } from "./withdrawal";
|
|
import { escapeHtml } from "./escape-html";
|
|
|
|
const input = {
|
|
name: "Erika Mustermann",
|
|
orderNumber: "WH-2026-0042",
|
|
email: "[email protected]",
|
|
message: null,
|
|
};
|
|
|
|
// 26.09.2026 12:03:07 UTC = 14:03:07 in Berlin (Sommerzeit)
|
|
const receivedAt = new Date(Date.UTC(2026, 8, 26, 12, 3, 7));
|
|
|
|
describe("formatReceivedAt", () => {
|
|
it("formats in German local time with seconds", () => {
|
|
expect(formatReceivedAt(receivedAt, "de")).toBe("26.09.2026, 14:03:07 Uhr (MESZ)");
|
|
});
|
|
|
|
it("uses winter time in winter", () => {
|
|
expect(formatReceivedAt(new Date(Date.UTC(2026, 0, 5, 9, 0, 0)), "de")).toBe("05.01.2026, 10:00:00 Uhr (MEZ)");
|
|
});
|
|
|
|
it("formats in English with the German time zone", () => {
|
|
expect(formatReceivedAt(receivedAt, "en")).toBe("26 September 2026, 14:03:07 (CEST, German time)");
|
|
});
|
|
});
|
|
|
|
describe("withdrawalDeclarationLines", () => {
|
|
it("states the withdrawal and the identifying data", () => {
|
|
expect(withdrawalDeclarationLines(input, "de")).toEqual([
|
|
"Hiermit widerrufe ich den Vertrag über die Bestellung WH-2026-0042.",
|
|
"Name: Erika Mustermann",
|
|
"E-Mail: [email protected]",
|
|
]);
|
|
});
|
|
|
|
it("adds an optional message", () => {
|
|
expect(withdrawalDeclarationLines({ ...input, message: "Falsche Sorte" }, "en")).toEqual([
|
|
"I hereby withdraw from the contract for order WH-2026-0042.",
|
|
"Name: Erika Mustermann",
|
|
"Email: [email protected]",
|
|
"Message: Falsche Sorte",
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("escapeHtml", () => {
|
|
it("neutralises markup", () => {
|
|
expect(escapeHtml(`<a href="x">'&'</a>`)).toBe("<a href="x">'&'</a>");
|
|
});
|
|
});
|
|
|
|
describe("withdrawalReceiptEmailContent", () => {
|
|
it("contains the declaration and the time of receipt in text and HTML", () => {
|
|
const mail = withdrawalReceiptEmailContent(input, receivedAt, "de");
|
|
expect(mail.subject).toBe("Eingangsbestätigung deines Widerrufs zu WH-2026-0042");
|
|
expect(mail.text).toContain("Hiermit widerrufe ich den Vertrag über die Bestellung WH-2026-0042.");
|
|
expect(mail.text).toContain("26.09.2026, 14:03:07 Uhr (MESZ)");
|
|
expect(mail.bodyHtml).toContain("26.09.2026, 14:03:07 Uhr (MESZ)");
|
|
});
|
|
|
|
it("escapes user input in the HTML body and strips line breaks from the subject", () => {
|
|
const mail = withdrawalReceiptEmailContent(
|
|
{ ...input, name: `<img src=x onerror=alert(1)>`, orderNumber: "A1\r\nBcc: [email protected]" },
|
|
receivedAt,
|
|
"de"
|
|
);
|
|
expect(mail.bodyHtml).not.toContain("<img");
|
|
expect(mail.bodyHtml).toContain("<img src=x onerror=alert(1)>");
|
|
expect(mail.subject).not.toMatch(/[\r\n]/);
|
|
});
|
|
});
|