diff --git a/lib/validations/user.ts b/lib/validations/user.ts index 9c5d2777c2..7452c6ad44 100644 --- a/lib/validations/user.ts +++ b/lib/validations/user.ts @@ -1,4 +1,5 @@ import { withValidation } from "next-validations"; +import * as tzdb from "tzdata"; import { z } from "zod"; import { _UserModel as User } from "@calcom/prisma/zod"; @@ -14,11 +15,45 @@ enum weekdays { SUNDAY = "Sunday", } +// @note: extracted from apps/web/next-i18next.config.js, update if new locales. +enum locales { + EN = "en", + FR = "fr", + IT = "it", + RU = "ru", + ES = "es", + DE = "de", + PT = "pt", + RO = "ro", + NL = "nl", + PT_BR = "pt-BR", + ES_419 = "es-419", + KO = "ko", + JA = "ja", + PL = "pl", + AR = "ar", + IW = "iw", + ZH_CN = "zh-CN", + ZH_TW = "zh-TW", + CS = "cs", + SR = "sr", + SV = "sv", + VI = "vi", +} +enum theme { + DARK = "dark", + LIGHT = "light", +} + +enum timeFormat { + TWELVE = 12, + TWENTY_FOUR = 24, +} + // @note: These are the values that are editable via PATCH method on the user Model export const schemaUserBaseBodyParams = User.pick({ name: true, bio: true, - avatar: true, timeZone: true, weekStart: true, endTime: true, @@ -31,6 +66,8 @@ export const schemaUserBaseBodyParams = User.pick({ darkBrandColor: true, allowDynamicBooking: true, away: true, + // @note: disallowing avatar changes via API for now. We can add it later if needed. User should upload image via UI. + // avatar: true, }).partial(); // @note: partial() is used to allow for the user to edit only the fields they want to edit making all optional, // if want to make any required do it in the schemaRequiredParams @@ -39,9 +76,26 @@ export const schemaUserBaseBodyParams = User.pick({ // for example making weekStart only accept weekdays as input const schemaUserRequiredParams = z.object({ weekStart: z.nativeEnum(weekdays).optional(), + brandColor: z.string().min(4).max(9).regex(/^#/).optional(), + timeZone: z + .string() + // @note: This is a custom validation that checks if the timezone is valid and exists in the tzdb library + .refine((tz: string) => Object.keys(tzdb.zones).includes(tz)) + .optional(), + bufferTime: z.number().min(0).max(86400).optional(), + startTime: z.number().min(0).max(86400).optional(), + endTime: z.number().min(0).max(86400).optional(), + theme: z.nativeEnum(theme).optional(), + timeFormat: z.nativeEnum(timeFormat).optional(), + defaultScheduleId: z + .number() + .refine((id: number) => id > 0) + .optional(), + locale: z.nativeEnum(locales), }); -// @note: These are the values that are editable via PATCH method on the user Model +// @note: These are the values that are editable via PATCH method on the user Model, +// merging both BaseBodyParams with RequiredParams, and omiting whatever we want at the end. export const schemaUserEditBodyParams = schemaUserBaseBodyParams.merge(schemaUserRequiredParams).omit({}); // @note: These are the values that are always returned when reading a user diff --git a/pages/api/users/[id].ts b/pages/api/users/[id].ts index 031ea71e0e..66386d38d8 100644 --- a/pages/api/users/[id].ts +++ b/pages/api/users/[id].ts @@ -104,12 +104,23 @@ export async function userById(req: NextApiRequest, res: NextApiResponse) { break; case "PATCH": - // FIXME: Validate body against different Edit validation, skip username. const safeBody = schemaUserEditBodyParams.safeParse(body); if (!safeBody.success) { res.status(400).json({ message: "Bad request", error: safeBody.error }); throw new Error("Invalid request body"); } + const userSchedules = await prisma.schedule.findMany({ + where: { userId }, + }); + const userSchedulesIds = userSchedules.map((schedule) => schedule.id); + // @note: here we make sure user can only make as default his own scheudles + if (!userSchedulesIds.includes(Number(safeBody?.data?.defaultScheduleId))) { + res.status(400).json({ + message: "Bad request", + error: "Invalid default schedule id", + }); + throw new Error("Invalid request body value: defaultScheduleId"); + } await prisma.user .update({ where: { id: userId },