From 3aaa1cde0dd69f42bb523f6e5db8f2f31d947c32 Mon Sep 17 00:00:00 2001 From: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Date: Tue, 28 Mar 2023 11:40:13 +0200 Subject: [PATCH] Fixes formatted description in email + sanitize html everywhere (#7928) * use sanitize-html instead * add list formatting * add lost changes from merging main * fixes caused by merge * remove dompurify * Revert "remove dompurify" This reverts commit 583bb623c7d3c0c7daa28e63ab2b1ac2da68c0d8. * sanitize already done in editor * Update yarn.lock --------- Co-authored-by: CarinaWolli Co-authored-by: Alex van Andel --- .../EventTypeDescriptionSafeHTML.tsx | 6 +- apps/web/components/team/screens/Team.tsx | 11 +-- apps/web/package.json | 2 + apps/web/pages/[user].tsx | 8 +- apps/web/pages/[user]/[type].tsx | 8 +- apps/web/pages/[user]/book.tsx | 2 + apps/web/pages/d/[link]/[slug].tsx | 2 + apps/web/pages/d/[link]/book.tsx | 2 + apps/web/pages/event-types/index.tsx | 2 +- apps/web/pages/team/[slug].tsx | 13 ++- apps/web/pages/team/[slug]/[type].tsx | 2 + apps/web/pages/team/[slug]/book.tsx | 2 + packages/emails/src/components/Info.tsx | 21 ++++- .../src/templates/BaseScheduledEmail.tsx | 2 +- .../ee/teams/pages/team-profile-view.tsx | 2 +- .../components/EventTypeDescription.tsx | 4 +- packages/lib/markdownIt.ts | 9 --- packages/lib/markdownToSafeHTML.ts | 23 ++++++ packages/prisma/index.ts | 6 +- .../eventTypeDescriptionParseAndSanitize.ts | 43 ---------- packages/prisma/middleware/index.ts | 1 - packages/prisma/seed-app-store.config.json | 3 +- .../trpc/server/routers/viewer/eventTypes.ts | 2 + packages/trpc/server/routers/viewer/teams.tsx | 3 +- .../editor/plugins/ToolbarPlugin.tsx | 2 +- yarn.lock | 81 ++++++++++++++++++- 26 files changed, 175 insertions(+), 87 deletions(-) create mode 100644 packages/lib/markdownToSafeHTML.ts delete mode 100644 packages/prisma/middleware/eventTypeDescriptionParseAndSanitize.ts diff --git a/apps/web/components/eventtype/EventTypeDescriptionSafeHTML.tsx b/apps/web/components/eventtype/EventTypeDescriptionSafeHTML.tsx index d60a64019c..29f283b2ba 100644 --- a/apps/web/components/eventtype/EventTypeDescriptionSafeHTML.tsx +++ b/apps/web/components/eventtype/EventTypeDescriptionSafeHTML.tsx @@ -1,11 +1,11 @@ export type EventTypeDescriptionSafeProps = { - eventType: { description: string | null }; + eventType: { description: string | null; descriptionAsSafeHTML: string | null }; }; export const EventTypeDescriptionSafeHTML = ({ eventType }: EventTypeDescriptionSafeProps) => { const props: JSX.IntrinsicElements["div"] = { suppressHydrationWarning: true }; - // @ts-expect-error: @see packages/prisma/middleware/eventTypeDescriptionParseAndSanitize.ts - if (eventType.description) props.dangerouslySetInnerHTML = { __html: eventType.descriptionAsSafeHTML }; + if (eventType.description) + props.dangerouslySetInnerHTML = { __html: eventType.descriptionAsSafeHTML || "" }; return
; }; diff --git a/apps/web/components/team/screens/Team.tsx b/apps/web/components/team/screens/Team.tsx index 4da45f819b..76ce292fe2 100644 --- a/apps/web/components/team/screens/Team.tsx +++ b/apps/web/components/team/screens/Team.tsx @@ -2,13 +2,14 @@ import Link from "next/link"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { md } from "@calcom/lib/markdownIt"; import type { TeamWithMembers } from "@calcom/lib/server/queries/teams"; import { Avatar } from "@calcom/ui"; type TeamType = NonNullable; type MembersType = TeamType["members"]; -type MemberType = MembersType[number]; +type MemberType = MembersType[number] & { safeBio: string | null }; + +type TeamTypeWithSafeHtml = Omit & { members: MemberType[] }; const Member = ({ member, teamName }: { member: MemberType; teamName: string | null }) => { const { t } = useLocale(); @@ -30,7 +31,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n <>
) : ( @@ -43,7 +44,7 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n ); }; -const Members = ({ members, teamName }: { members: MembersType; teamName: string | null }) => { +const Members = ({ members, teamName }: { members: MemberType[]; teamName: string | null }) => { if (!members || members.length === 0) { return null; } @@ -57,7 +58,7 @@ const Members = ({ members, teamName }: { members: MembersType; teamName: string ); }; -const Team = ({ team }: { team: TeamType }) => { +const Team = ({ team }: { team: TeamTypeWithSafeHtml }) => { return (
diff --git a/apps/web/package.json b/apps/web/package.json index 9e80820ddb..0f9ae44fd3 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -119,6 +119,7 @@ "react-window": "^1.8.7", "remark": "^14.0.2", "rrule": "^2.7.1", + "sanitize-html": "^2.10.0", "schema-dts": "^1.1.0", "short-uuid": "^4.2.0", "strip-markdown": "^5.0.0", @@ -156,6 +157,7 @@ "@types/react-phone-number-input": "^3.0.14", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", + "@types/sanitize-html": "^2.9.0", "@types/stripe": "^8.0.417", "@types/uuid": "8.3.1", "autoprefixer": "^10.4.12", diff --git a/apps/web/pages/[user].tsx b/apps/web/pages/[user].tsx index 8e032088f8..c24af1651c 100644 --- a/apps/web/pages/[user].tsx +++ b/apps/web/pages/[user].tsx @@ -24,7 +24,7 @@ import defaultEvents, { } from "@calcom/lib/defaultEvents"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useTheme from "@calcom/lib/hooks/useTheme"; -import { md } from "@calcom/lib/markdownIt"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import prisma from "@calcom/prisma"; import { baseEventTypeSelect } from "@calcom/prisma/selects"; @@ -147,7 +147,7 @@ export default function User(props: inferSSRProps & E <>
)} @@ -343,6 +343,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => const eventTypes = eventTypesRaw.map((eventType) => ({ ...eventType, metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}), + descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), })); const isSingleUser = users.length === 1; @@ -352,9 +353,12 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => }) : []; + const safeBio = markdownToSafeHTML(user.bio) || ""; + return { props: { users, + safeBio, profile, user: { emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx index b0879a49ce..4e5c27aed2 100644 --- a/apps/web/pages/[user]/[type].tsx +++ b/apps/web/pages/[user]/[type].tsx @@ -5,7 +5,7 @@ import type { LocationObject } from "@calcom/app-store/locations"; import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants"; import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { addListFormatting } from "@calcom/lib/markdownIt"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import type { User } from "@calcom/prisma/client"; import { isBrandingHidden } from "@lib/isBrandingHidden"; @@ -59,7 +59,6 @@ Type.isThemeSupported = true; const paramsSchema = z.object({ type: z.string(), user: z.string() }); async function getUserPageProps(context: GetStaticPropsContext) { // load server side dependencies - const MarkdownIt = await import("markdown-it").then((mod) => mod.default); const prisma = await import("@calcom/prisma").then((mod) => mod.default); const { privacyFilteredLocations } = await import("@calcom/app-store/locations"); const { parseRecurringEvent } = await import("@calcom/lib/isRecurringEvent"); @@ -124,9 +123,6 @@ async function getUserPageProps(context: GetStaticPropsContext) { }, }, }); - - const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true }); - if (!user || !user.eventTypes.length) return { notFound: true }; const [eventType]: ((typeof user.eventTypes)[number] & { @@ -153,7 +149,7 @@ async function getUserPageProps(context: GetStaticPropsContext) { metadata: EventTypeMetaDataSchema.parse(eventType.metadata || {}), recurringEvent: parseRecurringEvent(eventType.recurringEvent), locations: privacyFilteredLocations(locations), - descriptionAsSafeHTML: eventType.description ? addListFormatting(md.render(eventType.description)) : null, + descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), }); // Check if the user you are logging into has any active teams or premium user name const hasActiveTeam = diff --git a/apps/web/pages/[user]/book.tsx b/apps/web/pages/[user]/book.tsx index 764332c1fc..ab729bd3ff 100644 --- a/apps/web/pages/[user]/book.tsx +++ b/apps/web/pages/[user]/book.tsx @@ -14,6 +14,7 @@ import { getUsernameList, } from "@calcom/lib/defaultEvents"; import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import prisma, { bookEventTypeSelect } from "@calcom/prisma"; import { customInputSchema, @@ -189,6 +190,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { slug: u.username, theme: u.theme, })), + descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), }; })[0]; diff --git a/apps/web/pages/d/[link]/[slug].tsx b/apps/web/pages/d/[link]/[slug].tsx index 29ec5b3f80..bbb3c7eef4 100644 --- a/apps/web/pages/d/[link]/[slug].tsx +++ b/apps/web/pages/d/[link]/[slug].tsx @@ -5,6 +5,7 @@ import type { LocationObject } from "@calcom/core/location"; import { privacyFilteredLocations } from "@calcom/core/location"; import { parseRecurringEvent } from "@calcom/lib"; import { getWorkingHours } from "@calcom/lib/availability"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { availiblityPageEventTypeSelect } from "@calcom/prisma"; import prisma from "@calcom/prisma"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; @@ -119,6 +120,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => hideBranding: u.hideBranding, timeZone: u.timeZone, })), + descriptionAsSafeHTML: markdownToSafeHTML(hashedLink.eventType.description), }); const [user] = users; diff --git a/apps/web/pages/d/[link]/book.tsx b/apps/web/pages/d/[link]/book.tsx index d71e7457e1..6e148844f9 100644 --- a/apps/web/pages/d/[link]/book.tsx +++ b/apps/web/pages/d/[link]/book.tsx @@ -1,6 +1,7 @@ import type { GetServerSidePropsContext } from "next"; import { parseRecurringEvent } from "@calcom/lib"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import prisma from "@calcom/prisma"; import { bookEventTypeSelect } from "@calcom/prisma/selects"; import { customInputSchema, eventTypeBookingFields, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; @@ -93,6 +94,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { brandColor: u.brandColor, darkBrandColor: u.darkBrandColor, })), + descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), }; })[0]; diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index ecacf53eb8..aa52db80c7 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -141,7 +141,7 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
diff --git a/apps/web/pages/team/[slug].tsx b/apps/web/pages/team/[slug].tsx index 0a9741b179..1695750ab7 100644 --- a/apps/web/pages/team/[slug].tsx +++ b/apps/web/pages/team/[slug].tsx @@ -10,7 +10,7 @@ import { CAL_URL } from "@calcom/lib/constants"; import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useTheme from "@calcom/lib/hooks/useTheme"; -import { md } from "@calcom/lib/markdownIt"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { getTeamWithMembers } from "@calcom/lib/server/queries/teams"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry"; import prisma from "@calcom/prisma"; @@ -113,7 +113,7 @@ function TeamPage({ team, isUnpublished }: TeamPageProps) { <>
)} @@ -187,11 +187,18 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => ...user, avatar: CAL_URL + "/" + user.username + "/avatar.png", })), + descriptionAsSafeHTML: markdownToSafeHTML(type.description), })); + const safeBio = markdownToSafeHTML(team.bio) || ""; + + const members = team.members.map((member) => { + return { ...member, safeBio: markdownToSafeHTML(member.bio || "") }; + }); + return { props: { - team, + team: { ...team, safeBio, members }, trpcState: ssr.dehydrate(), }, } as const; diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx index 455e6e4de0..5249962186 100644 --- a/apps/web/pages/team/[slug]/[type].tsx +++ b/apps/web/pages/team/[slug]/[type].tsx @@ -4,6 +4,7 @@ import type { LocationObject } from "@calcom/core/location"; import { privacyFilteredLocations } from "@calcom/core/location"; import { parseRecurringEvent } from "@calcom/lib"; import { getWorkingHours } from "@calcom/lib/availability"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import prisma from "@calcom/prisma"; import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; @@ -170,6 +171,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => hideBranding, timeZone, })), + descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), }); eventTypeObject.availability = []; diff --git a/apps/web/pages/team/[slug]/book.tsx b/apps/web/pages/team/[slug]/book.tsx index 6ed37f89a1..afce8a1438 100644 --- a/apps/web/pages/team/[slug]/book.tsx +++ b/apps/web/pages/team/[slug]/book.tsx @@ -5,6 +5,7 @@ import type { LocationObject } from "@calcom/app-store/locations"; import { privacyFilteredLocations } from "@calcom/app-store/locations"; import { getBookingFieldsWithSystemFields } from "@calcom/features/bookings/lib/getBookingFields"; import { parseRecurringEvent } from "@calcom/lib"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import prisma from "@calcom/prisma"; import { customInputSchema, eventTypeBookingFields, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; @@ -121,6 +122,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { image: u.avatar, slug: u.username, })), + descriptionAsSafeHTML: markdownToSafeHTML(eventType.description), }; })[0]; diff --git a/packages/emails/src/components/Info.tsx b/packages/emails/src/components/Info.tsx index a737e3177b..f91e4da20b 100644 --- a/packages/emails/src/components/Info.tsx +++ b/packages/emails/src/components/Info.tsx @@ -1,3 +1,5 @@ +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; + const Spacer = () =>

