diff --git a/apps/api/v1/lib/validations/attendee.ts b/apps/api/v1/lib/validations/attendee.ts index b1be1c2024..17338f0e8f 100644 --- a/apps/api/v1/lib/validations/attendee.ts +++ b/apps/api/v1/lib/validations/attendee.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { _AttendeeModel as Attendee } from "@calcom/prisma/zod"; import { timeZone } from "~/lib/validations/shared/timeZone"; @@ -14,7 +15,7 @@ export const schemaAttendeeBaseBodyParams = Attendee.pick({ const schemaAttendeeCreateParams = z .object({ bookingId: z.number().int(), - email: z.string().email(), + email: emailSchema, name: z.string(), timeZone: timeZone, }) @@ -23,7 +24,7 @@ const schemaAttendeeCreateParams = z const schemaAttendeeEditParams = z .object({ name: z.string().optional(), - email: z.string().email().optional(), + email: emailSchema.optional(), timeZone: timeZone.optional(), }) .strict(); diff --git a/apps/api/v1/lib/validations/shared/queryAttendeeEmail.ts b/apps/api/v1/lib/validations/shared/queryAttendeeEmail.ts index d7919bf53f..c90abc5b42 100644 --- a/apps/api/v1/lib/validations/shared/queryAttendeeEmail.ts +++ b/apps/api/v1/lib/validations/shared/queryAttendeeEmail.ts @@ -1,16 +1,18 @@ import { withValidation } from "next-validations"; import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; + import { baseApiParams } from "./baseApiParams"; // Extracted out as utility function so can be reused // at different endpoints that require this validation. export const schemaQueryAttendeeEmail = baseApiParams.extend({ - attendeeEmail: z.string().email(), + attendeeEmail: emailSchema, }); export const schemaQuerySingleOrMultipleAttendeeEmails = z.object({ - attendeeEmail: z.union([z.string().email(), z.array(z.string().email())]).optional(), + attendeeEmail: z.union([emailSchema, z.array(emailSchema)]).optional(), }); export const withValidQueryAttendeeEmail = withValidation({ diff --git a/apps/api/v1/lib/validations/shared/queryUserEmail.ts b/apps/api/v1/lib/validations/shared/queryUserEmail.ts index 342f36264a..49f96f6354 100644 --- a/apps/api/v1/lib/validations/shared/queryUserEmail.ts +++ b/apps/api/v1/lib/validations/shared/queryUserEmail.ts @@ -1,16 +1,18 @@ import { withValidation } from "next-validations"; import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; + import { baseApiParams } from "./baseApiParams"; // Extracted out as utility function so can be reused // at different endpoints that require this validation. export const schemaQueryUserEmail = baseApiParams.extend({ - email: z.string().email(), + email: emailSchema, }); export const schemaQuerySingleOrMultipleUserEmails = z.object({ - email: z.union([z.string().email(), z.array(z.string().email())]), + email: z.union([emailSchema, z.array(emailSchema)]), }); export const withValidQueryUserEmail = withValidation({ diff --git a/apps/api/v1/lib/validations/user.ts b/apps/api/v1/lib/validations/user.ts index d984613066..2b86addd21 100644 --- a/apps/api/v1/lib/validations/user.ts +++ b/apps/api/v1/lib/validations/user.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { checkUsername } from "@calcom/lib/server/checkUsername"; import { _UserModel as User } from "@calcom/prisma/zod"; import { iso8601 } from "@calcom/prisma/zod-utils"; @@ -92,7 +93,7 @@ export const schemaUserBaseBodyParams = User.pick({ // Here we can both require or not (adding optional or nullish) and also rewrite validations for any value // for example making weekStart only accept weekdays as input const schemaUserEditParams = z.object({ - email: z.string().email().toLowerCase(), + email: emailSchema.toLowerCase(), username: usernameSchema, weekStart: z.nativeEnum(weekdays).optional(), brandColor: z.string().min(4).max(9).regex(/^#/).optional(), @@ -115,7 +116,7 @@ const schemaUserEditParams = z.object({ // merging both BaseBodyParams with RequiredParams, and omiting whatever we want at the end. const schemaUserCreateParams = z.object({ - email: z.string().email().toLowerCase(), + email: emailSchema.toLowerCase(), username: usernameSchema, weekStart: z.nativeEnum(weekdays).optional(), brandColor: z.string().min(4).max(9).regex(/^#/).optional(), diff --git a/apps/web/components/settings/SecondaryEmailModal.tsx b/apps/web/components/settings/SecondaryEmailModal.tsx index 9bc540ed15..b4643af92a 100644 --- a/apps/web/components/settings/SecondaryEmailModal.tsx +++ b/apps/web/components/settings/SecondaryEmailModal.tsx @@ -3,6 +3,7 @@ import { useEffect } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Dialog, @@ -37,7 +38,7 @@ const SecondaryEmailModal = ({ const formMethods = useForm({ resolver: zodResolver( z.object({ - email: z.string().email(), + email: emailSchema, }) ), }); diff --git a/apps/web/components/setup/AdminUser.tsx b/apps/web/components/setup/AdminUser.tsx index ee95820606..4ee1724698 100644 --- a/apps/web/components/setup/AdminUser.tsx +++ b/apps/web/components/setup/AdminUser.tsx @@ -7,6 +7,7 @@ import { z } from "zod"; import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid"; import { WEBSITE_URL } from "@calcom/lib/constants"; +import { emailRegex } from "@calcom/lib/emailSchema"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { EmailField, EmptyScreen, Label, PasswordField, TextField } from "@calcom/ui"; @@ -39,7 +40,7 @@ export const AdminUser = (props: { onSubmit: () => void; onError: () => void; on username: z .string() .refine((val) => val.trim().length >= 1, { message: t("at_least_characters", { count: 1 }) }), - email_address: z.string().email({ message: t("enter_valid_email") }), + email_address: z.string().regex(emailRegex, { message: t("enter_valid_email") }), full_name: z.string().min(3, t("at_least_characters", { count: 3 })), password: z.string().superRefine((data, ctx) => { const isStrict = true; diff --git a/apps/web/lib/signup/getServerSideProps.tsx b/apps/web/lib/signup/getServerSideProps.tsx index 6e4377bf22..d3d2804349 100644 --- a/apps/web/lib/signup/getServerSideProps.tsx +++ b/apps/web/lib/signup/getServerSideProps.tsx @@ -6,20 +6,21 @@ import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiu import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml"; import { getFeatureFlag } from "@calcom/features/flags/server/utils"; import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants"; +import { emailSchema } from "@calcom/lib/emailSchema"; import slugify from "@calcom/lib/slugify"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants"; import { ssrInit } from "@server/lib/ssr"; -const checkValidEmail = (email: string) => z.string().email().safeParse(email).success; +const checkValidEmail = (email: string) => emailSchema.safeParse(email).success; const querySchema = z.object({ username: z .string() .optional() .transform((val) => val || ""), - email: z.string().email().optional(), + email: emailSchema.optional(), }); export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { diff --git a/apps/web/modules/auth/login-view.tsx b/apps/web/modules/auth/login-view.tsx index c8341098b7..478ec4523e 100644 --- a/apps/web/modules/auth/login-view.tsx +++ b/apps/web/modules/auth/login-view.tsx @@ -12,6 +12,7 @@ import { z } from "zod"; import { SAMLLogin } from "@calcom/features/auth/SAMLLogin"; import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode"; import { HOSTED_CAL_FEATURES, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants"; +import { emailRegex } from "@calcom/lib/emailSchema"; import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLastUsed, LastUsed } from "@calcom/lib/hooks/useLastUsed"; @@ -59,7 +60,7 @@ PageProps & WithNonceProps<{}>) { email: z .string() .min(1, `${t("error_required_field")}`) - .email(`${t("enter_valid_email")}`), + .regex(emailRegex, `${t("enter_valid_email")}`), ...(!!totpEmail ? {} : { password: z.string().min(1, `${t("error_required_field")}`) }), }) // Passthrough other fields like totpCode diff --git a/apps/web/modules/settings/my-account/profile-view.tsx b/apps/web/modules/settings/my-account/profile-view.tsx index c737c3cd26..f524acfa9e 100644 --- a/apps/web/modules/settings/my-account/profile-view.tsx +++ b/apps/web/modules/settings/my-account/profile-view.tsx @@ -12,6 +12,7 @@ import { z } from "zod"; import { ErrorCode } from "@calcom/features/auth/lib/ErrorCode"; import SectionBottomActions from "@calcom/features/settings/SectionBottomActions"; import { APP_NAME, FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { md } from "@calcom/lib/markdownIt"; @@ -523,12 +524,12 @@ const ProfileForm = ({ .max(FULL_NAME_LENGTH_MAX_LIMIT, { message: t("max_limit_allowed_hint", { limit: FULL_NAME_LENGTH_MAX_LIMIT }), }), - email: z.string().email(), + email: emailSchema, bio: z.string(), secondaryEmails: z.array( z.object({ id: z.number(), - email: z.string().email(), + email: emailSchema, emailVerified: z.union([z.string(), z.null()]).optional(), emailPrimary: z.boolean().optional(), }) diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index 6cbe28bd20..f749804ae4 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -1,17 +1,13 @@ import type { NextApiRequest, NextApiResponse } from "next"; -import { z } from "zod"; import { passwordResetRequest } from "@calcom/features/auth/lib/passwordResetRequest"; import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { defaultHandler } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; async function handler(req: NextApiRequest, res: NextApiResponse) { - const email = z - .string() - .email() - .transform((val) => val.toLowerCase()) - .safeParse(req.body?.email); + const email = emailSchema.transform((val) => val.toLowerCase()).safeParse(req.body?.email); if (!email.success) { return res.status(400).json({ message: "email is required" }); diff --git a/apps/web/pages/api/auth/setup.ts b/apps/web/pages/api/auth/setup.ts index fb196cc325..c092a7505a 100644 --- a/apps/web/pages/api/auth/setup.ts +++ b/apps/web/pages/api/auth/setup.ts @@ -3,6 +3,7 @@ import z from "zod"; import { hashPassword } from "@calcom/features/auth/lib/hashPassword"; import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid"; +import { emailRegex } from "@calcom/lib/emailSchema"; import { HttpError } from "@calcom/lib/http-error"; import { defaultHandler, defaultResponder } from "@calcom/lib/server"; import slugify from "@calcom/lib/slugify"; @@ -14,7 +15,7 @@ const querySchema = z.object({ .string() .refine((val) => val.trim().length >= 1, { message: "Please enter at least one character" }), full_name: z.string().min(3, "Please enter at least 3 characters"), - email_address: z.string().email({ message: "Please enter a valid email" }), + email_address: z.string().regex(emailRegex, { message: "Please enter a valid email" }), password: z.string().refine((val) => isPasswordValid(val.trim(), false, true), { message: "The password must be a minimum of 15 characters long containing at least one number and have a mixture of uppercase and lowercase letters", diff --git a/apps/web/pages/api/sync/helpscout/index.ts b/apps/web/pages/api/sync/helpscout/index.ts index 7110d1d040..3931f75758 100644 --- a/apps/web/pages/api/sync/helpscout/index.ts +++ b/apps/web/pages/api/sync/helpscout/index.ts @@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import getRawBody from "raw-body"; import z from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { default as webPrisma } from "@calcom/prisma"; export const config = { @@ -13,7 +14,7 @@ export const config = { const helpscoutRequestBodySchema = z.object({ customer: z.object({ - email: z.string().email(), + email: emailSchema, }), }); diff --git a/packages/app-store/exchangecalendar/api/_postAdd.ts b/packages/app-store/exchangecalendar/api/_postAdd.ts index dbea3db769..0a7709f4e2 100644 --- a/packages/app-store/exchangecalendar/api/_postAdd.ts +++ b/packages/app-store/exchangecalendar/api/_postAdd.ts @@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { z } from "zod"; import { symmetricEncrypt } from "@calcom/lib/crypto"; +import { emailSchema } from "@calcom/lib/emailSchema"; import logger from "@calcom/lib/logger"; import { defaultResponder } from "@calcom/lib/server"; import prisma from "@calcom/prisma"; @@ -14,7 +15,7 @@ import { CalendarService } from "../lib"; const formSchema = z .object({ url: z.string().url(), - username: z.string().email(), + username: emailSchema, password: z.string(), authenticationMethod: z.number().default(ExchangeAuthentication.STANDARD), exchangeVersion: z.number().default(ExchangeVersion.Exchange2016), diff --git a/packages/app-store/exchangecalendar/pages/setup/index.tsx b/packages/app-store/exchangecalendar/pages/setup/index.tsx index 6acde1651d..92ba50af9a 100644 --- a/packages/app-store/exchangecalendar/pages/setup/index.tsx +++ b/packages/app-store/exchangecalendar/pages/setup/index.tsx @@ -5,6 +5,7 @@ import { Controller, useForm } from "react-hook-form"; import { Toaster } from "react-hot-toast"; import z from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { Alert, Button, EmailField, Form, PasswordField, SelectField, TextField } from "@calcom/ui"; @@ -22,7 +23,7 @@ interface IFormData { const schema = z .object({ url: z.string().url(), - username: z.string().email(), + username: emailSchema, password: z.string(), authenticationMethod: z.number().default(ExchangeAuthentication.STANDARD), exchangeVersion: z.number().default(ExchangeVersion.Exchange2016), diff --git a/packages/app-store/routing-forms/trpc/response.handler.ts b/packages/app-store/routing-forms/trpc/response.handler.ts index 728355a844..f66bc5ce09 100644 --- a/packages/app-store/routing-forms/trpc/response.handler.ts +++ b/packages/app-store/routing-forms/trpc/response.handler.ts @@ -1,6 +1,7 @@ import { Prisma } from "@prisma/client"; import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; import logger from "@calcom/lib/logger"; import { safeStringify } from "@calcom/lib/safeStringify"; import type { PrismaClient } from "@calcom/prisma"; @@ -75,7 +76,7 @@ export const responseHandler = async ({ ctx, input }: ResponseHandlerOptions) => } let schema; if (field.type === "email") { - schema = z.string().email(); + schema = emailSchema; } else if (field.type === "phone") { schema = z.any(); } else { diff --git a/packages/features/auth/SAMLLogin.tsx b/packages/features/auth/SAMLLogin.tsx index 75420c5503..ba410c6543 100644 --- a/packages/features/auth/SAMLLogin.tsx +++ b/packages/features/auth/SAMLLogin.tsx @@ -4,6 +4,7 @@ import { useFormContext } from "react-hook-form"; import z from "zod"; import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants"; +import { emailRegex } from "@calcom/lib/emailSchema"; import { LastUsed, useLastUsed } from "@calcom/lib/hooks/useLastUsed"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; @@ -17,7 +18,7 @@ interface Props { } const schema = z.object({ - email: z.string().email({ message: "Please enter a valid email" }), + email: z.string().regex(emailRegex, { message: "Please enter a valid email" }), }); export function SAMLLogin({ diff --git a/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx b/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx index 860116bf9b..2afd989ffc 100644 --- a/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx +++ b/packages/features/users/components/UserTable/EditSheet/EditUserForm.tsx @@ -5,6 +5,7 @@ import { useMemo, useState } from "react"; import { Controller, useForm, useFormContext } from "react-hook-form"; import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; import { trpc, type RouterOutputs } from "@calcom/trpc/react"; @@ -46,7 +47,7 @@ const attributeSchema = z.object({ const editSchema = z.object({ name: z.string(), username: z.string(), - email: z.string().email(), + email: emailSchema, avatar: z.string(), bio: z.string(), role: z.enum([MembershipRole.MEMBER, MembershipRole.ADMIN, MembershipRole.OWNER]), diff --git a/packages/lib/emailSchema.ts b/packages/lib/emailSchema.ts new file mode 100644 index 0000000000..72d94cce3f --- /dev/null +++ b/packages/lib/emailSchema.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +/** @see https://github.com/colinhacks/zod/issues/3155#issuecomment-2060045794 */ +export const emailRegex = + /^(?!\.)(?!.*\.\.)([A-Z0-9_+-\.']*)[A-Z0-9_+'-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; + +export const emailSchema = z.string().regex(emailRegex); diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index afb86fd9c0..0bb3556961 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -202,7 +202,7 @@ model DestinationCalendar { id Int @id @default(autoincrement()) integration String externalId String - /// @zod.email() + /// @zod.custom(imports.emailSchema) primaryEmail String? user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int? @unique @@ -247,7 +247,7 @@ model User { id Int @id @default(autoincrement()) username String? name String? - /// @zod.email() + /// @zod.custom(imports.emailSchema) email String emailVerified DateTime? password UserPassword? @@ -578,7 +578,7 @@ model Booking { user User? @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int? // User's email at the time of booking - /// @zod.email() + /// @zod.custom(imports.emailSchema) userPrimaryEmail String? references BookingReference[] eventType EventType? @relation(fields: [eventTypeId], references: [id]) diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index 23cb4b2a04..d7194b0955 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -18,6 +18,7 @@ import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid"; import type { FieldType as FormBuilderFieldType } from "@calcom/features/form-builder/schema"; import { fieldsSchema as formBuilderFieldsSchema } from "@calcom/features/form-builder/schema"; import { isSupportedTimeZone } from "@calcom/lib/date-fns"; +import { emailSchema as emailRegexSchema, emailRegex } from "@calcom/lib/emailSchema"; import { slugify } from "@calcom/lib/slugify"; import { EventTypeCustomInputType } from "@calcom/prisma/enums"; @@ -699,11 +700,12 @@ export const unlockedManagedEventTypeProps = { destinationCalendar: allManagedEventTypeProps.destinationCalendar, }; +export const emailSchema = emailRegexSchema; + // The PR at https://github.com/colinhacks/zod/pull/2157 addresses this issue and improves email validation // I introduced this refinement(to be used with z.email()) as a short term solution until we upgrade to a zod // version that will include updates in the above PR. export const emailSchemaRefinement = (value: string) => { - const emailRegex = /^([A-Z0-9_+-]+\.?)*[A-Z0-9_+-]@([A-Z0-9][A-Z0-9-]*\.)+[A-Z]{2,}$/i; return emailRegex.test(value); }; @@ -711,7 +713,7 @@ export const signupSchema = z.object({ // Username is marked optional here because it's requirement depends on if it's the Organization invite or a team invite which isn't easily done in zod // It's better handled beyond zod in `validateAndGetCorrectedUsernameAndEmail` username: z.string().optional(), - email: z.string().email({ message: "Invalid email" }), + email: z.string().regex(emailRegex, { message: "Invalid email" }), password: z.string().superRefine((data, ctx) => { const isStrict = false; const result = isPasswordValid(data, true, isStrict); @@ -730,7 +732,7 @@ export const signupSchema = z.object({ }); export const ZVerifyCodeInputSchema = z.object({ - email: z.string().email(), + email: emailSchema, code: z.string(), }); @@ -743,4 +745,4 @@ export const getStringAsNumberRequiredSchema = (t: TFunction) => export const bookingSeatDataSchema = z.object({ description: z.string().optional(), responses: bookingResponses, -}); +}); \ No newline at end of file diff --git a/packages/trpc/server/routers/publicViewer/samlTenantProduct.schema.ts b/packages/trpc/server/routers/publicViewer/samlTenantProduct.schema.ts index f54d1e47cf..c64ab2b78a 100644 --- a/packages/trpc/server/routers/publicViewer/samlTenantProduct.schema.ts +++ b/packages/trpc/server/routers/publicViewer/samlTenantProduct.schema.ts @@ -1,7 +1,9 @@ import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; + export const ZSamlTenantProductInputSchema = z.object({ - email: z.string().email(), + email: emailSchema, }); export type TSamlTenantProductInputSchema = z.infer; diff --git a/packages/trpc/server/routers/viewer/admin/createSelfHostedLicenseKey.schema.ts b/packages/trpc/server/routers/viewer/admin/createSelfHostedLicenseKey.schema.ts index 64189e28f5..7b6010bded 100644 --- a/packages/trpc/server/routers/viewer/admin/createSelfHostedLicenseKey.schema.ts +++ b/packages/trpc/server/routers/viewer/admin/createSelfHostedLicenseKey.schema.ts @@ -1,5 +1,7 @@ import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; + const BillingType = z.enum(["PER_BOOKING", "PER_USER"]); const BillingPeriod = z.enum(["MONTHLY", "ANNUALLY"]); @@ -9,7 +11,7 @@ export const ZCreateSelfHostedLicenseSchema = z.object({ entityPrice: z.number().nonnegative(), billingPeriod: BillingPeriod, overages: z.number().nonnegative(), - billingEmail: z.string().email(), + billingEmail: emailSchema, }); export type TCreateSelfHostedLicenseSchema = z.infer; diff --git a/packages/trpc/server/routers/viewer/auth/resendVerifyEmail.schema.ts b/packages/trpc/server/routers/viewer/auth/resendVerifyEmail.schema.ts index 888ebe9d4d..a6aef6f296 100644 --- a/packages/trpc/server/routers/viewer/auth/resendVerifyEmail.schema.ts +++ b/packages/trpc/server/routers/viewer/auth/resendVerifyEmail.schema.ts @@ -1,8 +1,10 @@ import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; + export const ZResendVerifyEmailSchema = z .object({ - email: z.string().email(), + email: emailSchema, }) .optional(); diff --git a/packages/trpc/server/routers/viewer/organizations/create.schema.ts b/packages/trpc/server/routers/viewer/organizations/create.schema.ts index c9e4dc9691..69a37d262d 100644 --- a/packages/trpc/server/routers/viewer/organizations/create.schema.ts +++ b/packages/trpc/server/routers/viewer/organizations/create.schema.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; import slugify from "@calcom/lib/slugify"; export enum BillingPeriod { @@ -10,7 +11,7 @@ export enum BillingPeriod { export const ZCreateInputSchema = z.object({ name: z.string(), slug: z.string().transform((val) => slugify(val.trim())), - orgOwnerEmail: z.string().email(), + orgOwnerEmail: emailSchema, language: z.string().optional(), seats: z.number().optional(), pricePerSeat: z.number().optional(), diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema.ts index 02b8ac0095..79059bd47a 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { MAX_NB_INVITES } from "@calcom/lib/constants"; +import { emailSchema } from "@calcom/lib/emailSchema"; import { MembershipRole } from "@calcom/prisma/enums"; export const ZInviteMemberInputSchema = z.object({ @@ -12,7 +13,7 @@ export const ZInviteMemberInputSchema = z.object({ .union([ z.string(), z.object({ - email: z.string().email(), + email: emailSchema, role: z.nativeEnum(MembershipRole), }), ]) @@ -47,7 +48,7 @@ export const ZInviteMemberInputSchema = z.object({ .refine( (value) => { if (Array.isArray(value)) { - return !value.some((email) => !z.string().email().safeParse(email).success); + return !value.some((email) => !emailSchema.safeParse(email).success); } return true; }, diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMemberUtils.test.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMemberUtils.test.ts index 9133df42b6..e138fdb434 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMemberUtils.test.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMemberUtils.test.ts @@ -179,7 +179,9 @@ describe("Invite Member Utils", () => { it("should not throw an error if the email is valid", () => { const validEmail = "valid-email@example.com"; + const validEmailWithApostrophe = "valid'email@example.com"; expect(() => checkInputEmailIsValid(validEmail)).not.toThrow(); + expect(() => checkInputEmailIsValid(validEmailWithApostrophe)).not.toThrow(); }); }); describe("getOrgConnectionInfo", () => { diff --git a/packages/trpc/server/routers/viewer/teams/resendInvitation.schema.ts b/packages/trpc/server/routers/viewer/teams/resendInvitation.schema.ts index b494294630..a98ffd635a 100644 --- a/packages/trpc/server/routers/viewer/teams/resendInvitation.schema.ts +++ b/packages/trpc/server/routers/viewer/teams/resendInvitation.schema.ts @@ -1,8 +1,10 @@ import { z } from "zod"; +import { emailSchema } from "@calcom/lib/emailSchema"; + export const ZResendInvitationInputSchema = z.object({ teamId: z.number(), - email: z.string().email(), + email: emailSchema, language: z.string(), isOrg: z.boolean().default(false), });