From 48dde246e92fd2ed94c84bf39389078ab9ec639a Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Tue, 21 Nov 2023 22:33:01 +0530 Subject: [PATCH] test: Add more orgs tests (#12241) --- apps/web/pages/signup.tsx | 4 +- apps/web/playwright/fixtures/clipboard.ts | 34 +++++ apps/web/playwright/fixtures/users.ts | 20 ++- apps/web/playwright/lib/fixtures.ts | 9 ++ apps/web/playwright/lib/testUtils.ts | 11 ++ apps/web/playwright/organization/expects.ts | 28 ++++ .../organization/organization-creation.e2e.ts | 143 ++++++++++++++++++ .../organization-invitation.e2e.ts | 119 +++++++++++++++ package.json | 1 + .../organizations/pages/settings/members.tsx | 18 --- .../components/MemberInvitationModal.tsx | 12 +- .../components/UserTable/UserListTable.tsx | 7 +- .../viewer/organizations/create.handler.ts | 8 +- .../organizations/verifyCode.handler.ts | 6 +- yarn.lock | 12 ++ 15 files changed, 397 insertions(+), 35 deletions(-) create mode 100644 apps/web/playwright/fixtures/clipboard.ts create mode 100644 apps/web/playwright/organization/expects.ts create mode 100644 apps/web/playwright/organization/organization-creation.e2e.ts create mode 100644 apps/web/playwright/organization/organization-invitation.e2e.ts diff --git a/apps/web/pages/signup.tsx b/apps/web/pages/signup.tsx index 0b0cb0e5a8..041dcd943d 100644 --- a/apps/web/pages/signup.tsx +++ b/apps/web/pages/signup.tsx @@ -37,7 +37,7 @@ type SignupProps = inferSSRProps; const checkValidEmail = (email: string) => z.string().email().safeParse(email).success; const getOrgUsernameFromEmail = (email: string, autoAcceptEmailDomain: string) => { - const [emailUser, emailDomain] = email.split("@"); + const [emailUser, emailDomain = ""] = email.split("@"); const username = emailDomain === autoAcceptEmailDomain ? slugify(emailUser) @@ -143,7 +143,7 @@ export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoA methods.clearErrors("apiError"); } - if (methods.getValues().username === undefined && isOrgInviteByLink && orgAutoAcceptEmail) { + if (!methods.getValues().username && isOrgInviteByLink && orgAutoAcceptEmail) { methods.setValue( "username", getOrgUsernameFromEmail(methods.getValues().email, orgAutoAcceptEmail) diff --git a/apps/web/playwright/fixtures/clipboard.ts b/apps/web/playwright/fixtures/clipboard.ts new file mode 100644 index 0000000000..47cc92d95c --- /dev/null +++ b/apps/web/playwright/fixtures/clipboard.ts @@ -0,0 +1,34 @@ +import type { Page } from "@playwright/test"; + +declare global { + interface Window { + E2E_CLIPBOARD_VALUE?: string; + } +} + +export type Window = typeof window; +// creates the single server fixture +export const createClipboardFixture = (page: Page) => { + return { + reset: async () => { + await page.evaluate(() => { + delete window.E2E_CLIPBOARD_VALUE; + }); + }, + get: async () => { + return getClipboardValue({ page }); + }, + }; +}; + +function getClipboardValue({ page }: { page: Page }) { + return page.evaluate(() => { + return new Promise((resolve, reject) => { + setInterval(() => { + if (!window.E2E_CLIPBOARD_VALUE) return; + resolve(window.E2E_CLIPBOARD_VALUE); + }, 500); + setTimeout(() => reject(new Error("Timeout")), 1000); + }); + }); +} diff --git a/apps/web/playwright/fixtures/users.ts b/apps/web/playwright/fixtures/users.ts index 0f07d18507..ae5fbfbec2 100644 --- a/apps/web/playwright/fixtures/users.ts +++ b/apps/web/playwright/fixtures/users.ts @@ -86,12 +86,14 @@ const createTeamAndAddUser = async ( user, isUnpublished, isOrg, + isOrgVerified, hasSubteam, organizationId, }: { - user: { id: number; username: string | null; role?: MembershipRole }; + user: { id: number; email: string; username: string | null; role?: MembershipRole }; isUnpublished?: boolean; isOrg?: boolean; + isOrgVerified?: boolean; hasSubteam?: true; organizationId?: number | null; }, @@ -103,7 +105,14 @@ const createTeamAndAddUser = async ( }; data.metadata = { ...(isUnpublished ? { requestedSlug: slug } : {}), - ...(isOrg ? { isOrganization: true } : {}), + ...(isOrg + ? { + isOrganization: true, + isOrganizationVerified: !!isOrgVerified, + orgAutoAcceptEmail: user.email.split("@")[1], + isOrganizationConfigured: false, + } + : {}), }; data.slug = !isUnpublished ? slug : undefined; if (isOrg && hasSubteam) { @@ -145,6 +154,7 @@ export const createUsersFixture = (page: Page, emails: API | undefined, workerIn teamEventSlug?: string; teamEventLength?: number; isOrg?: boolean; + isOrgVerified?: boolean; hasSubteam?: true; isUnpublished?: true; } = {} @@ -292,9 +302,10 @@ export const createUsersFixture = (page: Page, emails: API | undefined, workerIn if (scenario.hasTeam) { const team = await createTeamAndAddUser( { - user: { id: user.id, username: user.username, role: "OWNER" }, + user: { id: user.id, email: user.email, username: user.username, role: "OWNER" }, isUnpublished: scenario.isUnpublished, isOrg: scenario.isOrg, + isOrgVerified: scenario.isOrgVerified, hasSubteam: scenario.hasSubteam, organizationId: opts?.organizationId, }, @@ -410,6 +421,9 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => { routingForms: user.routingForms, self, apiLogin: async () => apiLogin({ ...(await self()), password: user.username }, store.page), + /** + * @deprecated use apiLogin instead + */ login: async () => login({ ...(await self()), password: user.username }, store.page), logout: async () => { await page.goto("/auth/logout"); diff --git a/apps/web/playwright/lib/fixtures.ts b/apps/web/playwright/lib/fixtures.ts index 2e54268db3..cf66ebb2f9 100644 --- a/apps/web/playwright/lib/fixtures.ts +++ b/apps/web/playwright/lib/fixtures.ts @@ -4,10 +4,12 @@ import type { API } from "mailhog"; import mailhog from "mailhog"; import { IS_MAILHOG_ENABLED } from "@calcom/lib/constants"; +import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; import type { ExpectedUrlDetails } from "../../../../playwright.config"; import { createBookingsFixture } from "../fixtures/bookings"; +import { createClipboardFixture } from "../fixtures/clipboard"; import { createEmbedsFixture } from "../fixtures/embeds"; import { createOrgsFixture } from "../fixtures/orgs"; import { createPaymentsFixture } from "../fixtures/payments"; @@ -28,6 +30,7 @@ export interface Fixtures { emails?: API; routingForms: ReturnType; bookingPage: ReturnType; + clipboard: ReturnType; } declare global { @@ -85,6 +88,8 @@ export const test = base.extend({ const mailhogAPI = mailhog(); await use(mailhogAPI); } else { + //FIXME: Ideally we should error out here. If someone is running tests with mailhog disabled, they should be aware of it + logger.warn("Mailhog is not enabled - Skipping Emails verification"); await use(undefined); } }, @@ -92,4 +97,8 @@ export const test = base.extend({ const bookingPage = createBookingPageFixture(page); await use(bookingPage); }, + clipboard: async ({ page }, use) => { + const clipboard = createClipboardFixture(page); + await use(clipboard); + }, }); diff --git a/apps/web/playwright/lib/testUtils.ts b/apps/web/playwright/lib/testUtils.ts index b9cf3850d6..7038b656b1 100644 --- a/apps/web/playwright/lib/testUtils.ts +++ b/apps/web/playwright/lib/testUtils.ts @@ -1,11 +1,13 @@ import type { Frame, Page } from "@playwright/test"; import { expect } from "@playwright/test"; +import { createHash } from "crypto"; import EventEmitter from "events"; import type { IncomingMessage, ServerResponse } from "http"; import { createServer } from "http"; // eslint-disable-next-line no-restricted-imports import { noop } from "lodash"; import type { API, Messages } from "mailhog"; +import { totp } from "otplib"; import type { Prisma } from "@calcom/prisma/client"; import { BookingStatus } from "@calcom/prisma/enums"; @@ -278,3 +280,12 @@ export async function createUserWithSeatedEventAndAttendees( }); return { user, eventType, booking }; } + +export function generateTotpCode(email: string) { + const secret = createHash("md5") + .update(email + process.env.CALENDSO_ENCRYPTION_KEY) + .digest("hex"); + + totp.options = { step: 90 }; + return totp.generate(secret); +} diff --git a/apps/web/playwright/organization/expects.ts b/apps/web/playwright/organization/expects.ts new file mode 100644 index 0000000000..e5ba1a0e83 --- /dev/null +++ b/apps/web/playwright/organization/expects.ts @@ -0,0 +1,28 @@ +import type { Page } from "@playwright/test"; +import { expect } from "@playwright/test"; +import { JSDOM } from "jsdom"; +// eslint-disable-next-line no-restricted-imports +import type { API, Messages } from "mailhog"; + +import { getEmailsReceivedByUser } from "../lib/testUtils"; + +export async function expectInvitationEmailToBeReceived( + page: Page, + emails: API | undefined, + userEmail: string, + subject: string, + returnLink?: string +) { + if (!emails) return null; + // We need to wait for the email to go through, otherwise it will fail + // eslint-disable-next-line playwright/no-wait-for-timeout + await page.waitForTimeout(5000); + const receivedEmails = await getEmailsReceivedByUser({ emails, userEmail }); + expect(receivedEmails?.total).toBe(1); + const [firstReceivedEmail] = (receivedEmails as Messages).items; + expect(firstReceivedEmail.subject).toBe(subject); + if (!returnLink) return; + const dom = new JSDOM(firstReceivedEmail.html); + const anchor = dom.window.document.querySelector(`a[href*="${returnLink}"]`); + return anchor?.getAttribute("href"); +} diff --git a/apps/web/playwright/organization/organization-creation.e2e.ts b/apps/web/playwright/organization/organization-creation.e2e.ts new file mode 100644 index 0000000000..19b3477026 --- /dev/null +++ b/apps/web/playwright/organization/organization-creation.e2e.ts @@ -0,0 +1,143 @@ +import { expect } from "@playwright/test"; +import path from "path"; + +import { test } from "../lib/fixtures"; +import { generateTotpCode } from "../lib/testUtils"; +import { expectInvitationEmailToBeReceived } from "./expects"; + +test.afterAll(({ users, emails }) => { + users.deleteAll(); + emails?.deleteAll(); +}); + +function capitalize(text: string) { + if (!text) { + return text; + } + return text.charAt(0).toUpperCase() + text.slice(1); +} + +test.describe("Organization", () => { + test("should be able to create an organization and complete onboarding", async ({ + page, + users, + emails, + }) => { + const orgOwner = await users.create(); + const orgDomain = `${orgOwner.username}-org`; + const orgName = capitalize(`${orgOwner.username}-org`); + await orgOwner.apiLogin(); + await page.goto("/settings/organizations/new"); + await page.waitForLoadState("networkidle"); + + await test.step("Basic info", async () => { + // Check required fields + await page.locator("button[type=submit]").click(); + await expect(page.locator(".text-red-700")).toHaveCount(3); + + // Happy path + await page.locator("input[name=adminEmail]").fill(`john@${orgDomain}.com`); + expect(await page.locator("input[name=name]").inputValue()).toEqual(orgName); + expect(await page.locator("input[name=slug]").inputValue()).toEqual(orgDomain); + await page.locator("button[type=submit]").click(); + await page.waitForLoadState("networkidle"); + + // Check admin email about code verification + await expectInvitationEmailToBeReceived( + page, + emails, + `john@${orgOwner.username}-org.com`, + "Verify your email to create an organization" + ); + + await test.step("Verification", async () => { + // Code verification + await expect(page.locator("#modal-title")).toBeVisible(); + await page.locator("input[name='2fa1']").fill(generateTotpCode(`john@${orgDomain}.com`)); + await page.locator("button:text('Verify')").click(); + + // Check admin email about DNS pending action + await expectInvitationEmailToBeReceived( + page, + emails, + "admin@example.com", + "New organization created: pending action" + ); + + // Waiting to be in next step URL + await page.waitForURL("/settings/organizations/*/set-password"); + }); + }); + + await test.step("Admin password", async () => { + // Check required fields + await page.locator("button[type=submit]").click(); + await expect(page.locator(".text-red-700")).toHaveCount(3); // 3 password hints + + // Happy path + await page.locator("input[name='password']").fill("ADMIN_user2023$"); + await page.locator("button[type=submit]").click(); + + // Waiting to be in next step URL + await page.waitForURL("/settings/organizations/*/about"); + }); + + await test.step("About the organization", async () => { + // Choosing an avatar + await page.locator('button:text("Upload")').click(); + const fileChooserPromise = page.waitForEvent("filechooser"); + await page.getByText("Choose a file...").click(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(path.join(__dirname, "../../public/apple-touch-icon.png")); + await page.locator('button:text("Save")').click(); + + // About text + await page.locator('textarea[name="about"]').fill("This is a testing org"); + await page.locator("button[type=submit]").click(); + + // Waiting to be in next step URL + await page.waitForURL("/settings/organizations/*/onboard-admins"); + }); + + await test.step("On-board administrators", async () => { + // Required field + await page.locator("button[type=submit]").click(); + + // Happy path + await page.locator('textarea[name="emails"]').fill(`rick@${orgDomain}.com`); + await page.locator("button[type=submit]").click(); + + // Check if invited admin received the invitation email + await expectInvitationEmailToBeReceived( + page, + emails, + `rick@${orgDomain}.com`, + `${orgName}'s admin invited you to join the organization ${orgName} on Cal.com` + ); + + // Waiting to be in next step URL + await page.waitForURL("/settings/organizations/*/add-teams"); + }); + + await test.step("Create teams", async () => { + // Initial state + await expect(page.locator('input[name="teams.0.name"]')).toHaveCount(1); + await expect(page.locator('button:text("Continue")')).toBeDisabled(); + + // Filling one team + await page.locator('input[name="teams.0.name"]').fill("Marketing"); + await expect(page.locator('button:text("Continue")')).toBeEnabled(); + + // Adding another team + await page.locator('button:text("Add a team")').click(); + await expect(page.locator('button:text("Continue")')).toBeDisabled(); + await expect(page.locator('input[name="teams.1.name"]')).toHaveCount(1); + await page.locator('input[name="teams.1.name"]').fill("Sales"); + await expect(page.locator('button:text("Continue")')).toBeEnabled(); + + // Finishing the creation wizard + await page.locator('button:text("Continue")').click(); + await page.waitForURL("/event-types"); + }); + }); +}); diff --git a/apps/web/playwright/organization/organization-invitation.e2e.ts b/apps/web/playwright/organization/organization-invitation.e2e.ts new file mode 100644 index 0000000000..6561a01e55 --- /dev/null +++ b/apps/web/playwright/organization/organization-invitation.e2e.ts @@ -0,0 +1,119 @@ +import { expect } from "@playwright/test"; + +import { test } from "../lib/fixtures"; +import { expectInvitationEmailToBeReceived } from "./expects"; + +test.describe.configure({ mode: "parallel" }); + +test.afterEach(async ({ users, emails, clipboard }) => { + clipboard.reset(); + await users.deleteAll(); + emails?.deleteAll(); +}); + +test.describe("Organization", () => { + test("Invitation (non verified)", async ({ browser, page, users, emails, clipboard }) => { + const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true }); + const { team: org } = await orgOwner.getOrg(); + await orgOwner.apiLogin(); + await page.goto("/settings/organizations/members"); + await page.waitForLoadState("networkidle"); + + await test.step("To the organization by email (external user)", async () => { + const invitedUserEmail = `rick@domain-${Date.now()}.com`; + await page.locator('button:text("Add")').click(); + await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); + await page.locator('button:text("Send invite")').click(); + await page.waitForLoadState("networkidle"); + const inviteLink = await expectInvitationEmailToBeReceived( + page, + emails, + invitedUserEmail, + `${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`, + "signup?token" + ); + + // Check newly invited member exists and is pending + await expect( + page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`) + ).toHaveCount(1); + + // eslint-disable-next-line playwright/no-conditional-in-test + if (!inviteLink) return null; + + // Follow invite link in new window + const context = await browser.newContext(); + const newPage = await context.newPage(); + newPage.goto(inviteLink); + await newPage.waitForLoadState("networkidle"); + + // Check required fields + await newPage.locator("button[type=submit]").click(); + await expect(newPage.locator(".text-red-700")).toHaveCount(3); // 3 password hints + await newPage.locator("input[name=password]").fill(`P4ssw0rd!`); + await newPage.locator("button[type=submit]").click(); + await newPage.waitForURL("/getting-started?from=signup"); + await context.close(); + await newPage.close(); + + // Check newly invited member is not pending anymore + await page.bringToFront(); + await page.goto("/settings/organizations/members"); + page.locator(`[data-testid="login-form"]`); + await expect( + page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`) + ).toHaveCount(0); + }); + + await test.step("To the organization by invite link", async () => { + // Get the invite link + await page.locator('button:text("Add")').click(); + await page.locator(`[data-testid="copy-invite-link-button"]`).click(); + const inviteLink = await clipboard.get(); + await page.waitForLoadState("networkidle"); + + // Follow invite link in new window + const context = await browser.newContext(); + const inviteLinkPage = await context.newPage(); + await inviteLinkPage.goto(inviteLink); + await inviteLinkPage.waitForLoadState("networkidle"); + + // Check required fields + await inviteLinkPage.locator("button[type=submit]").click(); + await expect(inviteLinkPage.locator(".text-red-700")).toHaveCount(4); // email + 3 password hints + + // Happy path + await inviteLinkPage.locator("input[name=email]").fill(`rick@domain-${Date.now()}.com`); + await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`); + await inviteLinkPage.locator("button[type=submit]").click(); + await inviteLinkPage.waitForURL("/getting-started"); + }); + }); + + test("Invitation (verified)", async ({ browser, page, users, emails }) => { + const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true }); + const { team: org } = await orgOwner.getOrg(); + await orgOwner.apiLogin(); + await page.goto("/settings/organizations/members"); + await page.waitForLoadState("networkidle"); + + await test.step("To the organization by email (internal user)", async () => { + const invitedUserEmail = `rick@example.com`; + await page.locator('button:text("Add")').click(); + await page.locator('input[name="inviteUser"]').fill(invitedUserEmail); + await page.locator('button:text("Send invite")').click(); + await page.waitForLoadState("networkidle"); + await expectInvitationEmailToBeReceived( + page, + emails, + invitedUserEmail, + `${org.name}'s admin invited you to join the organization ${org.name} on Cal.com` + ); + + // Check newly invited member exists and is pending + await expect( + page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`) + ).toHaveCount(0); + }); + }); +}); diff --git a/package.json b/package.json index 4f74854f79..586870f28e 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "@playwright/test": "^1.31.2", "@snaplet/copycat": "^0.3.0", "@testing-library/jest-dom": "^5.16.5", + "@types/jsdom": "^21.1.3", "@types/jsonwebtoken": "^9.0.3", "c8": "^7.13.0", "checkly": "latest", diff --git a/packages/features/ee/organizations/pages/settings/members.tsx b/packages/features/ee/organizations/pages/settings/members.tsx index 36ba6b64c5..9f40c42a57 100644 --- a/packages/features/ee/organizations/pages/settings/members.tsx +++ b/packages/features/ee/organizations/pages/settings/members.tsx @@ -11,24 +11,6 @@ const MembersView = () => {
- {/* {team && ( - <> - {isInviteOpen && ( - - )} - - )} */}
diff --git a/packages/features/ee/teams/components/MemberInvitationModal.tsx b/packages/features/ee/teams/components/MemberInvitationModal.tsx index 4aeffb573c..fc8fcf90fc 100644 --- a/packages/features/ee/teams/components/MemberInvitationModal.tsx +++ b/packages/features/ee/teams/components/MemberInvitationModal.tsx @@ -25,6 +25,7 @@ import { TextAreaField, } from "@calcom/ui"; import { Link } from "@calcom/ui/components/icon"; +import type { Window as WindowWithClipboardValue } from "@calcom/web/playwright/fixtures/clipboard"; import type { PendingMember } from "../lib/types"; import { GoogleWorkspaceInviteButton } from "./GoogleWorkspaceInviteButton"; @@ -92,8 +93,15 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps) const inviteLink = isOrgInvite || (props?.orgMembers && props.orgMembers?.length > 0) ? orgInviteLink : teamInviteLink; - await navigator.clipboard.writeText(inviteLink); - showToast(t("invite_link_copied"), "success"); + try { + await navigator.clipboard.writeText(inviteLink); + showToast(t("invite_link_copied"), "success"); + } catch (e) { + if (process.env.NEXT_PUBLIC_IS_E2E) { + (window as WindowWithClipboardValue).E2E_CLIPBOARD_VALUE = inviteLink; + } + console.error(e); + } }; const options: MembershipRoleOption[] = useMemo(() => { diff --git a/packages/features/users/components/UserTable/UserListTable.tsx b/packages/features/users/components/UserTable/UserListTable.tsx index 21abc1bc9a..a2b9ecb3d6 100644 --- a/packages/features/users/components/UserTable/UserListTable.tsx +++ b/packages/features/users/components/UserTable/UserListTable.tsx @@ -204,12 +204,15 @@ export function UserListTable() { id: "teams", header: "Teams", cell: ({ row }) => { - const { teams, accepted } = row.original; + const { teams, accepted, email } = row.original; // TODO: Implement click to filter return (
{accepted ? null : ( - + Pending )} diff --git a/packages/trpc/server/routers/viewer/organizations/create.handler.ts b/packages/trpc/server/routers/viewer/organizations/create.handler.ts index 1e0f9a2e03..1b5509afb5 100644 --- a/packages/trpc/server/routers/viewer/organizations/create.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/create.handler.ts @@ -7,12 +7,7 @@ import { sendAdminOrganizationNotification } from "@calcom/emails"; import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; import { subdomainSuffix } from "@calcom/features/ee/organizations/lib/orgDomains"; import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability"; -import { - IS_TEAM_BILLING_ENABLED, - RESERVED_SUBDOMAINS, - IS_PRODUCTION, - WEBAPP_URL, -} from "@calcom/lib/constants"; +import { IS_TEAM_BILLING_ENABLED, RESERVED_SUBDOMAINS, WEBAPP_URL } from "@calcom/lib/constants"; import { getTranslation } from "@calcom/lib/server/i18n"; import slugify from "@calcom/lib/slugify"; import { prisma } from "@calcom/prisma"; @@ -175,7 +170,6 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => { return { user: { ...createOwnerOrg, password } }; } else { - if (!IS_PRODUCTION) return { checked: true }; const language = await getTranslation(input.language ?? "en", "common"); const secret = createHash("md5") diff --git a/packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts b/packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts index 885bb3b6ac..17bfa84be5 100644 --- a/packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts +++ b/packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts @@ -2,6 +2,7 @@ import { createHash } from "crypto"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { IS_PRODUCTION } from "@calcom/lib/constants"; +import logger from "@calcom/lib/logger"; import { totpRawCheck } from "@calcom/lib/totp"; import type { ZVerifyCodeInputSchema } from "@calcom/prisma/zod-utils"; import type { TrpcSessionUser } from "@calcom/trpc/server/trpc"; @@ -21,7 +22,10 @@ export const verifyCodeHandler = async ({ ctx, input }: VerifyCodeOptions) => { if (!user || !email || !code) throw new TRPCError({ code: "BAD_REQUEST" }); - if (!IS_PRODUCTION) return true; + if (!IS_PRODUCTION || process.env.NEXT_PUBLIC_IS_E2E) { + logger.warn(`Skipping code verification in dev/E2E environment`); + return true; + } await checkRateLimitAndThrowError({ rateLimitingType: "core", identifier: email, diff --git a/yarn.lock b/yarn.lock index 4e231a4e0d..01cf0964ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13331,6 +13331,17 @@ __metadata: languageName: node linkType: hard +"@types/jsdom@npm:^21.1.3": + version: 21.1.4 + resolution: "@types/jsdom@npm:21.1.4" + dependencies: + "@types/node": "*" + "@types/tough-cookie": "*" + parse5: ^7.0.0 + checksum: 915f619111dadd8d1bb7f12b6736c9d2e486911e1aed086de5fb003e7e40ae1e368da322dc04f2122ef47faf40ca75b9315ae2df3e8011f882dcf84660fb0d68 + languageName: node + linkType: hard + "@types/jsforce@npm:^1.11.0": version: 1.11.0 resolution: "@types/jsforce@npm:1.11.0" @@ -17197,6 +17208,7 @@ __metadata: "@playwright/test": ^1.31.2 "@snaplet/copycat": ^0.3.0 "@testing-library/jest-dom": ^5.16.5 + "@types/jsdom": ^21.1.3 "@types/jsonwebtoken": ^9.0.3 c8: ^7.13.0 checkly: latest