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:
1 parent
4cf89d273e
commit
3b1de34451
8 files changed
+88
-1
No files matched your search
@@ -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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
});
|
||||
+27
@@ -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;
|
||||
+8
@@ -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>;
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user