From 2f7e80857c4ffb05a2dd8d793044ecc8ec1dd645 Mon Sep 17 00:00:00 2001 From: semantabhandari Date: Mon, 6 Jul 2026 14:35:44 +0545 Subject: [PATCH] fix(fastify): use unprefixed email in duplicate-email warning body The duplicate-email registration warning rendered the internal tenant-namespaced address (_) in the email body instead of the clean email the user typed. In the emailPasswordSignUp override the email is namespaced per tenant before storage, and the warning's templateData.emailId was set from the prefixed input.email rather than the original clean value. The `to` field was already correct. Pass originalEmail to templateData.emailId so {{emailId}} renders the raw address. Adds a test asserting the warning's templateData.emailId equals the raw email with no _ prefix, and bumps the version to 0.29.2. --- package.json | 2 +- packages/fastify/package.json | 2 +- .../__test__/emailPasswordSignUp.test.ts | 72 +++++++++++++++++++ .../emailPasswordSignUp.ts | 2 +- packages/react/package.json | 2 +- packages/vue/package.json | 2 +- 6 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 packages/fastify/src/supertokens/recipes/third-party-email-password/__test__/emailPasswordSignUp.test.ts diff --git a/package.json b/package.json index a96fcefb..74c04db3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@prefabs.tech/saas", - "version": "0.29.1", + "version": "0.29.2", "private": true, "repository": { "type": "git", diff --git a/packages/fastify/package.json b/packages/fastify/package.json index d8076795..92cecf27 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -1,6 +1,6 @@ { "name": "@prefabs.tech/saas-fastify", - "version": "0.29.1", + "version": "0.29.2", "description": "saas plugin for fastify", "homepage": "https://github.com/prefabs-tech/saas/tree/main/packages/fastify#readme", "repository": { diff --git a/packages/fastify/src/supertokens/recipes/third-party-email-password/__test__/emailPasswordSignUp.test.ts b/packages/fastify/src/supertokens/recipes/third-party-email-password/__test__/emailPasswordSignUp.test.ts new file mode 100644 index 00000000..97078992 --- /dev/null +++ b/packages/fastify/src/supertokens/recipes/third-party-email-password/__test__/emailPasswordSignUp.test.ts @@ -0,0 +1,72 @@ +import type { FastifyInstance } from "fastify"; +import type { RecipeInterface } from "supertokens-node/recipe/thirdpartyemailpassword"; + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const sendEmailMock = vi.fn(); + +vi.mock("@prefabs.tech/fastify-user", () => ({ + areRolesExist: vi.fn().mockResolvedValue(true), + getUserService: vi.fn(), + sendEmail: (...arguments_: unknown[]) => sendEmailMock(...arguments_), + verifyEmail: vi.fn(), +})); + +// Imported after the mock so the override picks up the mocked sendEmail. +const { default: emailPasswordSignUp } = await import("../emailPasswordSignUp"); + +const accountId = "76948988-1313-4ff5-9138-3dd3a3c95c70_"; +const cleanEmail = "user@example.com"; + +const buildFastify = () => + ({ + config: { + user: { + supertokens: { + sendUserAlreadyExistsWarning: true, + }, + }, + }, + log: { + error: vi.fn(), + }, + slonik: {}, + }) as unknown as FastifyInstance; + +const buildOriginalImplementation = () => + ({ + emailPasswordSignUp: vi.fn().mockResolvedValue({ + status: "EMAIL_ALREADY_EXISTS_ERROR", + }), + }) as unknown as RecipeInterface; + +describe("emailPasswordSignUp duplicate email warning", () => { + beforeEach(() => { + sendEmailMock.mockClear(); + }); + + it("sends the warning with the unprefixed email in templateData", async () => { + const fastify = buildFastify(); + const handler = emailPasswordSignUp(buildOriginalImplementation(), fastify); + + await handler({ + email: cleanEmail, + password: "password", + tenantId: "public", + userContext: { + authEmailPrefix: accountId, + }, + } as Parameters[0]); + + expect(sendEmailMock).toHaveBeenCalledTimes(1); + + const payload = sendEmailMock.mock.calls[0][0]; + + // Body renders {{emailId}} verbatim, so it must be the clean email. + expect(payload.templateData.emailId).toBe(cleanEmail); + expect(payload.templateData.emailId).not.toContain(accountId); + + // Recipient is also the clean email. + expect(payload.to).toBe(cleanEmail); + }); +}); diff --git a/packages/fastify/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts b/packages/fastify/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts index e14e03a2..f308044e 100644 --- a/packages/fastify/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts +++ b/packages/fastify/src/supertokens/recipes/third-party-email-password/emailPasswordSignUp.ts @@ -155,7 +155,7 @@ const emailPasswordSignUp = ( config.user.emailOverrides?.duplicateEmail?.subject || "Duplicate Email Registration", templateData: { - emailId: input.email, + emailId: originalEmail, }, templateName: config.user.emailOverrides?.duplicateEmail?.templateName || diff --git a/packages/react/package.json b/packages/react/package.json index 4e664c71..85adc2bb 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@prefabs.tech/saas-react", - "version": "0.29.1", + "version": "0.29.2", "description": "Saas plugin for react", "type": "module", "exports": { diff --git a/packages/vue/package.json b/packages/vue/package.json index 86fb9af5..6735487a 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@prefabs.tech/saas-vue", - "version": "0.29.1", + "version": "0.29.2", "description": "Saas plugin for vue", "type": "module", "exports": {