Send sign-in mail as HTML so quoted-printable wrapping cannot break the verify URL.
Staging was delivering magic links that looked expired because the token query string was encoded and wrapped in plaintext MIME. Also read rate-limit retry from the API error body, mention spam in the success copy, and document SES relay DNS. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import nodemailer from "nodemailer";
|
||||
import {
|
||||
buildVerifyLinkParts,
|
||||
resolveMailFrom,
|
||||
} from "../../lib/server/mail";
|
||||
|
||||
const VERIFY_URL =
|
||||
"https://staging.communityrule.info/api/auth/magic-link/verify?token=5IdE_BHowaw-QJj7Rwue7CbB8wDXvYITvnxRb1FGqxA";
|
||||
|
||||
function decodeQuotedPrintable(value: string): string {
|
||||
return value
|
||||
.replace(/=\r?\n/g, "")
|
||||
.replace(/=([0-9A-Fa-f]{2})/g, (_, hex: string) =>
|
||||
String.fromCharCode(Number.parseInt(hex, 16)),
|
||||
);
|
||||
}
|
||||
|
||||
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 both text and the HTML href", () => {
|
||||
const { text, html } = buildVerifyLinkParts(
|
||||
VERIFY_URL,
|
||||
"Open this link to sign in (it expires in 15 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(">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 "Rule <beta>"");
|
||||
expect(html).toContain(
|
||||
'href="https://example.test/verify?token=a&b="c""',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("MIME encoding of verify-link mail", () => {
|
||||
it("keeps a clickable href after quoted-printable encoding", async () => {
|
||||
const { text, html } = buildVerifyLinkParts(
|
||||
VERIFY_URL,
|
||||
"Open this link to sign in (it expires in 15 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,
|
||||
});
|
||||
const raw = Buffer.isBuffer(info.message)
|
||||
? info.message.toString("utf8")
|
||||
: String(info.message);
|
||||
const decoded = decodeQuotedPrintable(raw);
|
||||
expect(decoded).toContain(`href="${VERIFY_URL}"`);
|
||||
expect(decoded).toContain(VERIFY_URL);
|
||||
expect(decoded).not.toContain("token=3D");
|
||||
});
|
||||
});
|
||||
|
||||
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>",
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user