; export const Info = (props: { @@ -6,8 +8,14 @@ export const Info = (props: { extraInfo?: React.ReactNode; withSpacer?: boolean; lineThrough?: boolean; + formatted?: boolean; }) => { if (!props.description || props.description === "") return null; + + const descriptionCSS = "color: '#101010'; font-weight: 400; line-height: 24px; margin: 0;"; + + const safeDescription = markdownToSafeHTML(props.description.toString()) || ""; + return ( <> {props.withSpacer && } @@ -21,7 +29,18 @@ export const Info = (props: { whiteSpace: "pre-wrap", textDecoration: props.lineThrough ? "line-through" : undefined, }}> - {props.description} + {props.formatted ? ( +

", `

`) + .replaceAll("

  • ", `
  • `), + }} + /> + ) : ( + props.description + )}

    {props.extraInfo}
  • diff --git a/packages/emails/src/templates/BaseScheduledEmail.tsx b/packages/emails/src/templates/BaseScheduledEmail.tsx index 5d084731b3..654c0378a5 100644 --- a/packages/emails/src/templates/BaseScheduledEmail.tsx +++ b/packages/emails/src/templates/BaseScheduledEmail.tsx @@ -76,7 +76,7 @@ export const BaseScheduledEmail = ( - + {props.includeAppsStatus && } diff --git a/packages/features/ee/teams/pages/team-profile-view.tsx b/packages/features/ee/teams/pages/team-profile-view.tsx index 419aec51a0..8215d46cff 100644 --- a/packages/features/ee/teams/pages/team-profile-view.tsx +++ b/packages/features/ee/teams/pages/team-profile-view.tsx @@ -256,7 +256,7 @@ const ProfileView = () => {
    )} diff --git a/packages/features/eventtypes/components/EventTypeDescription.tsx b/packages/features/eventtypes/components/EventTypeDescription.tsx index 6b4953e3ce..705588b7e7 100644 --- a/packages/features/eventtypes/components/EventTypeDescription.tsx +++ b/packages/features/eventtypes/components/EventTypeDescription.tsx @@ -7,7 +7,6 @@ import type { z } from "zod"; import { classNames, parseRecurringEvent } from "@calcom/lib"; import getPaymentAppData from "@calcom/lib/getPaymentAppData"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { addListFormatting, md } from "@calcom/lib/markdownIt"; import type { baseEventTypeSelect } from "@calcom/prisma"; import type { EventTypeModel } from "@calcom/prisma/zod"; import { Badge } from "@calcom/ui"; @@ -26,6 +25,7 @@ export type EventTypeDescriptionProps = { z.infer, Exclude | "metadata" > & { + descriptionAsSafeHTML?: string | null; recurringEvent: Prisma.JsonValue; seatsPerTimeSlot?: number; }; @@ -57,7 +57,7 @@ export const EventTypeDescription = ({ shortenDescription ? "line-clamp-4" : "" )} dangerouslySetInnerHTML={{ - __html: addListFormatting(md.render(eventType.description)), + __html: eventType.descriptionAsSafeHTML || "", }} /> )} diff --git a/packages/lib/markdownIt.ts b/packages/lib/markdownIt.ts index 45df9a5fdd..2ac2daa290 100644 --- a/packages/lib/markdownIt.ts +++ b/packages/lib/markdownIt.ts @@ -1,12 +1,3 @@ import MarkdownIt from "markdown-it"; export const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true }); - -export function addListFormatting(html: string) { - return html - .replaceAll("
      ", "
        ") - .replaceAll( - "
          ", - "
            " - ); -} diff --git a/packages/lib/markdownToSafeHTML.ts b/packages/lib/markdownToSafeHTML.ts new file mode 100644 index 0000000000..f4c993377b --- /dev/null +++ b/packages/lib/markdownToSafeHTML.ts @@ -0,0 +1,23 @@ +import sanitizeHtml from "sanitize-html"; + +import { md } from "@calcom/lib/markdownIt"; + +export function markdownToSafeHTML(markdown: string | null) { + if (!markdown) return null; + + const html = md.render(markdown); + + const safeHTML = sanitizeHtml(html); + + const safeHTMLWithListFormatting = safeHTML + .replace( + /
              /g, + "
                " + ) + .replace( + /
                  /g, + "
                    " + ); + + return safeHTMLWithListFormatting; +} diff --git a/packages/prisma/index.ts b/packages/prisma/index.ts index 6782dbf9c3..2f91849dfe 100644 --- a/packages/prisma/index.ts +++ b/packages/prisma/index.ts @@ -1,6 +1,7 @@ -import { Prisma, PrismaClient } from "@prisma/client"; +import type { Prisma } from "@prisma/client"; +import { PrismaClient } from "@prisma/client"; -import { bookingReferenceMiddleware, eventTypeDescriptionParseAndSanitizeMiddleware } from "./middleware"; +import { bookingReferenceMiddleware } from "./middleware"; declare global { // eslint-disable-next-line no-var @@ -21,7 +22,6 @@ if (process.env.NODE_ENV !== "production") { } // If any changed on middleware server restart is required bookingReferenceMiddleware(prisma); -eventTypeDescriptionParseAndSanitizeMiddleware(prisma); export default prisma; diff --git a/packages/prisma/middleware/eventTypeDescriptionParseAndSanitize.ts b/packages/prisma/middleware/eventTypeDescriptionParseAndSanitize.ts deleted file mode 100644 index ab561e1f20..0000000000 --- a/packages/prisma/middleware/eventTypeDescriptionParseAndSanitize.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { PrismaClient, EventType } from "@prisma/client"; - -import { md } from "@calcom/lib/markdownIt"; - -function parseAndSanitize(description: string) { - const parsedMarkdown = md.render(description); - return parsedMarkdown; -} - -function getParsedResults(eventTypes: EventType | EventType[]) { - const results = Array.isArray(eventTypes) ? eventTypes : [eventTypes]; - const parsedResults = results.map((record) => ({ - ...record, - descriptionAsSafeHTML: record.description ? parseAndSanitize(record.description) : null, - })); - /* If the original result was an array, return the parsed array, otherwise is a single record */ - return Array.isArray(eventTypes) ? parsedResults : parsedResults[0]; -} - -/** - * Parses event type descriptions and treat them as markdown, - * then sanitizes the resulting HTML and adds a new `descriptionAsSafeHTML` property. - */ -function eventTypeDescriptionParseAndSanitize(prisma: PrismaClient) { - prisma.$use(async (params, next) => { - if (params.model === "Team") { - const result = await next(params); - if (result?.eventTypes) { - result.eventTypes = getParsedResults(result.eventTypes); - } - return result; - } else if (params.model === "EventType") { - const result = await next(params); - if (result) { - return getParsedResults(result); - } - return result; - } - return next(params); - }); -} - -export default eventTypeDescriptionParseAndSanitize; diff --git a/packages/prisma/middleware/index.ts b/packages/prisma/middleware/index.ts index fea8946dd6..90097ed181 100644 --- a/packages/prisma/middleware/index.ts +++ b/packages/prisma/middleware/index.ts @@ -1,2 +1 @@ export { default as bookingReferenceMiddleware } from "./bookingReference"; -export { default as eventTypeDescriptionParseAndSanitizeMiddleware } from "./eventTypeDescriptionParseAndSanitize"; diff --git a/packages/prisma/seed-app-store.config.json b/packages/prisma/seed-app-store.config.json index cda49aa8b7..47cec99b9c 100644 --- a/packages/prisma/seed-app-store.config.json +++ b/packages/prisma/seed-app-store.config.json @@ -203,7 +203,8 @@ "categories": ["video"], "slug": "facetime", "type": "facetime_video", - "isTemplate": false}, + "isTemplate": false + }, { "dirName": "zohocrm", "categories": ["other"], diff --git a/packages/trpc/server/routers/viewer/eventTypes.ts b/packages/trpc/server/routers/viewer/eventTypes.ts index 2cd6f7be00..b0cb38a703 100644 --- a/packages/trpc/server/routers/viewer/eventTypes.ts +++ b/packages/trpc/server/routers/viewer/eventTypes.ts @@ -12,6 +12,7 @@ import getApps, { getAppFromLocationValue, getAppFromSlug } from "@calcom/app-st import { validateIntervalLimitOrder } from "@calcom/lib"; import { CAL_URL } from "@calcom/lib/constants"; import getEventTypeById from "@calcom/lib/getEventTypeById"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { baseEventTypeSelect, baseUserSelect } from "@calcom/prisma"; import { _DestinationCalendarModel, _EventTypeModel } from "@calcom/prisma/zod"; import type { CustomInputSchema } from "@calcom/prisma/zod-utils"; @@ -283,6 +284,7 @@ export const eventTypesRouter = router({ const mapEventType = (eventType: (typeof user.eventTypes)[number]) => ({ ...eventType, + safeDescription: markdownToSafeHTML(eventType.description), users: !!eventType.hosts?.length ? eventType.hosts.map((host) => host.user) : eventType.users, // @FIXME: cc @hariombalhara This is failing with production data // metadata: EventTypeMetaDataSchema.parse(eventType.metadata), diff --git a/packages/trpc/server/routers/viewer/teams.tsx b/packages/trpc/server/routers/viewer/teams.tsx index 7982919810..98affd758b 100644 --- a/packages/trpc/server/routers/viewer/teams.tsx +++ b/packages/trpc/server/routers/viewer/teams.tsx @@ -11,6 +11,7 @@ import { updateQuantitySubscriptionFromStripe, } from "@calcom/features/ee/teams/lib/payments"; import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants"; +import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML"; import { getTranslation } from "@calcom/lib/server/i18n"; import { getTeamWithMembers, isTeamAdmin, isTeamMember, isTeamOwner } from "@calcom/lib/server/queries/teams"; import slugify from "@calcom/lib/slugify"; @@ -42,9 +43,9 @@ export const viewerTeamsRouter = router({ throw new TRPCError({ code: "NOT_FOUND", message: "Team not found." }); } const membership = team?.members.find((membership) => membership.id === ctx.user.id); - return { ...team, + safeBio: markdownToSafeHTML(team.bio), membership: { role: membership?.role as MembershipRole, accepted: membership?.accepted, diff --git a/packages/ui/components/editor/plugins/ToolbarPlugin.tsx b/packages/ui/components/editor/plugins/ToolbarPlugin.tsx index 1c4fe0eb9b..ed6efb33c7 100644 --- a/packages/ui/components/editor/plugins/ToolbarPlugin.tsx +++ b/packages/ui/components/editor/plugins/ToolbarPlugin.tsx @@ -351,7 +351,7 @@ export default function ToolbarPlugin(props: TextEditorProps) { editor.registerUpdateListener(({ editorState, prevEditorState }) => { editorState.read(() => { - const textInHtml = $generateHtmlFromNodes(editor); + const textInHtml = $generateHtmlFromNodes(editor).replace(/</g, "<").replace(/>/g, ">"); props.setText(textInHtml); }); if (!prevEditorState._selection) editor.blur(); diff --git a/yarn.lock b/yarn.lock index b0cd77a507..e2ca01c027 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4883,6 +4883,7 @@ __metadata: "@types/react-phone-number-input": ^3.0.14 "@types/react-virtualized-auto-sizer": ^1.0.1 "@types/react-window": ^1.8.5 + "@types/sanitize-html": ^2.9.0 "@types/stripe": ^8.0.417 "@types/turndown": ^5.0.1 "@types/uuid": 8.3.1 @@ -4952,6 +4953,7 @@ __metadata: react-window: ^1.8.7 remark: ^14.0.2 rrule: ^2.7.1 + sanitize-html: ^2.10.0 schema-dts: ^1.1.0 short-uuid: ^4.2.0 strip-markdown: ^5.0.0 @@ -13028,6 +13030,15 @@ __metadata: languageName: node linkType: hard +"@types/sanitize-html@npm:^2.9.0": + version: 2.9.0 + resolution: "@types/sanitize-html@npm:2.9.0" + dependencies: + htmlparser2: ^8.0.0 + checksum: b60f42b740bbfb1b1434ce8b43925a38ecc608b60aa654fd009d2e22e33f324b61d370768c55bd2fd98e03de08518ffa8911d61606c483526fb931bb8b59d1b0 + languageName: node + linkType: hard + "@types/scheduler@npm:*": version: 0.16.2 resolution: "@types/scheduler@npm:0.16.2" @@ -19131,6 +19142,17 @@ __metadata: languageName: node linkType: hard +"dom-serializer@npm:^2.0.0": + version: 2.0.0 + resolution: "dom-serializer@npm:2.0.0" + dependencies: + domelementtype: ^2.3.0 + domhandler: ^5.0.2 + entities: ^4.2.0 + checksum: cd1810544fd8cdfbd51fa2c0c1128ec3a13ba92f14e61b7650b5de421b88205fd2e3f0cc6ace82f13334114addb90ed1c2f23074a51770a8e9c1273acbc7f3e6 + languageName: node + linkType: hard + "dom-walk@npm:^0.1.0": version: 0.1.2 resolution: "dom-walk@npm:0.1.2" @@ -19145,7 +19167,7 @@ __metadata: languageName: node linkType: hard -"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0": +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": version: 2.3.0 resolution: "domelementtype@npm:2.3.0" checksum: ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 @@ -19179,6 +19201,15 @@ __metadata: languageName: node linkType: hard +"domhandler@npm:^5.0.1, domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": + version: 5.0.3 + resolution: "domhandler@npm:5.0.3" + dependencies: + domelementtype: ^2.3.0 + checksum: 0f58f4a6af63e6f3a4320aa446d28b5790a009018707bce2859dcb1d21144c7876482b5188395a188dfa974238c019e0a1e610d2fc269a12b2c192ea2b0b131c + languageName: node + linkType: hard + "domino@npm:^2.1.6": version: 2.1.6 resolution: "domino@npm:2.1.6" @@ -19211,6 +19242,17 @@ __metadata: languageName: node linkType: hard +"domutils@npm:^3.0.1": + version: 3.0.1 + resolution: "domutils@npm:3.0.1" + dependencies: + dom-serializer: ^2.0.0 + domelementtype: ^2.3.0 + domhandler: ^5.0.1 + checksum: 23aa7a840572d395220e173cb6263b0d028596e3950100520870a125af33ff819e6f609e1606d6f7d73bd9e7feb03bb404286e57a39063b5384c62b724d987b3 + languageName: node + linkType: hard + "dot-case@npm:^1.1.0": version: 1.1.2 resolution: "dot-case@npm:1.1.2" @@ -19602,7 +19644,7 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.4.0": +"entities@npm:^4.2.0, entities@npm:^4.4.0": version: 4.4.0 resolution: "entities@npm:4.4.0" checksum: 84d250329f4b56b40fa93ed067b194db21e8815e4eb9b59f43a086f0ecd342814f6bc483de8a77da5d64e0f626033192b1b4f1792232a7ea6b970ebe0f3187c2 @@ -23760,6 +23802,18 @@ __metadata: languageName: node linkType: hard +"htmlparser2@npm:^8.0.0": + version: 8.0.2 + resolution: "htmlparser2@npm:8.0.2" + dependencies: + domelementtype: ^2.3.0 + domhandler: ^5.0.3 + domutils: ^3.0.1 + entities: ^4.4.0 + checksum: 29167a0f9282f181da8a6d0311b76820c8a59bc9e3c87009e21968264c2987d2723d6fde5a964d4b7b6cba663fca96ffb373c06d8223a85f52a6089ced942700 + languageName: node + linkType: hard + "http-cache-semantics@npm:3.8.1": version: 3.8.1 resolution: "http-cache-semantics@npm:3.8.1" @@ -30736,6 +30790,13 @@ __metadata: languageName: node linkType: hard +"parse-srcset@npm:^1.0.2": + version: 1.0.2 + resolution: "parse-srcset@npm:1.0.2" + checksum: 3a0380380c6082021fcce982f0b89fb8a493ce9dfd7d308e5e6d855201e80db8b90438649b31fdd82a3d6089a8ca17dccddaa2b730a718389af4c037b8539ebf + languageName: node + linkType: hard + "parse5-htmlparser2-tree-adapter@npm:^6.0.0": version: 6.0.1 resolution: "parse5-htmlparser2-tree-adapter@npm:6.0.1" @@ -31515,7 +31576,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.13, postcss@npm:^8.4.21": +"postcss@npm:^8.3.11, postcss@npm:^8.4.13, postcss@npm:^8.4.21": version: 8.4.21 resolution: "postcss@npm:8.4.21" dependencies: @@ -34466,6 +34527,20 @@ __metadata: languageName: node linkType: hard +"sanitize-html@npm:^2.10.0": + version: 2.10.0 + resolution: "sanitize-html@npm:2.10.0" + dependencies: + deepmerge: ^4.2.2 + escape-string-regexp: ^4.0.0 + htmlparser2: ^8.0.0 + is-plain-object: ^5.0.0 + parse-srcset: ^1.0.2 + postcss: ^8.3.11 + checksum: 0cb2bb330ed966a4d667b1890322dd868a67f527f87c04d7e3be1688fcfda20f7452a9a7744870751f51e255742e7264a287d9bcfcd64d4cd74a3c99f99c73d2 + languageName: node + linkType: hard + "saslprep@npm:^1.0.3": version: 1.0.3 resolution: "saslprep@npm:1.0.3"