mirror of
https://github.com/formbricks/formbricks.git
synced 2026-02-04 22:00:01 -06:00
fix: fixes personalized links when single use id is enabled (#6270)
This commit is contained in:
@@ -92,7 +92,7 @@ export const GET = async (request: Request, props: { params: Promise<TContactLin
|
||||
});
|
||||
}
|
||||
|
||||
const surveyUrlResult = getContactSurveyLink(params.contactId, params.surveyId, 7);
|
||||
const surveyUrlResult = await getContactSurveyLink(params.contactId, params.surveyId, 7);
|
||||
|
||||
if (!surveyUrlResult.ok) {
|
||||
return handleApiError(request, surveyUrlResult.error);
|
||||
|
||||
@@ -82,11 +82,11 @@ export const GET = async (
|
||||
}
|
||||
|
||||
// Generate survey links for each contact
|
||||
const contactLinks = contacts
|
||||
.map((contact) => {
|
||||
const contactLinks = await Promise.all(
|
||||
contacts.map(async (contact) => {
|
||||
const { contactId, attributes } = contact;
|
||||
|
||||
const surveyUrlResult = getContactSurveyLink(
|
||||
const surveyUrlResult = await getContactSurveyLink(
|
||||
contactId,
|
||||
params.surveyId,
|
||||
query?.expirationDays || undefined
|
||||
@@ -107,10 +107,11 @@ export const GET = async (
|
||||
expiresAt,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
);
|
||||
|
||||
const filteredContactLinks = contactLinks.filter(Boolean);
|
||||
return responses.successResponse({
|
||||
data: contactLinks,
|
||||
data: filteredContactLinks,
|
||||
meta,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { ENCRYPTION_KEY } from "@/lib/constants";
|
||||
import * as crypto from "@/lib/crypto";
|
||||
import { getPublicDomain } from "@/lib/getPublicUrl";
|
||||
import { generateSurveySingleUseId } from "@/lib/utils/single-use-surveys";
|
||||
import { getSurvey } from "@/modules/survey/lib/survey";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import * as contactSurveyLink from "./contact-survey-link";
|
||||
|
||||
// Mock all modules needed (this gets hoisted to the top of the file)
|
||||
@@ -33,12 +36,22 @@ vi.mock("@/lib/crypto", () => ({
|
||||
symmetricDecrypt: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/survey/lib/survey", () => ({
|
||||
getSurvey: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/utils/single-use-surveys", () => ({
|
||||
generateSurveySingleUseId: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("Contact Survey Link", () => {
|
||||
const mockContactId = "contact-123";
|
||||
const mockSurveyId = "survey-456";
|
||||
const mockToken = "mock.jwt.token";
|
||||
const mockEncryptedContactId = "encrypted-contact-id";
|
||||
const mockEncryptedSurveyId = "encrypted-survey-id";
|
||||
const mockedGetSurvey = vi.mocked(getSurvey);
|
||||
const mockedGenerateSurveySingleUseId = vi.mocked(generateSurveySingleUseId);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -60,11 +73,17 @@ describe("Contact Survey Link", () => {
|
||||
contactId: mockEncryptedContactId,
|
||||
surveyId: mockEncryptedSurveyId,
|
||||
} as any);
|
||||
|
||||
mockedGetSurvey.mockResolvedValue({
|
||||
id: mockSurveyId,
|
||||
singleUse: { enabled: false, isEncrypted: false },
|
||||
} as TSurvey);
|
||||
mockedGenerateSurveySingleUseId.mockReturnValue("single-use-id");
|
||||
});
|
||||
|
||||
describe("getContactSurveyLink", () => {
|
||||
test("creates a survey link with encrypted contact and survey IDs", () => {
|
||||
const result = contactSurveyLink.getContactSurveyLink(mockContactId, mockSurveyId);
|
||||
test("creates a survey link with encrypted contact and survey IDs", async () => {
|
||||
const result = await contactSurveyLink.getContactSurveyLink(mockContactId, mockSurveyId);
|
||||
|
||||
// Verify encryption was called for both IDs
|
||||
expect(crypto.symmetricEncrypt).toHaveBeenCalledWith(mockContactId, ENCRYPTION_KEY);
|
||||
@@ -85,11 +104,13 @@ describe("Contact Survey Link", () => {
|
||||
ok: true,
|
||||
data: `${getPublicDomain()}/c/${mockToken}`,
|
||||
});
|
||||
|
||||
expect(mockedGenerateSurveySingleUseId).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("adds expiration to the token when expirationDays is provided", () => {
|
||||
test("adds expiration to the token when expirationDays is provided", async () => {
|
||||
const expirationDays = 7;
|
||||
contactSurveyLink.getContactSurveyLink(mockContactId, mockSurveyId, expirationDays);
|
||||
await contactSurveyLink.getContactSurveyLink(mockContactId, mockSurveyId, expirationDays);
|
||||
|
||||
// Verify JWT sign was called with expiration
|
||||
expect(jwt.sign).toHaveBeenCalledWith(
|
||||
@@ -102,7 +123,55 @@ describe("Contact Survey Link", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("throws an error when ENCRYPTION_KEY is not available", async () => {
|
||||
test("returns a not_found error when survey does not exist", async () => {
|
||||
mockedGetSurvey.mockResolvedValue(null as unknown as TSurvey);
|
||||
|
||||
const result = await contactSurveyLink.getContactSurveyLink(mockContactId, "unfound-survey-id");
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: false,
|
||||
error: {
|
||||
type: "not_found",
|
||||
message: "Survey not found",
|
||||
details: [{ field: "surveyId", issue: "not_found" }],
|
||||
},
|
||||
});
|
||||
expect(mockedGetSurvey).toHaveBeenCalledWith("unfound-survey-id");
|
||||
});
|
||||
|
||||
test("creates a link with unencrypted single use ID when enabled", async () => {
|
||||
mockedGetSurvey.mockResolvedValue({
|
||||
id: mockSurveyId,
|
||||
singleUse: { enabled: true, isEncrypted: false },
|
||||
} as TSurvey);
|
||||
mockedGenerateSurveySingleUseId.mockReturnValue("suId-unencrypted");
|
||||
|
||||
const result = await contactSurveyLink.getContactSurveyLink(mockContactId, mockSurveyId);
|
||||
|
||||
expect(mockedGenerateSurveySingleUseId).toHaveBeenCalledWith(false);
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
data: `${getPublicDomain()}/c/${mockToken}?suId=suId-unencrypted`,
|
||||
});
|
||||
});
|
||||
|
||||
test("creates a link with encrypted single use ID when enabled and encrypted", async () => {
|
||||
mockedGetSurvey.mockResolvedValue({
|
||||
id: mockSurveyId,
|
||||
singleUse: { enabled: true, isEncrypted: true },
|
||||
} as TSurvey);
|
||||
mockedGenerateSurveySingleUseId.mockReturnValue("suId-encrypted");
|
||||
|
||||
const result = await contactSurveyLink.getContactSurveyLink(mockContactId, mockSurveyId);
|
||||
|
||||
expect(mockedGenerateSurveySingleUseId).toHaveBeenCalledWith(true);
|
||||
expect(result).toEqual({
|
||||
ok: true,
|
||||
data: `${getPublicDomain()}/c/${mockToken}?suId=suId-encrypted`,
|
||||
});
|
||||
});
|
||||
|
||||
test("returns an error when ENCRYPTION_KEY is not available", async () => {
|
||||
// Reset modules so the new mock is used by the module under test
|
||||
vi.resetModules();
|
||||
// Re‑mock constants to simulate missing ENCRYPTION_KEY
|
||||
@@ -113,7 +182,7 @@ describe("Contact Survey Link", () => {
|
||||
// Re‑import the modules so they pick up the new mock
|
||||
const { getContactSurveyLink } = await import("./contact-survey-link");
|
||||
|
||||
const result = getContactSurveyLink(mockContactId, mockSurveyId);
|
||||
const result = await getContactSurveyLink(mockContactId, mockSurveyId);
|
||||
expect(result).toEqual({
|
||||
ok: false,
|
||||
error: {
|
||||
@@ -141,7 +210,7 @@ describe("Contact Survey Link", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("throws an error when token verification fails", () => {
|
||||
test("returns an error when token verification fails", () => {
|
||||
vi.mocked(jwt.verify).mockImplementation(() => {
|
||||
throw new Error("Token verification failed");
|
||||
});
|
||||
@@ -157,7 +226,7 @@ describe("Contact Survey Link", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("throws an error when token has invalid format", () => {
|
||||
test("returns an error when token has invalid format", () => {
|
||||
// Mock JWT.verify to return an incomplete payload
|
||||
vi.mocked(jwt.verify).mockReturnValue({
|
||||
// Missing surveyId
|
||||
@@ -178,7 +247,7 @@ describe("Contact Survey Link", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("throws an error when ENCRYPTION_KEY is not available", async () => {
|
||||
test("returns an error when ENCRYPTION_KEY is not available", async () => {
|
||||
vi.resetModules();
|
||||
vi.doMock("@/lib/constants", () => ({
|
||||
ENCRYPTION_KEY: undefined,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { ENCRYPTION_KEY } from "@/lib/constants";
|
||||
import { symmetricDecrypt, symmetricEncrypt } from "@/lib/crypto";
|
||||
import { getPublicDomain } from "@/lib/getPublicUrl";
|
||||
import { generateSurveySingleUseId } from "@/lib/utils/single-use-surveys";
|
||||
import { ApiErrorResponseV2 } from "@/modules/api/v2/types/api-error";
|
||||
import { getSurvey } from "@/modules/survey/lib/survey";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { Result, err, ok } from "@formbricks/types/error-handlers";
|
||||
|
||||
// Creates an encrypted personalized survey link for a contact
|
||||
export const getContactSurveyLink = (
|
||||
export const getContactSurveyLink = async (
|
||||
contactId: string,
|
||||
surveyId: string,
|
||||
expirationDays?: number
|
||||
): Result<string, ApiErrorResponseV2> => {
|
||||
): Promise<Result<string, ApiErrorResponseV2>> => {
|
||||
if (!ENCRYPTION_KEY) {
|
||||
return err({
|
||||
type: "internal_server_error",
|
||||
@@ -19,10 +21,27 @@ export const getContactSurveyLink = (
|
||||
});
|
||||
}
|
||||
|
||||
const survey = await getSurvey(surveyId);
|
||||
if (!survey) {
|
||||
return err({
|
||||
type: "not_found",
|
||||
message: "Survey not found",
|
||||
details: [{ field: "surveyId", issue: "not_found" }],
|
||||
});
|
||||
}
|
||||
|
||||
const { enabled: isSingleUseEnabled, isEncrypted: isSingleUseEncrypted } = survey.singleUse ?? {};
|
||||
|
||||
// Encrypt the contact and survey IDs
|
||||
const encryptedContactId = symmetricEncrypt(contactId, ENCRYPTION_KEY);
|
||||
const encryptedSurveyId = symmetricEncrypt(surveyId, ENCRYPTION_KEY);
|
||||
|
||||
let singleUseId: string | undefined;
|
||||
|
||||
if (isSingleUseEnabled) {
|
||||
singleUseId = generateSurveySingleUseId(isSingleUseEncrypted ?? false);
|
||||
}
|
||||
|
||||
// Create JWT payload with encrypted IDs
|
||||
const payload = {
|
||||
contactId: encryptedContactId,
|
||||
@@ -43,7 +62,9 @@ export const getContactSurveyLink = (
|
||||
const token = jwt.sign(payload, ENCRYPTION_KEY, tokenOptions);
|
||||
|
||||
// Return the personalized URL
|
||||
return ok(`${getPublicDomain()}/c/${token}`);
|
||||
return singleUseId
|
||||
? ok(`${getPublicDomain()}/c/${token}?suId=${singleUseId}`)
|
||||
: ok(`${getPublicDomain()}/c/${token}`);
|
||||
};
|
||||
|
||||
// Validates and decrypts a contact survey JWT token
|
||||
@@ -59,7 +80,10 @@ export const verifyContactSurveyToken = (
|
||||
|
||||
try {
|
||||
// Verify the token
|
||||
const decoded = jwt.verify(token, ENCRYPTION_KEY) as { contactId: string; surveyId: string };
|
||||
const decoded = jwt.verify(token, ENCRYPTION_KEY) as {
|
||||
contactId: string;
|
||||
surveyId: string;
|
||||
};
|
||||
|
||||
if (!decoded || !decoded.contactId || !decoded.surveyId) {
|
||||
throw err("Invalid token format");
|
||||
|
||||
@@ -501,11 +501,11 @@ export const generatePersonalLinks = async (surveyId: string, segmentId: string,
|
||||
}
|
||||
|
||||
// Generate survey links for each contact
|
||||
const contactLinks = contactsResult
|
||||
.map((contact) => {
|
||||
const contactLinks = await Promise.all(
|
||||
contactsResult.map(async (contact) => {
|
||||
const { contactId, attributes } = contact;
|
||||
|
||||
const surveyUrlResult = getContactSurveyLink(contactId, surveyId, expirationDays);
|
||||
const surveyUrlResult = await getContactSurveyLink(contactId, surveyId, expirationDays);
|
||||
|
||||
if (!surveyUrlResult.ok) {
|
||||
logger.error(
|
||||
@@ -522,7 +522,8 @@ export const generatePersonalLinks = async (surveyId: string, segmentId: string,
|
||||
expirationDays,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
);
|
||||
|
||||
return contactLinks;
|
||||
const filteredContactLinks = contactLinks.filter(Boolean);
|
||||
return filteredContactLinks;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getSurvey } from "@/modules/survey/lib/survey";
|
||||
import { SurveyInactive } from "@/modules/survey/link/components/survey-inactive";
|
||||
import { renderSurvey } from "@/modules/survey/link/components/survey-renderer";
|
||||
import { getExistingContactResponse } from "@/modules/survey/link/lib/data";
|
||||
import { checkAndValidateSingleUseId } from "@/modules/survey/link/lib/helper";
|
||||
import { getBasicSurveyMetadata } from "@/modules/survey/link/lib/metadata-utils";
|
||||
import { getProjectByEnvironmentId } from "@/modules/survey/link/lib/project";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
@@ -14,6 +15,7 @@ interface ContactSurveyPageProps {
|
||||
jwt: string;
|
||||
}>;
|
||||
searchParams: Promise<{
|
||||
suId?: string;
|
||||
verify?: string;
|
||||
lang?: string;
|
||||
embed?: string;
|
||||
@@ -46,9 +48,10 @@ export const generateMetadata = async (props: ContactSurveyPageProps): Promise<M
|
||||
export const ContactSurveyPage = async (props: ContactSurveyPageProps) => {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
|
||||
const t = await getTranslate();
|
||||
const { jwt } = params;
|
||||
const { preview } = searchParams;
|
||||
const { preview, suId } = searchParams;
|
||||
|
||||
const result = verifyContactSurveyToken(jwt);
|
||||
if (!result.ok) {
|
||||
@@ -62,6 +65,7 @@ export const ContactSurveyPage = async (props: ContactSurveyPageProps) => {
|
||||
// So we show SurveyInactive without project data (shows branding by default for backward compatibility)
|
||||
return <SurveyInactive status="link invalid" />;
|
||||
}
|
||||
|
||||
const { surveyId, contactId } = result.data;
|
||||
|
||||
const existingResponse = await getExistingContactResponse(surveyId, contactId)();
|
||||
@@ -81,10 +85,26 @@ export const ContactSurveyPage = async (props: ContactSurveyPageProps) => {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const isSingleUseSurvey = survey?.singleUse?.enabled;
|
||||
const isSingleUseSurveyEncrypted = survey?.singleUse?.isEncrypted;
|
||||
|
||||
let singleUseId: string | undefined = undefined;
|
||||
|
||||
if (isSingleUseSurvey) {
|
||||
const validatedSingleUseId = checkAndValidateSingleUseId(suId, isSingleUseSurveyEncrypted);
|
||||
if (!validatedSingleUseId) {
|
||||
const project = await getProjectByEnvironmentId(survey.environmentId);
|
||||
return <SurveyInactive status="link invalid" project={project ?? undefined} />;
|
||||
}
|
||||
|
||||
singleUseId = validatedSingleUseId;
|
||||
}
|
||||
|
||||
return renderSurvey({
|
||||
survey,
|
||||
searchParams,
|
||||
contactId,
|
||||
isPreview,
|
||||
singleUseId,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { validateSurveySingleUseId } from "@/app/lib/singleUseSurveys";
|
||||
import { verifyTokenForLinkSurvey } from "@/lib/jwt";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { getEmailVerificationDetails } from "./helper";
|
||||
import { checkAndValidateSingleUseId, getEmailVerificationDetails } from "./helper";
|
||||
|
||||
vi.mock("@/lib/jwt", () => ({
|
||||
verifyTokenForLinkSurvey: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/lib/singleUseSurveys", () => ({
|
||||
validateSurveySingleUseId: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("getEmailVerificationDetails", () => {
|
||||
const mockedVerifyTokenForLinkSurvey = vi.mocked(verifyTokenForLinkSurvey);
|
||||
const testSurveyId = "survey-123";
|
||||
@@ -54,3 +59,82 @@ describe("getEmailVerificationDetails", () => {
|
||||
expect(mockedVerifyTokenForLinkSurvey).toHaveBeenCalledWith(testToken, testSurveyId);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkAndValidateSingleUseId", () => {
|
||||
const mockedValidateSurveySingleUseId = vi.mocked(validateSurveySingleUseId);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
test("returns null when no suid is provided", () => {
|
||||
const result = checkAndValidateSingleUseId();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(mockedValidateSurveySingleUseId).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns null when suid is empty string", () => {
|
||||
const result = checkAndValidateSingleUseId("");
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(mockedValidateSurveySingleUseId).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns suid as-is when isEncrypted is false", () => {
|
||||
const testSuid = "plain-suid-123";
|
||||
const result = checkAndValidateSingleUseId(testSuid, false);
|
||||
|
||||
expect(result).toBe(testSuid);
|
||||
expect(mockedValidateSurveySingleUseId).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns suid as-is when isEncrypted is not provided (defaults to false)", () => {
|
||||
const testSuid = "plain-suid-123";
|
||||
const result = checkAndValidateSingleUseId(testSuid);
|
||||
|
||||
expect(result).toBe(testSuid);
|
||||
expect(mockedValidateSurveySingleUseId).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns validated suid when isEncrypted is true and validation succeeds", () => {
|
||||
const encryptedSuid = "encrypted-suid-123";
|
||||
const validatedSuid = "validated-suid-456";
|
||||
mockedValidateSurveySingleUseId.mockReturnValueOnce(validatedSuid);
|
||||
|
||||
const result = checkAndValidateSingleUseId(encryptedSuid, true);
|
||||
|
||||
expect(result).toBe(validatedSuid);
|
||||
expect(mockedValidateSurveySingleUseId).toHaveBeenCalledWith(encryptedSuid);
|
||||
});
|
||||
|
||||
test("returns null when isEncrypted is true and validation returns undefined", () => {
|
||||
const encryptedSuid = "invalid-encrypted-suid";
|
||||
mockedValidateSurveySingleUseId.mockReturnValueOnce(undefined);
|
||||
|
||||
const result = checkAndValidateSingleUseId(encryptedSuid, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(mockedValidateSurveySingleUseId).toHaveBeenCalledWith(encryptedSuid);
|
||||
});
|
||||
|
||||
test("returns null when isEncrypted is true and validation returns empty string", () => {
|
||||
const encryptedSuid = "invalid-encrypted-suid";
|
||||
mockedValidateSurveySingleUseId.mockReturnValueOnce("");
|
||||
|
||||
const result = checkAndValidateSingleUseId(encryptedSuid, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(mockedValidateSurveySingleUseId).toHaveBeenCalledWith(encryptedSuid);
|
||||
});
|
||||
|
||||
test("returns null when isEncrypted is true and validation returns null", () => {
|
||||
const encryptedSuid = "invalid-encrypted-suid";
|
||||
mockedValidateSurveySingleUseId.mockReturnValueOnce(null as any);
|
||||
|
||||
const result = checkAndValidateSingleUseId(encryptedSuid, true);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(mockedValidateSurveySingleUseId).toHaveBeenCalledWith(encryptedSuid);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import "server-only";
|
||||
import { validateSurveySingleUseId } from "@/app/lib/singleUseSurveys";
|
||||
import { verifyTokenForLinkSurvey } from "@/lib/jwt";
|
||||
|
||||
interface emailVerificationDetails {
|
||||
@@ -25,3 +26,15 @@ export const getEmailVerificationDetails = async (
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const checkAndValidateSingleUseId = (suid?: string, isEncrypted = false): string | null => {
|
||||
if (!suid?.trim()) return null;
|
||||
|
||||
if (isEncrypted) {
|
||||
const validatedSingleUseId = validateSurveySingleUseId(suid);
|
||||
if (!validatedSingleUseId) return null;
|
||||
return validatedSingleUseId;
|
||||
}
|
||||
|
||||
return suid;
|
||||
};
|
||||
|
||||
@@ -13,6 +13,16 @@ import { logger } from "@formbricks/logger";
|
||||
import { TSurvey } from "@formbricks/types/surveys/types";
|
||||
import { LinkSurveyPage, generateMetadata } from "./page";
|
||||
|
||||
// Mock server-side constants to prevent client-side access
|
||||
vi.mock("@/lib/constants", () => ({
|
||||
IS_FORMBRICKS_CLOUD: false,
|
||||
IS_RECAPTCHA_CONFIGURED: false,
|
||||
RECAPTCHA_SITE_KEY: "test-key",
|
||||
IMPRINT_URL: "https://example.com/imprint",
|
||||
PRIVACY_URL: "https://example.com/privacy",
|
||||
ENCRYPTION_KEY: "0".repeat(32),
|
||||
}));
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("next/navigation", () => ({
|
||||
notFound: vi.fn(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { validateSurveySingleUseId } from "@/app/lib/singleUseSurveys";
|
||||
import { SurveyInactive } from "@/modules/survey/link/components/survey-inactive";
|
||||
import { renderSurvey } from "@/modules/survey/link/components/survey-renderer";
|
||||
import { getResponseBySingleUseId, getSurveyWithMetadata } from "@/modules/survey/link/lib/data";
|
||||
import { checkAndValidateSingleUseId } from "@/modules/survey/link/lib/helper";
|
||||
import { getProjectByEnvironmentId } from "@/modules/survey/link/lib/project";
|
||||
import { getMetadataForLinkSurvey } from "@/modules/survey/link/metadata";
|
||||
import type { Metadata } from "next";
|
||||
@@ -60,23 +60,13 @@ export const LinkSurveyPage = async (props: LinkSurveyPageProps) => {
|
||||
let singleUseId: string | undefined = undefined;
|
||||
|
||||
if (isSingleUseSurvey) {
|
||||
// check if the single use id is present for single use surveys
|
||||
if (!suId) {
|
||||
const validatedSingleUseId = checkAndValidateSingleUseId(suId, isSingleUseSurveyEncrypted);
|
||||
if (!validatedSingleUseId) {
|
||||
const project = await getProjectByEnvironmentId(survey.environmentId);
|
||||
return <SurveyInactive status="link invalid" project={project ?? undefined} />;
|
||||
}
|
||||
|
||||
// if encryption is enabled, validate the single use id
|
||||
let validatedSingleUseId: string | undefined = undefined;
|
||||
if (isSingleUseSurveyEncrypted) {
|
||||
validatedSingleUseId = validateSurveySingleUseId(suId);
|
||||
if (!validatedSingleUseId) {
|
||||
const project = await getProjectByEnvironmentId(survey.environmentId);
|
||||
return <SurveyInactive status="link invalid" project={project ?? undefined} />;
|
||||
}
|
||||
}
|
||||
// if encryption is disabled, use the suId as is
|
||||
singleUseId = validatedSingleUseId ?? suId;
|
||||
singleUseId = validatedSingleUseId;
|
||||
}
|
||||
|
||||
let singleUseResponse;
|
||||
|
||||
Reference in New Issue
Block a user