From 14592de6c48c034579f752b23f156956002c1eb4 Mon Sep 17 00:00:00 2001 From: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Date: Tue, 27 May 2025 22:28:03 +0400 Subject: [PATCH] fix: Anonymize host email in ics (#21544) * add virta check for anonymizing host * move to env var * move env var to constants * fix: filter empty string * fix test * fix test --------- Co-authored-by: Udit Takkar --- packages/emails/lib/generateIcsString.test.ts | 16 ++++++++++++---- packages/emails/lib/generateIcsString.ts | 12 +++++++++++- packages/lib/constants.ts | 1 + turbo.json | 3 ++- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/emails/lib/generateIcsString.test.ts b/packages/emails/lib/generateIcsString.test.ts index d271de9e5c..43d0fe5387 100644 --- a/packages/emails/lib/generateIcsString.test.ts +++ b/packages/emails/lib/generateIcsString.test.ts @@ -1,5 +1,6 @@ import { describe, expect } from "vitest"; +import { ORGANIZER_EMAIL_EXEMPT_DOMAINS } from "@calcom/lib/constants"; import { buildCalendarEvent, buildPerson } from "@calcom/lib/test/builder"; import { buildVideoCallData } from "@calcom/lib/test/builder"; import type { CalendarEvent } from "@calcom/types/Calendar"; @@ -26,14 +27,21 @@ const testIcsStringContains = ({ }) => { const DTSTART = `${event.startTime.split(".")[0].split(":").slice(0, 2).join(":").replace(/[-:]/g, "")}00Z`; const DTEND = `${event.endTime.split(".")[0].split(":").slice(0, 2).join(":").replace(/[-:]/g, "")}00Z`; - + const isOrganizerExempt = ORGANIZER_EMAIL_EXEMPT_DOMAINS?.split(",") + .filter((domain) => domain.trim() !== "") + .some((domain) => event.organizer.email.toLowerCase().endsWith(domain.toLowerCase())); expect(icsString).toEqual(expect.stringContaining(`UID:${event.iCalUID}`)); // Sometimes the deeply equal stringMatching error appears. Don't want to add flakey tests // expect(icsString).toEqual(expect.stringContaining(`SUMMARY:${event.title}`)); expect(icsString).toEqual(expect.stringContaining(`DTSTART:${DTSTART}`)); - expect(icsString).toEqual( - expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`) - ); + if (event.hideOrganizerEmail && !isOrganizerExempt) { + expect(icsString).toEqual(expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}`)); + expect(icsString).not.toEqual(expect.stringContaining(`mailto:${event.organizer.email}`)); + } else { + expect(icsString).toEqual( + expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`) + ); + } expect(icsString).toEqual(expect.stringContaining(`DTEND:${DTEND}`)); expect(icsString).toEqual(expect.stringContaining(`STATUS:${status}`)); // Getting an error expected icsString to deeply equal stringMatching diff --git a/packages/emails/lib/generateIcsString.ts b/packages/emails/lib/generateIcsString.ts index fa3dbe4328..981834f2c8 100644 --- a/packages/emails/lib/generateIcsString.ts +++ b/packages/emails/lib/generateIcsString.ts @@ -5,6 +5,7 @@ import { RRule } from "rrule"; import { getRichDescription } from "@calcom/lib/CalEventParser"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; +import { ORGANIZER_EMAIL_EXEMPT_DOMAINS } from "@calcom/lib/constants"; import type { CalendarEvent, Person } from "@calcom/types/Calendar"; export enum BookingAction { @@ -65,6 +66,10 @@ const generateIcsString = ({ recurrenceRule = new RRule(event.recurringEvent).toString().replace("RRULE:", ""); } + const isOrganizerExempt = ORGANIZER_EMAIL_EXEMPT_DOMAINS?.split(",") + .filter((domain) => domain.trim() !== "") + .some((domain) => event.organizer.email.toLowerCase().endsWith(domain.toLowerCase())); + const icsEvent = createEvent({ uid: event.iCalUID || event.uid!, sequence: event.iCalSequence || 0, @@ -74,7 +79,12 @@ const generateIcsString = ({ productId: "calcom/ics", title: event.title, description: getRichDescription(event, t), - organizer: { name: event.organizer.name, email: event.organizer.email }, + organizer: { + name: event.organizer.name, + ...(event.hideOrganizerEmail && !isOrganizerExempt + ? { email: "no-reply@cal.com" } + : { email: event.organizer.email }), + }, ...{ recurrenceRule }, attendees: [ ...event.attendees.map((attendee: Person) => ({ diff --git a/packages/lib/constants.ts b/packages/lib/constants.ts index eead47748c..945621c0a8 100644 --- a/packages/lib/constants.ts +++ b/packages/lib/constants.ts @@ -5,6 +5,7 @@ const RENDER_URL = process.env.RENDER_EXTERNAL_URL ? `https://${process.env.REND export const CALCOM_ENV = process.env.CALCOM_ENV || process.env.NODE_ENV; export const IS_PRODUCTION = CALCOM_ENV === "production"; export const IS_PRODUCTION_BUILD = process.env.NODE_ENV === "production"; +export const ORGANIZER_EMAIL_EXEMPT_DOMAINS = process.env.ORGANIZER_EMAIL_EXEMPT_DOMAINS || ""; const IS_DEV = CALCOM_ENV === "development"; /** https://app.cal.com */ diff --git a/turbo.json b/turbo.json index 8978ee74ed..6188cd0742 100644 --- a/turbo.json +++ b/turbo.json @@ -484,6 +484,7 @@ "CALCOM_ADMIN_API_KEY", "NEXT_PUBLIC_SINGLE_ORG_MODE_ENABLED", "CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY", - "CAL_VIDEO_RECORDING_TOKEN_SECRET" + "CAL_VIDEO_RECORDING_TOKEN_SECRET", + "ORGANIZER_EMAIL_EXEMPT_DOMAINS" ] }