From f21aa90113b674234d34c8b9edeee6d3760456a2 Mon Sep 17 00:00:00 2001 From: Nadav Nir Date: Wed, 17 Jun 2026 10:54:16 +0200 Subject: [PATCH] fix: add 24h expiry to email verification token The verify JWT was signed without expiresIn, making it permanently valid. Now uses the existing VERIFY_LIFESPAN_MS constant (24h). Closes https://github.com/need4deed-org/be/issues/651 Co-Authored-By: Claude Sonnet 4.6 --- src/services/notify/events/email-verification.ts | 10 +++++----- .../services/notify/email-verification.test.ts | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/services/notify/events/email-verification.ts b/src/services/notify/events/email-verification.ts index fda6fb8f..d7fe5f97 100644 --- a/src/services/notify/events/email-verification.ts +++ b/src/services/notify/events/email-verification.ts @@ -5,6 +5,7 @@ import { emailTemplateTtlMs, emailVerificationManifestUrl, urlEmailVerification, + VERIFY_LIFESPAN_MS, } from "../../../config/constants"; import type User from "../../../data/entity/user.entity"; import { fetchJsonFromUrl } from "../../../data/utils"; @@ -114,11 +115,10 @@ export async function sendEmailVerification( throw new Error("User email is required for verification"); } - const token = jwt.sign({ - id: user.id, - email: user.email, - type: "verify" as TokenType, - }); + const token = jwt.sign( + { id: user.id, email: user.email, type: "verify" as TokenType }, + { expiresIn: `${VERIFY_LIFESPAN_MS}` }, + ); const url = `${urlEmailVerification}/${token}`; logger.debug(`sendEmailVerification: ${user.email}, url: ${url}`); diff --git a/src/test/services/notify/email-verification.test.ts b/src/test/services/notify/email-verification.test.ts index 1a616bed..cee70479 100644 --- a/src/test/services/notify/email-verification.test.ts +++ b/src/test/services/notify/email-verification.test.ts @@ -1,6 +1,6 @@ import { Lang } from "need4deed-sdk"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { urlEmailVerification } from "../../../config/constants"; +import { urlEmailVerification, VERIFY_LIFESPAN_MS } from "../../../config/constants"; import { fetchJsonFromUrl } from "../../../data/utils"; import { resetVerificationTemplateCache, @@ -12,7 +12,8 @@ vi.mock("../../../data/utils", () => ({ })); const send = vi.fn(); -const deps = { email: { send }, jwt: { sign: () => "tok" } } as any; +const sign = vi.fn(() => "tok"); +const deps = { email: { send }, jwt: { sign } } as any; const user = (over: any = {}) => ({ id: 1, email: "u@x.de", ...over }); const expectedUrl = `${urlEmailVerification}/tok`; @@ -90,4 +91,15 @@ describe("sendEmailVerification", () => { sendEmailVerification(deps, user({ email: undefined })), ).rejects.toThrow("User email is required"); }); + + it("signs the token with VERIFY_LIFESPAN_MS as expiresIn", async () => { + vi.mocked(fetchJsonFromUrl).mockResolvedValue(manifest); + + await sendEmailVerification(deps, user()); + + expect(sign).toHaveBeenCalledWith( + expect.objectContaining({ type: "verify" }), + expect.objectContaining({ expiresIn: `${VERIFY_LIFESPAN_MS}` }), + ); + }); });