Files
adilalloandCursor f780eac1fa Encode sign-in mail as base64 so webmail cannot mangle the verify token.
Quoted-printable still rewrote token= as token=3D in the raw MIME, which some clients never decode. Distinguish a missing token from a real expiry, and keep links valid for 60 minutes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 17:07:00 -06:00

114 lines
3.7 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import nodemailer from "nodemailer";
import {
MAIL_TEXT_ENCODING,
buildVerifyLinkParts,
resolveMailFrom,
} from "../../lib/server/mail";
const VERIFY_URL =
"https://staging.communityrule.info/api/auth/magic-link/verify?token=5IdE_BHowaw-QJj7Rwue7CbB8wDXvYITvnxRb1FGqxA";
function decodeBase64Parts(raw: string): string {
const blocks = [
...raw.matchAll(
/Content-Transfer-Encoding:\s*base64\s*\r?\n\r?\n([A-Za-z0-9+/=\s]+)/gi,
),
];
return blocks
.map((match) =>
Buffer.from(match[1].replace(/\s/g, ""), "base64").toString("utf8"),
)
.join("\n");
}
const MAIL_FROM_KEYS = ["SMTP_FROM", "CLOUDRON_MAIL_FROM"] as const;
const ORIGINAL_FROM = Object.fromEntries(
MAIL_FROM_KEYS.map((key) => [key, process.env[key]]),
) as Record<(typeof MAIL_FROM_KEYS)[number], string | undefined>;
afterEach(() => {
for (const key of MAIL_FROM_KEYS) {
const original = ORIGINAL_FROM[key];
if (original === undefined) delete process.env[key];
else process.env[key] = original;
}
});
describe("buildVerifyLinkParts", () => {
it("puts the exact verify URL in text, href, and visible HTML link", () => {
const { text, html } = buildVerifyLinkParts(
VERIFY_URL,
"Open this link to sign in (it expires in 60 minutes):",
"If you did not request this, you can ignore this email.",
"Sign in",
);
expect(text).toContain(`<${VERIFY_URL}>`);
expect(html).toContain(`href="${VERIFY_URL}"`);
expect(html).toContain(`>${VERIFY_URL}</a>`);
expect(html).toContain(">Sign in</a>");
});
it("escapes HTML in the intro and href", () => {
const { html } = buildVerifyLinkParts(
'https://example.test/verify?token=a&b="c"',
'View "Rule <beta>"',
"Ignore if unexpected.",
"Open",
);
expect(html).toContain("View &quot;Rule &lt;beta&gt;&quot;");
expect(html).toContain(
'href="https://example.test/verify?token=a&amp;b=&quot;c&quot;"',
);
});
});
describe("MIME encoding of verify-link mail", () => {
it("uses base64 so the raw MIME never contains token=3D", async () => {
const { text, html } = buildVerifyLinkParts(
VERIFY_URL,
"Open this link to sign in (it expires in 60 minutes):",
"If you did not request this, you can ignore this email.",
"Sign in",
);
const transporter = nodemailer.createTransport({
streamTransport: true,
buffer: true,
newline: "unix",
});
const info = await transporter.sendMail({
from: "Community Rule <staging.app@communityrule.info>",
to: "member@example.com",
subject: "Sign in to Community Rule",
text,
html,
textEncoding: MAIL_TEXT_ENCODING,
});
const raw = Buffer.isBuffer(info.message)
? info.message.toString("utf8")
: String(info.message);
expect(raw).toMatch(/Content-Transfer-Encoding:\s*base64/i);
expect(raw).not.toContain("token=3D");
expect(raw).not.toMatch(/quoted-printable/i);
const decoded = decodeBase64Parts(raw);
expect(decoded).toContain(`href="${VERIFY_URL}"`);
expect(decoded).toContain(VERIFY_URL);
});
});
describe("resolveMailFrom", () => {
it("prefers SMTP_FROM, then CLOUDRON_MAIL_FROM", () => {
delete process.env.SMTP_FROM;
delete process.env.CLOUDRON_MAIL_FROM;
expect(resolveMailFrom()).toBe("noreply@localhost");
process.env.CLOUDRON_MAIL_FROM = "staging.app@communityrule.info";
expect(resolveMailFrom()).toBe("staging.app@communityrule.info");
process.env.SMTP_FROM = "Community Rule <hello@communityrule.info>";
expect(resolveMailFrom()).toBe(
"Community Rule <hello@communityrule.info>",
);
});
});