diff --git a/src/app/api/utils/email.tsx b/src/app/api/utils/email.tsx index a20f378944..ca83c8ae11 100644 --- a/src/app/api/utils/email.tsx +++ b/src/app/api/utils/email.tsx @@ -4,14 +4,13 @@ import { SubscriberRow } from "knex/types/tables"; import { resetUnverifiedEmailAddress } from "../../../db/tables/emailAddresses"; -import { sendEmail } from "../../../utils/email"; +import { sendEmail, randomToken } from "../../../utils/email"; import { renderEmail } from "../../../emails/renderEmail"; import { VerifyEmailAddressEmail } from "../../../emails/templates/verifyEmailAddress/VerifyEmailAddressEmail"; import { sanitizeSubscriberRow } from "../../functions/server/sanitize"; import { getL10n } from "../../functions/l10n/serverComponents"; import { BadRequestError } from "../../../utils/error"; import { captureException } from "@sentry/node"; -import crypto from "crypto"; import { addUnsubscribeTokenForSubscriber, getEmailPreferenceForSubscriber, @@ -67,7 +66,7 @@ export async function unsubscribeLinkForSubscriber( subscriber: SerializedSubscriber, ) { try { - const newUnsubToken = randomString(); + const newUnsubToken = randomToken(); let sub; const getRes = await getEmailPreferenceForSubscriber(subscriber.id); if (getRes.unsubscribe_token) { @@ -98,7 +97,3 @@ export async function unsubscribeLinkForSubscriber( return null; } } - -function randomString(length: number = 64) { - return crypto.randomBytes(length).toString("hex"); -} diff --git a/src/db/tables/subscriber_email_preferences.ts b/src/db/tables/subscriber_email_preferences.ts index 56e0a96539..c036a18393 100644 --- a/src/db/tables/subscriber_email_preferences.ts +++ b/src/db/tables/subscriber_email_preferences.ts @@ -5,6 +5,7 @@ import createDbConnection from "../connect"; import { logger } from "../../app/functions/server/logging"; import { captureException } from "@sentry/node"; +import { randomToken } from "../../utils/email"; const knex = createDbConnection(); @@ -47,7 +48,7 @@ async function addEmailPreferenceForSubscriber( res = await knex("subscriber_email_preferences") .insert({ subscriber_id: subscriberId, - unsubscribe_token: preference.unsubscribe_token || "", + unsubscribe_token: preference.unsubscribe_token || randomToken(), monthly_monitor_report_free: preference.monthly_monitor_report_free ?? true, // @ts-ignore knex.fn.now() results in it being set to a date, diff --git a/src/utils/email.test.ts b/src/utils/email.test.ts index 77a633b315..03bff7c5b2 100644 --- a/src/utils/email.test.ts +++ b/src/utils/email.test.ts @@ -4,6 +4,7 @@ import { test, expect, jest } from "@jest/globals"; import type { createTransport, Transporter } from "nodemailer"; +import { randomToken } from "./email"; jest.mock("nodemailer", () => { return { @@ -135,3 +136,8 @@ test("EmailUtils.init with empty host uses jsonTransport. logs messages", async ).toBe(sendMailInfo); expect(mockedConsoleInfo).toHaveBeenCalledWith("sent_email", sendMailInfo); }); + +test("randomToken returns a random token of 2xlength (because of hex)", () => { + const token = randomToken(32); + expect(token).toHaveLength(64); +}); diff --git a/src/utils/email.ts b/src/utils/email.ts index cd6769d213..44e548ba5f 100644 --- a/src/utils/email.ts +++ b/src/utils/email.ts @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { createTransport, Transporter } from "nodemailer"; +import crypto from "crypto"; import { SentMessageInfo } from "nodemailer/lib/smtp-transport/index.js"; import { getEnvVarsOrThrow } from "../envVars"; @@ -84,4 +85,8 @@ async function sendEmail( } } -export { initEmail, sendEmail }; +function randomToken(length: number = 64) { + return crypto.randomBytes(length).toString("hex"); +} + +export { initEmail, sendEmail, randomToken };