chore: Add guest email blacklist (#15255)

* chore: Add guest email blacklist

* types

* Added check to only log when we've removed one

* feat: add verification logic

* chore: use base exftract email

* Added toLowerCase for guest email checks

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
This commit is contained in:
Keith WilliamsandUdit Takkar authored and GitHub committed 2024-05-30 17:13:34 +00:00
1 parent 4cf89d273e
commit 3b1de34451
8 files changed
+88 -1

No files matched your search

+3
View File
@@ -359,3 +359,6 @@ UNKEY_ROOT_KEY=
# Used for Cal.ai Enterprise Voice AI Agents
# https://retellai.com
RETELL_AI_KEY=
# Used to disallow emails as being added as guests on bookings
BLACKLISTED_GUEST_EMAILS=
@@ -1,6 +1,8 @@
import { useSession } from "next-auth/react";
import { useState } from "react";
import { useBookerStore } from "@calcom/features/bookings/Booker/store";
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import { showToast } from "@calcom/ui";
@@ -21,6 +23,8 @@ export const useVerifyEmail = ({
const [isEmailVerificationModalVisible, setEmailVerificationModalVisible] = useState(false);
const verifiedEmail = useBookerStore((state) => state.verifiedEmail);
const setVerifiedEmail = useBookerStore((state) => state.setVerifiedEmail);
const debouncedEmail = useDebounce(email, 600);
const { data: session } = useSession();
const { t } = useLocale();
const sendEmailVerificationByCodeMutation = trpc.viewer.auth.sendVerifyEmailCode.useMutation({
@@ -32,6 +36,17 @@ export const useVerifyEmail = ({
},
});
const { data: isEmailVerificationRequired } =
trpc.viewer.public.checkIfUserEmailVerificationRequired.useQuery(
{
userSessionEmail: session?.user.email || "",
email: debouncedEmail,
},
{
enabled: !!debouncedEmail,
}
);
const handleVerifyEmail = () => {
onVerifyEmail?.();
@@ -43,7 +58,8 @@ export const useVerifyEmail = ({
};
const renderConfirmNotVerifyEmailButtonCond =
!requiresBookerEmailVerification || (email && verifiedEmail && verifiedEmail === email);
(!requiresBookerEmailVerification && !isEmailVerificationRequired) ||
(email && verifiedEmail && verifiedEmail === email);
return {
handleVerifyEmail,
@@ -68,6 +68,7 @@ import { getUTCOffsetByTimezone } from "@calcom/lib/date-fns";
import { getDefaultEvent, getUsernameList } from "@calcom/lib/defaultEvents";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { extractBaseEmail } from "@calcom/lib/extract-base-email";
import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server";
import getPaymentAppData from "@calcom/lib/getPaymentAppData";
import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType";
@@ -1398,7 +1399,17 @@ async function handler(
},
];
const blacklistedGuestEmails = process.env.BLACKLISTED_GUEST_EMAILS
? process.env.BLACKLISTED_GUEST_EMAILS.split(",")
: [];
const guestsRemoved: string[] = [];
const guests = (reqGuests || []).reduce((guestArray, guest) => {
const baseGuestEmail = extractBaseEmail(guest).toLowerCase();
if (blacklistedGuestEmails.some((e) => e.toLowerCase() === baseGuestEmail)) {
guestsRemoved.push(guest);
return guestArray;
}
// If it's a team event, remove the team member from guests
if (isTeamEventType && users.some((user) => user.email === guest)) {
return guestArray;
@@ -1414,6 +1425,10 @@ async function handler(
return guestArray;
}, [] as Invitee);
if (guestsRemoved.length > 0) {
log.info("Removed guests from the booking", guestsRemoved);
}
const seed = `${organizerUser.username}:${dayjs(reqBody.start).utc().format()}:${new Date().getTime()}`;
const uid = translator.fromUUID(uuidv5(seed, uuidv5.URL));
+6
View File
@@ -0,0 +1,6 @@
// Function to extract base email
export const extractBaseEmail = (email: string): string => {
const [localPart, domain] = email.split("@");
const baseLocalPart = localPart.split("+")[0];
return `${baseLocalPart}@${domain}`;
};
@@ -1,6 +1,7 @@
import publicProcedure from "../../procedures/publicProcedure";
import { importHandler, router } from "../../trpc";
import { slotsRouter } from "../viewer/slots/_router";
import { ZUserEmailVerificationRequiredSchema } from "./checkIfUserEmailVerificationRequired.schema";
import { i18nInputSchema } from "./i18n.schema";
import { ZNoShowInputSchema } from "./noShow.schema";
import { event } from "./procedures/event";
@@ -56,4 +57,14 @@ export const publicViewerRouter = router({
);
return handler();
}),
checkIfUserEmailVerificationRequired: publicProcedure
.input(ZUserEmailVerificationRequiredSchema)
.query(async (opts) => {
const handler = await importHandler(
namespaced("checkIfUserEmailVerificationRequired"),
() => import("./checkIfUserEmailVerificationRequired.handler")
);
return handler(opts);
}),
});
@@ -0,0 +1,27 @@
import { extractBaseEmail } from "@calcom/lib/extract-base-email";
import logger from "@calcom/lib/logger";
import type { TUserEmailVerificationRequiredSchema } from "./checkIfUserEmailVerificationRequired.schema";
const log = logger.getSubLogger({ prefix: ["checkIfUserEmailVerificationRequired"] });
export const userWithEmailHandler = async ({ input }: { input: TUserEmailVerificationRequiredSchema }) => {
const { userSessionEmail, email } = input;
const baseEmail = extractBaseEmail(email);
const blacklistedGuestEmails = process.env.BLACKLISTED_GUEST_EMAILS
? process.env.BLACKLISTED_GUEST_EMAILS.split(",")
: [];
const blacklistedEmail = blacklistedGuestEmails.find(
(guestEmail: string) => guestEmail.toLowerCase() === baseEmail.toLowerCase()
);
if (!!blacklistedEmail && blacklistedEmail !== userSessionEmail) {
log.warn(`blacklistedEmail: ${blacklistedEmail}`);
return true;
}
return false;
};
export default userWithEmailHandler;
@@ -0,0 +1,8 @@
import { z } from "zod";
export const ZUserEmailVerificationRequiredSchema = z.object({
userSessionEmail: z.string().optional(),
email: z.string(),
});
export type TUserEmailVerificationRequiredSchema = z.infer<typeof ZUserEmailVerificationRequiredSchema>;
+1
View File
@@ -233,6 +233,7 @@
"BASECAMP3_CLIENT_ID",
"BASECAMP3_CLIENT_SECRET",
"BASECAMP3_USER_AGENT",
"BLACKLISTED_GUEST_EMAILS",
"AUTH_BEARER_TOKEN_VERCEL",
"BUILD_ID",
"CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY",