Skip to content

feat: add user-specific email verification setting#24298

Merged
keithwillcode merged 15 commits intomainfrom
devin/booker-email-verification-setting-1759751444
Oct 15, 2025
Merged

feat: add user-specific email verification setting#24298
keithwillcode merged 15 commits intomainfrom
devin/booker-email-verification-setting-1759751444

Conversation

@keithwillcode
Copy link
Contributor

@keithwillcode keithwillcode commented Oct 6, 2025

What does this PR do?

This PR converts the global BLACKLISTED_GUEST_EMAILS environment variable into a user-specific setting that allows users to enable email verification for their event bookings. When enabled, anyone trying to book events using the user's email address must verify email ownership and be logged in to prevent impersonation.

Key Changes:

  • ✅ Added requiresBookerEmailVerification boolean field to User model with database migration
  • ✅ Added settings UI toggle at /settings/my-account/general
  • ✅ Modified core booking logic to check user setting instead of just environment variable
  • ✅ Refactored direct Prisma queries in tRPC handlers to use UserRepository pattern
  • ✅ Added new error codes (BookerEmailBlocked, BookerEmailRequiresLogin) with proper error handling
  • ✅ Updated session middleware and handlers to include new field

Requested by: keith@cal.com
Taken over by: rodrigo@cal.com
Devin Session: https://app.devin.ai/sessions/5b3b084ccf034d35a488edc707adb59c

Visual Demo

Settings UI

The new toggle appears in General Settings:
Settings Toggle

Booking Flow

When enabled, users trying to book with the protected email will see email verification prompts (reuses existing verification system).

Mandatory Tasks

  • I have self-reviewed the code
  • N/A - No documentation changes required (internal feature)
  • I confirm automated tests are in place (existing test suite covers booking flows)

How should this be tested?

Environment Setup:

  • No special environment variables required
  • Standard Cal.com development setup

Test Steps:

  1. Settings UI Test:

    • Navigate to /settings/my-account/general
    • Toggle "Require Email Verification for Bookings" on/off
    • Verify setting persists after page refresh
  2. Booking Protection Test:

    • Enable the setting for User A
    • Try to book User A's event using User A's email while logged out
    • Should trigger email verification flow
    • Try booking while logged in as User A - should work normally
    • Try booking with a different email - should work normally
  3. Guest Filtering Test:

    • Enable setting for User B
    • Try adding User B's email as guest when booking any event
    • User B's email should be filtered out from guest list

Expected Behavior:

  • Setting toggles work and persist
  • Protected emails trigger verification when used by non-owners
  • Existing BLACKLISTED_GUEST_EMAILS behavior preserved
  • No impact on normal booking flows

Human Reviewer Checklist

🚨 High Risk Areas - Please Review Carefully:

  • Booking Logic Changes (packages/features/bookings/lib/handleNewBooking.ts):

    • Guest filtering logic with new emailToRequiresVerification Map
    • Error handling change from HttpError to ErrorWithCode
    • Integration with existing email verification system
  • Repository Pattern Refactoring:

    • New UserRepository methods: findByEmailWithEmailVerificationSetting and findManyByEmailsWithEmailVerificationSettings
    • Proper handling of primary + secondary emails
    • Case-insensitive email matching logic
  • Error Handling Pattern (checkIfBookerEmailIsBlocked.ts):

    • Changed from throwing HttpError to ErrorWithCode
    • Verify catch blocks handle new error types correctly
    • Check error messages are user-friendly
  • Database Migration Safety:

    • New requiresBookerEmailVerification field with proper default (false)
    • Migration only adds field, doesn't modify existing data
  • Session & Middleware Updates:

    • New field included in user session data
    • Settings UI properly connected to backend mutation

⚠️ Known Risks:

  • Large PR touching critical booking functionality
  • Core user table schema change
  • Multiple integration points (UI, booking logic, middleware)
  • Repository pattern introduces new query logic

@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions github-actions bot added the ❗️ migrations contains migration files label Oct 6, 2025
@keithwillcode keithwillcode added core area: core, team members only foundation labels Oct 6, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 6, 2025

Walkthrough

Adds per-user Booker email verification across the stack: UI toggle and i18n keys; Prisma schema + migration adding users.requiresBookerEmailVerification; UserRepository selection and new lookup methods exposing the flag; session and viewer handlers return the flag; profile update schema accepts the field; booking flows (checkIfBookerEmailIsBlocked, handleNewBooking, addGuests, and a public viewer check) consult the flag, accept/validate an optional verificationCode, filter or block guests/bookings accordingly, and migrate error handling to ErrorWithCode; tests and test fixtures updated to cover verification scenarios.

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly and accurately describes the primary change of introducing a user-specific email verification setting, matching the PR’s core objective without extraneous details.
Description Check ✅ Passed The description clearly outlines the purpose of converting a global blacklist into a per-user setting, details the key changes such as database migration, UI toggle, updated booking logic, repository refactoring, and error handling, and provides relevant testing steps, making it fully aligned with the changeset.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/booker-email-verification-setting-1759751444

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 20aafa1 and 875a266.

📒 Files selected for processing (1)
  • packages/prisma/schema.prisma (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/prisma/schema.prisma

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

devin-ai-integration bot added a commit that referenced this pull request Oct 6, 2025
- Remove unrelated Watchlist index drops from migration
- Add missing Watchlist indexes to schema.prisma to fix drift
- Refactor checkIfBookerEmailIsBlocked to throw ErrorWithCode
- Move HttpError handling to handleNewBooking caller layer

Addresses review comments on PR #24298

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
@vercel
Copy link

vercel bot commented Oct 6, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Oct 15, 2025 10:57am
cal-eu Ignored Ignored Oct 15, 2025 10:57am

devin-ai-integration bot added a commit that referenced this pull request Oct 8, 2025
…Watchlist changes

- Add findByEmailWithEmailVerificationSetting method to UserRepository
- Add findManyByEmailsWithEmailVerificationSettings method to UserRepository
- Refactor checkIfUserEmailVerificationRequired handler to use UserRepository
- Refactor addGuests handler to use UserRepository
- Remove unrelated Watchlist schema indices (organizationId/isGlobal, source)
- Remove unrelated WatchlistAudit unique constraint on id

Addresses review comments on PR #24298

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
rodrigoehlers pushed a commit that referenced this pull request Oct 13, 2025
- Remove unrelated Watchlist index drops from migration
- Add missing Watchlist indexes to schema.prisma to fix drift
- Refactor checkIfBookerEmailIsBlocked to throw ErrorWithCode
- Move HttpError handling to handleNewBooking caller layer

Addresses review comments on PR #24298

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
rodrigoehlers pushed a commit that referenced this pull request Oct 13, 2025
…Watchlist changes

- Add findByEmailWithEmailVerificationSetting method to UserRepository
- Add findManyByEmailsWithEmailVerificationSettings method to UserRepository
- Refactor checkIfUserEmailVerificationRequired handler to use UserRepository
- Refactor addGuests handler to use UserRepository
- Remove unrelated Watchlist schema indices (organizationId/isGlobal, source)
- Remove unrelated WatchlistAudit unique constraint on id

Addresses review comments on PR #24298

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
@rodrigoehlers rodrigoehlers force-pushed the devin/booker-email-verification-setting-1759751444 branch from 645d3a5 to 2580c48 Compare October 13, 2025 15:35
rodrigoehlers pushed a commit that referenced this pull request Oct 13, 2025
- Remove unrelated Watchlist index drops from migration
- Add missing Watchlist indexes to schema.prisma to fix drift
- Refactor checkIfBookerEmailIsBlocked to throw ErrorWithCode
- Move HttpError handling to handleNewBooking caller layer

Addresses review comments on PR #24298

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
rodrigoehlers pushed a commit that referenced this pull request Oct 13, 2025
…Watchlist changes

- Add findByEmailWithEmailVerificationSetting method to UserRepository
- Add findManyByEmailsWithEmailVerificationSettings method to UserRepository
- Refactor checkIfUserEmailVerificationRequired handler to use UserRepository
- Refactor addGuests handler to use UserRepository
- Remove unrelated Watchlist schema indices (organizationId/isGlobal, source)
- Remove unrelated WatchlistAudit unique constraint on id

Addresses review comments on PR #24298

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
@rodrigoehlers rodrigoehlers force-pushed the devin/booker-email-verification-setting-1759751444 branch from 2580c48 to af2e5c6 Compare October 13, 2025 15:38
rodrigoehlers pushed a commit that referenced this pull request Oct 14, 2025
- Remove unrelated Watchlist index drops from migration
- Add missing Watchlist indexes to schema.prisma to fix drift
- Refactor checkIfBookerEmailIsBlocked to throw ErrorWithCode
- Move HttpError handling to handleNewBooking caller layer

Addresses review comments on PR #24298

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
rodrigoehlers pushed a commit that referenced this pull request Oct 14, 2025
…Watchlist changes

- Add findByEmailWithEmailVerificationSetting method to UserRepository
- Add findManyByEmailsWithEmailVerificationSettings method to UserRepository
- Refactor checkIfUserEmailVerificationRequired handler to use UserRepository
- Refactor addGuests handler to use UserRepository
- Remove unrelated Watchlist schema indices (organizationId/isGlobal, source)
- Remove unrelated WatchlistAudit unique constraint on id

Addresses review comments on PR #24298

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
@rodrigoehlers rodrigoehlers force-pushed the devin/booker-email-verification-setting-1759751444 branch from 1abe87e to 342c638 Compare October 14, 2025 07:52
@rodrigoehlers rodrigoehlers marked this pull request as ready for review October 14, 2025 08:40
@rodrigoehlers rodrigoehlers force-pushed the devin/booker-email-verification-setting-1759751444 branch from 6745766 to f6e8f45 Compare October 14, 2025 13:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts (1)

30-46: Refactor DB access: use repositories and select (no include).
This handler still performs direct Prisma calls and uses include (and pulls user.credentials). Prior guidance asked to move DB access to repositories and avoid include; select only needed fields. Also avoid fetching credentials here.

As per coding guidelines

Also applies to: 129-136

packages/features/bookings/lib/handleNewBooking.ts (1)

510-521: Don’t coerce all verification errors to 403; map to proper HTTP status and preserve data

Map codes to 400/401/403 and pass error.data through.

   try {
     await checkIfBookerEmailIsBlocked({
       loggedInUserId: userId,
       bookerEmail,
       verificationCode: reqBody.verificationCode,
     });
   } catch (error) {
     if (error instanceof ErrorWithCode) {
-      throw new HttpError({ statusCode: 403, message: error.message });
+      const status =
+        error.code === ErrorCode.InvalidVerificationCode ||
+        error.code === ErrorCode.UnableToValidateVerificationCode
+          ? 400
+          : error.code === ErrorCode.BookerEmailRequiresLogin
+          ? 401
+          : 403;
+      throw new HttpError({
+        statusCode: status,
+        message: error.message,
+        // keep any extra payload (e.g., { email })
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        data: (error as any).data,
+      });
     }
     throw error;
   }
🧹 Nitpick comments (1)
packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts (1)

104-113: Precompute attendee/blacklist sets for O(1) lookups and fewer recomputations.
Replace repeated some()/includes() + extract calls with Sets for clarity and performance.

Apply this diff within the current block:

-  const uniqueGuests = deduplicatedGuests.filter((guest) => {
-    const baseGuestEmail = extractBaseEmail(guest).toLowerCase();
-    return (
-      !booking.attendees.some(
-        (attendee) => extractBaseEmail(attendee.email).toLowerCase() === baseGuestEmail
-      ) &&
-      !blacklistedGuestEmails.includes(baseGuestEmail) &&
-      !emailToRequiresVerification.get(baseGuestEmail)
-    );
-  });
+  const uniqueGuests = deduplicatedGuests.filter((guest) => {
+    const baseGuestEmail = extractBaseEmail(guest).toLowerCase();
+    return (
+      !baseAttendeeEmails.has(baseGuestEmail) &&
+      !blacklistedGuestEmailsSet.has(baseGuestEmail) &&
+      !emailToRequiresVerification.get(baseGuestEmail)
+    );
+  });

Add these declarations just above the filter:

const baseAttendeeEmails = new Set(
  booking.attendees.map((a) => extractBaseEmail(a.email).toLowerCase())
);
const blacklistedGuestEmailsSet = new Set(blacklistedGuestEmails);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0b76a1e and f6e8f45.

📒 Files selected for processing (13)
  • apps/web/modules/settings/my-account/general-view.tsx (2 hunks)
  • apps/web/public/static/locales/en/common.json (1 hunks)
  • packages/features/bookings/lib/handleNewBooking.ts (8 hunks)
  • packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts (2 hunks)
  • packages/features/users/repositories/UserRepository.ts (5 hunks)
  • packages/lib/errorCodes.ts (1 hunks)
  • packages/prisma/migrations/20251006111422_add_requires_booker_email_verification/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (1 hunks)
  • packages/trpc/server/middlewares/sessionMiddleware.ts (3 hunks)
  • packages/trpc/server/routers/publicViewer/checkIfUserEmailVerificationRequired.handler.ts (2 hunks)
  • packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts (3 hunks)
  • packages/trpc/server/routers/viewer/me/get.handler.ts (1 hunks)
  • packages/trpc/server/routers/viewer/me/updateProfile.schema.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/trpc/server/routers/publicViewer/checkIfUserEmailVerificationRequired.handler.ts
  • packages/prisma/schema.prisma
  • apps/web/public/static/locales/en/common.json
  • packages/prisma/migrations/20251006111422_add_requires_booker_email_verification/migration.sql
  • apps/web/modules/settings/my-account/general-view.tsx
  • packages/trpc/server/middlewares/sessionMiddleware.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/me/get.handler.ts
  • packages/trpc/server/routers/viewer/me/updateProfile.schema.ts
  • packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts
  • packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts
  • packages/features/users/repositories/UserRepository.ts
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/lib/errorCodes.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/trpc/server/routers/viewer/me/get.handler.ts
  • packages/trpc/server/routers/viewer/me/updateProfile.schema.ts
  • packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts
  • packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts
  • packages/features/users/repositories/UserRepository.ts
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/lib/errorCodes.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/trpc/server/routers/viewer/me/get.handler.ts
  • packages/trpc/server/routers/viewer/me/updateProfile.schema.ts
  • packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts
  • packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts
  • packages/features/users/repositories/UserRepository.ts
  • packages/features/bookings/lib/handleNewBooking.ts
  • packages/lib/errorCodes.ts
**/*Repository.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Repository files must include Repository suffix, prefix with technology if applicable (e.g., PrismaAppRepository.ts), and use PascalCase matching the exported class

Files:

  • packages/features/users/repositories/UserRepository.ts
🧠 Learnings (1)
📚 Learning: 2025-09-03T09:52:51.182Z
Learnt from: hariombalhara
PR: calcom/cal.com#23541
File: packages/features/bookings/lib/di/modules/RegularBookingServiceModule.ts:22-28
Timestamp: 2025-09-03T09:52:51.182Z
Learning: The IBookingServiceDependencies interface in packages/features/bookings/lib/handleNewBooking.ts contains 6 properties: cacheService, checkBookingAndDurationLimitsService, prismaClient, bookingRepository, featuresRepository, and checkBookingLimitsService. This interface is used by RegularBookingService and matches the depsMap structure in RegularBookingServiceModule.

Applied to files:

  • packages/features/bookings/lib/handleNewBooking.ts
🧬 Code graph analysis (3)
packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts (2)
packages/features/users/repositories/UserRepository.ts (1)
  • UserRepository (124-1181)
packages/emails/email-manager.ts (1)
  • sendAddGuestsEmails (672-697)
packages/features/users/repositories/UserRepository.ts (2)
packages/prisma/zod-utils.ts (1)
  • userMetadata (327-349)
packages/types/Calendar.d.ts (1)
  • SelectedCalendar (321-324)
packages/features/bookings/lib/handleNewBooking.ts (2)
packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts (1)
  • checkIfBookerEmailIsBlocked (7-92)
packages/lib/http-error.ts (1)
  • HttpError (1-45)
🔇 Additional comments (16)
packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts (4)

6-7: Good adoption of UserRepository and base-email util.
Aligns handler with repository pattern and shared normalization.


84-93: Base-email dedupe looks good.
Prevents alias/plus-tag duplicates within the same request.


94-103: Keying by matchedEmail is correct.
Prevents misses when repository matched a secondary email.


212-214: Sending only to filtered guests and logging real error: LGTM.
Correctly avoids emailing blocked/verification-required addresses and preserves error context.

packages/trpc/server/routers/viewer/me/get.handler.ts (1)

143-143: Expose requiresBookerEmailVerification — looks good

Field correctly surfaced from user to API consumer.

packages/trpc/server/routers/viewer/me/updateProfile.schema.ts (1)

24-24: Schema extension is appropriate

Optional boolean is fine and consistent with other flags.

packages/lib/errorCodes.ts (1)

29-32: New error codes aligned with feature

Enum additions are clear and non-breaking.

packages/features/bookings/lib/handleNewBooking.ts (3)

1191-1195: Guest removal by per-user setting — logic reads well

Case-insensitive base matching via map is correct.

Please confirm findManyByEmailsWithEmailVerificationSettings treats input emails case-insensitively and accounts for secondary emails to avoid false negatives.


26-26: Import/type surface updates look consistent

New types and repos are correctly imported as types; tree-shaking unaffected.

Also applies to: 39-39, 41-41, 47-47, 61-61, 98-98


451-459: No DI update needed: PrismaUserRepository is already provided in apps/api/v2/src/lib/modules/regular-booking.module.ts, so userRepository injection is satisfied.

Likely an incorrect or invalid review comment.

packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts (1)

22-24: Blocking logic and Prisma select look correct

  • Case-insensitive env match and minimal select are good.
  • ErrorWithCode usage (no HttpError here) aligns with layering guidance.

Also applies to: 48-51, 54-59, 86-90

packages/features/users/repositories/UserRepository.ts (5)

14-15: LGTM!

The new imports for DestinationCalendar, SelectedCalendar, and Prisma are properly added to support the new type definitions and raw SQL query method.


52-64: LGTM!

The SessionUser type updates improve type safety by using proper Prisma types for destinationCalendar, metadata, profiles, allSelectedCalendars, and userLevelSelectedCalendars. The new requiresBookerEmailVerification field correctly supports the per-user email verification feature.


112-112: LGTM!

Adding requiresBookerEmailVerification to the select object ensures the field is retrieved when needed.


306-318: LGTM!

The method properly normalizes emails, handles empty input, and delegates to the raw SQL method. The inclusion of matchedEmail in the return type is useful for distinguishing whether the primary or secondary email matched.


1005-1005: LGTM!

Adding requiresBookerEmailVerification to the session user select ensures the field is available in session data for booking flows to enforce email verification checks.

Comment on lines +1171 to +1181
const guestEmails = (reqGuests || []).map((email) => extractBaseEmail(email).toLowerCase());
const guestUsers = await userRepository.findManyByEmailsWithEmailVerificationSettings({
emails: guestEmails,
});

const emailToRequiresVerification = new Map<string, boolean>();
for (const user of guestUsers) {
const matchedBase = extractBaseEmail(user.matchedEmail ?? user.email).toLowerCase();
emailToRequiresVerification.set(matchedBase, user.requiresBookerEmailVerification === true);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Deduplicate guest emails before lookup; avoid logging raw PII

  • Use a Set to dedupe base emails to reduce query load.
  • guestsRemoved is later logged (Lines 1211-1213); avoid logging raw emails. Log count and masked values instead.
-  const guestEmails = (reqGuests || []).map((email) => extractBaseEmail(email).toLowerCase());
+  const guestEmails = Array.from(
+    new Set((reqGuests || []).map((email) => extractBaseEmail(email).toLowerCase()))
+  );

Example masking for later log (near Lines 1211-1213):

-  log.info("Removed guests from the booking", guestsRemoved);
+  const masked = guestsRemoved.map((e) => {
+    const [local, domain] = e.split("@");
+    return (local?.slice(0, 2) || "") + "***@" + (domain || "");
+  });
+  log.info("Removed guests from the booking", { count: guestsRemoved.length, masked });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const guestEmails = (reqGuests || []).map((email) => extractBaseEmail(email).toLowerCase());
const guestUsers = await userRepository.findManyByEmailsWithEmailVerificationSettings({
emails: guestEmails,
});
const emailToRequiresVerification = new Map<string, boolean>();
for (const user of guestUsers) {
const matchedBase = extractBaseEmail(user.matchedEmail ?? user.email).toLowerCase();
emailToRequiresVerification.set(matchedBase, user.requiresBookerEmailVerification === true);
}
const guestEmails = Array.from(
new Set((reqGuests || []).map((email) => extractBaseEmail(email).toLowerCase()))
);
const guestUsers = await userRepository.findManyByEmailsWithEmailVerificationSettings({
emails: guestEmails,
});
const emailToRequiresVerification = new Map<string, boolean>();
for (const user of guestUsers) {
const matchedBase = extractBaseEmail(user.matchedEmail ?? user.email).toLowerCase();
emailToRequiresVerification.set(matchedBase, user.requiresBookerEmailVerification === true);
}
🤖 Prompt for AI Agents
In packages/features/bookings/lib/handleNewBooking.ts around lines 1171 to 1181,
dedupe guest base-emails before querying and avoid handling raw PII in logs:
compute a Set of extractBaseEmail(email).toLowerCase() from reqGuests, convert
that Set to an array and pass it to
userRepository.findManyByEmailsWithEmailVerificationSettings to reduce query
load, build the emailToRequiresVerification map from the returned users using
matchedBase as before, and replace any future logging of removed/filtered guest
emails (near lines 1211-1213) to log only the count and masked forms (e.g.,
partial redaction) rather than raw email strings.

Comment on lines +70 to +72
try {
isValid = await verifyCodeUnAuthenticated(baseEmail, verificationCode);
} catch {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Bug: verification code validated against baseEmail instead of the exact email

Codes are issued to the original address; validating with a stripped base can cause false negatives for plus-addressing/aliases.

-        isValid = await verifyCodeUnAuthenticated(baseEmail, verificationCode);
+        isValid = await verifyCodeUnAuthenticated(bookerEmail, verificationCode);

🏁 Script executed:

#!/bin/bash
# Audit all usages to ensure we validate against the exact email elsewhere
rg -n -C2 'verifyCodeUnAuthenticated\s*\('

Length of output: 2120


🏁 Script executed:

#!/bin/bash
# Inspect definition and usage of baseEmail and bookerEmail in the file
rg -n -C3 'baseEmail' packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts
rg -n -C3 'bookerEmail' packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts

Length of output: 1831


Replace baseEmail with bookerEmail in verification call

- isValid = await verifyCodeUnAuthenticated(baseEmail, verificationCode);
+ isValid = await verifyCodeUnAuthenticated(bookerEmail, verificationCode);

Ensures codes issued to alias addresses validate correctly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
isValid = await verifyCodeUnAuthenticated(baseEmail, verificationCode);
} catch {
try {
isValid = await verifyCodeUnAuthenticated(bookerEmail, verificationCode);
} catch {
🤖 Prompt for AI Agents
In
packages/features/bookings/lib/handleNewBooking/checkIfBookerEmailIsBlocked.ts
around lines 70 to 72, the call to verifyCodeUnAuthenticated currently passes
baseEmail but should pass bookerEmail so verification uses the actual booker
address (ensuring codes issued to alias addresses validate correctly); update
the function call to use bookerEmail and keep existing try/catch logic intact.

Comment on lines +279 to +304
async findByEmailWithEmailVerificationSetting({ email }: { email: string }) {
const user = await this.prismaClient.user.findFirst({
where: {
OR: [
{
email: email.toLowerCase(),
emailVerified: { not: null },
},
{
secondaryEmails: {
some: {
email: email.toLowerCase(),
emailVerified: { not: null },
},
},
},
],
},
select: {
id: true,
email: true,
requiresBookerEmailVerification: true,
},
});
return user;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add locked user filtering for consistency and security.

The method doesn't filter out locked users, but findVerifiedUsersByEmailsRaw at lines 340 and 354 explicitly filters locked = FALSE. This inconsistency could allow locked users to bypass email verification checks in booking flows.

Apply this diff to add the locked user filter:

 async findByEmailWithEmailVerificationSetting({ email }: { email: string }) {
   const user = await this.prismaClient.user.findFirst({
     where: {
+      locked: false,
       OR: [
         {
           email: email.toLowerCase(),
           emailVerified: { not: null },
         },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async findByEmailWithEmailVerificationSetting({ email }: { email: string }) {
const user = await this.prismaClient.user.findFirst({
where: {
OR: [
{
email: email.toLowerCase(),
emailVerified: { not: null },
},
{
secondaryEmails: {
some: {
email: email.toLowerCase(),
emailVerified: { not: null },
},
},
},
],
},
select: {
id: true,
email: true,
requiresBookerEmailVerification: true,
},
});
return user;
}
async findByEmailWithEmailVerificationSetting({ email }: { email: string }) {
const user = await this.prismaClient.user.findFirst({
where: {
locked: false,
OR: [
{
email: email.toLowerCase(),
emailVerified: { not: null },
},
{
secondaryEmails: {
some: {
email: email.toLowerCase(),
emailVerified: { not: null },
},
},
},
],
},
select: {
id: true,
email: true,
requiresBookerEmailVerification: true,
},
});
return user;
}
🤖 Prompt for AI Agents
In packages/features/users/repositories/UserRepository.ts around lines 279 to
304, the query in findByEmailWithEmailVerificationSetting doesn't exclude locked
users unlike findVerifiedUsersByEmailsRaw; update the Prisma where clause to
include locked: false (e.g., add locked: false at the top-level of the where so
both the primary email and secondaryEmails branches respect the locked=false
filter) so locked accounts are consistently excluded from verified-email
lookups.

Comment on lines +320 to +356
private async findVerifiedUsersByEmailsRaw(emails: string[]) {
const emailListSql = Prisma.join(emails.map((e) => Prisma.sql`${e}`));
return this.prismaClient.$queryRaw<
Array<{
id: number;
email: string;
matchedEmail: string;
requiresBookerEmailVerification: boolean;
}>
>(Prisma.sql`
SELECT
u."id",
u."email",
u."email" AS "matchedEmail",
u."requiresBookerEmailVerification"
FROM
"public"."users" AS u
WHERE
u."email" IN (${emailListSql})
AND u."emailVerified" IS NOT NULL
AND u."locked" = FALSE
UNION
SELECT
u."id",
u."email",
t0."email" AS "matchedEmail",
u."requiresBookerEmailVerification"
FROM
"public"."users" AS u
INNER JOIN "public"."SecondaryEmail" AS t0
ON t0."userId" = u."id"
WHERE
t0."email" IN (${emailListSql})
AND t0."emailVerified" IS NOT NULL
AND u."locked" = FALSE
`);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent duplicate results when a user has multiple matching emails.

The UNION query doesn't deduplicate results. If a user has both their primary email and one of their secondary emails in the emails array, they'll appear twice in the results (once from each SELECT). This could lead to duplicate processing in calling code.

Apply this diff to deduplicate results using UNION instead of keeping it as-is, or use a CTE with DISTINCT:

-    >(Prisma.sql`
+    >(Prisma.sql`
+      WITH matched_users AS (
       SELECT
         u."id",
         u."email",
         u."email" AS "matchedEmail",
         u."requiresBookerEmailVerification"
       FROM
         "public"."users" AS u
       WHERE
         u."email" IN (${emailListSql})
         AND u."emailVerified" IS NOT NULL
         AND u."locked" = FALSE
       UNION
       SELECT
         u."id",
         u."email",
         t0."email" AS "matchedEmail",
         u."requiresBookerEmailVerification"
       FROM
         "public"."users" AS u
       INNER JOIN "public"."SecondaryEmail" AS t0
         ON t0."userId" = u."id"
       WHERE
         t0."email" IN (${emailListSql})
         AND t0."emailVerified" IS NOT NULL
         AND u."locked" = FALSE
+      )
+      SELECT DISTINCT ON ("id", "matchedEmail")
+        "id",
+        "email",
+        "matchedEmail",
+        "requiresBookerEmailVerification"
+      FROM matched_users
     `);

Alternatively, if you need all matched emails per user, consider returning a structure that groups them by user ID in the calling code.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/features/users/repositories/UserRepository.ts around lines 320-356,
the current UNION returns duplicate rows when a user matches both primary and
secondary emails because the matchedEmail column differs; change the query to
first UNION all matches in a CTE or subquery and then SELECT from that CTE
grouping by user id (and other user fields) to deduplicate — e.g. aggregate
matched emails into an array (array_agg) or use DISTINCT ON (u.id) /
MIN(matchedEmail) to ensure each user appears only once while preserving matched
email(s) as a list.

- Add 9 test scenarios covering user email verification setting
- Test main booker verification (logged in/out, with/without code)
- Test secondary email verification as main booker and guest
- Test guest filtering when verification is required
- Test plus-addressed email handling
- Test multiple guests with mixed verification requirements
- Test invalid verification code error handling
- Update bookingScenario helper to support requiresBookerEmailVerification and secondaryEmails

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (10)
apps/web/test/utils/bookingScenario/bookingScenario.ts (4)

12-12: Decouple tests from repositories when possible

Importing ProfileRepository solely for UID generation couples test utils to domain code. Consider generating a UID locally (e.g., using uuidv4 already imported) unless the exact format from ProfileRepository is required by assertions.


839-853: Batch-create secondary emails to speed up tests

Create entries in bulk to reduce per-item awaits.

Apply this diff:

-  for (const user of users) {
-    if (user.secondaryEmails) {
-      log.debug("Creating SecondaryEmail entries for user", user.id);
-      for (const secondaryEmail of user.secondaryEmails) {
-        await prismock.secondaryEmail.create({
-          data: {
-            email: secondaryEmail.email,
-            emailVerified: secondaryEmail.emailVerified,
-            userId: user.id,
-          },
-        });
-      }
-    }
-  }
+  for (const user of users) {
+    if (user.secondaryEmails?.length) {
+      log.debug("Creating SecondaryEmail entries for user", user.id);
+      await prismock.secondaryEmail.createMany({
+        data: user.secondaryEmails.map((se) => ({
+          email: se.email,
+          emailVerified: se.emailVerified,
+          userId: user.id,
+        })),
+      });
+    }
+  }

865-865: Prefer select over include for Prisma queries

Switch to select with the minimal fields/relations required to align with the project guidelines. This avoids over-fetching and matches production patterns. As per coding guidelines


1575-1577: getOrganizer: plumbed-through fields; consider defaults and shared types

  • Optionally default requiresBookerEmailVerification to false to mirror DB default and avoid undefined in tests.
  • Extract a shared SecondaryEmailInput type and reuse it here and in InputUser to avoid duplication.

Also applies to: 1595-1597, 1619-1621

packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts (6)

752-755: Use Prisma select instead of include (tests too)

Select only fields you need. Avoid include per guidelines.

As per coding guidelines

-      const booking = await prismaMock.booking.findFirst({
-        where: { id: createdBooking.id },
-        include: { attendees: true },
-      });
+      const booking = await prismaMock.booking.findFirst({
+        where: { id: createdBooking.id },
+        select: { attendees: { select: { email: true } } },
+      });

836-839: Use select over include here as well

Keep Prisma usage consistent; select only attendee email.

As per coding guidelines

-      const booking = await prismaMock.booking.findFirst({
-        where: { id: createdBooking.id },
-        include: { attendees: true },
-      });
+      const booking = await prismaMock.booking.findFirst({
+        where: { id: createdBooking.id },
+        select: { attendees: { select: { email: true } } },
+      });

1000-1003: Prefer select here too

Avoid include; select attendee email only.

As per coding guidelines

-      const booking = await prismaMock.booking.findFirst({
-        where: { id: createdBooking.id },
-        include: { attendees: true },
-      });
+      const booking = await prismaMock.booking.findFirst({
+        where: { id: createdBooking.id },
+        select: { attendees: { select: { email: true } } },
+      });

516-527: Also assert verification isn’t called when logged in

Strengthen signal: when owner is logged in, code verification should not run.

       const createdBooking = await handleNewBooking({
         bookingData: mockBookingData,
         userId: 201,
       });
 
+      const { verifyCodeUnAuthenticated } = await import("@calcom/trpc/server/routers/viewer/auth/util");
+      expect(verifyCodeUnAuthenticated).not.toHaveBeenCalled();

752-760: Add explicit null-check on booking fetch (clearer failures)

If booking isn’t found, tests fail with undefined errors. Assert non-null for clarity.

-      const guestEmails = booking?.attendees.map((a) => a.email).filter((e) => e !== booker.email);
+      expect(booking).toBeTruthy();
+      const guestEmails = booking!.attendees.map((a) => a.email).filter((e) => e !== booker.email);

Also applies to: 841-844, 1005-1012


846-912: Add case-insensitive email test (optional)

Recommend a test using User@Example.com to ensure CI-matching across primary/secondary emails and plus-address normalization.

I can add a parametric test covering case-insensitive main/secondary emails and plus-address combos. Want me to draft it?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f6e8f45 and 4ed356e.

📒 Files selected for processing (2)
  • apps/web/test/utils/bookingScenario/bookingScenario.ts (7 hunks)
  • packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts (10 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts
  • apps/web/test/utils/bookingScenario/bookingScenario.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts
  • apps/web/test/utils/bookingScenario/bookingScenario.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts
  • apps/web/test/utils/bookingScenario/bookingScenario.ts
🧬 Code graph analysis (1)
packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts (1)
apps/web/test/utils/bookingScenario/bookingScenario.ts (7)
  • getBooker (2274-2288)
  • getOrganizer (1557-1622)
  • TestData (1276-1548)
  • getGoogleCalendarCredential (1229-1237)
  • createBookingScenario (1015-1046)
  • getScenarioData (1624-1712)
  • mockCalendarToHaveNoBusySlots (2013-2026)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Linters / lint
  • GitHub Check: Tests / Unit
  • GitHub Check: Type check / check-types
🔇 Additional comments (3)
apps/web/test/utils/bookingScenario/bookingScenario.ts (1)

243-245: InputUser extensions look good

requiresBookerEmailVerification and secondaryEmails additions align with the feature and test use cases.

packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts (2)

27-29: Mock wiring looks good

Module-level vi.mock + dynamic import pattern is correct and isolates verifyCodeUnAuthenticated for each test.


39-41: Good cleanup of env state

afterEach reset of BLACKLISTED_GUEST_EMAILS prevents test bleed.

Comment on lines +101 to 107
await expect(
handleNewBooking({
bookingData: mockBookingData,
})
).rejects.toThrow(
"Attendee email has been blocked. Make sure to login as organizer@example.com to use this email for creating a booking."
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Assert error codes, not messages

Message strings are brittle. PR introduces ErrorWithCode and new codes; assert on error.code instead.

Example change for one test (apply similarly to others):

-      await expect(
-        handleNewBooking({
-          bookingData: mockBookingData,
-        })
-      ).rejects.toThrow(
-        "Attendee email has been blocked. Make sure to login as user@example.com to use this email for creating a booking."
-      );
+      await expect(
+        handleNewBooking({
+          bookingData: mockBookingData,
+        })
+      ).rejects.toHaveProperty("code", "BookerEmailRequiresLogin");

Run this to find the exact code identifiers to use and where they’re thrown:

Also applies to: 179-184, 447-454, 670-676, 906-910, 1078-1083


🏁 Script executed:

#!/bin/bash
# Locate new error codes and their throw sites
rg -nP -C2 '(BookerEmail[A-Za-z]+|InvalidVerificationCode|VerificationCode)' packages | sed -n '1,200p'
rg -nPU -C3 '(?s)throw new\s+ErrorWithCode\('
# Find tests asserting the specific strings to update
rg -nP -C2 'toThrow\([^)]*Attendee email has been blocked|Invalid verification code|booker_limit_exceeded_error' --type=ts

Length of output: 29705


Assert on error codes, not messages
Replace all .toThrow(<message>) and expect(error.message).toEqual(...) in booking-validations.test.ts with assertions on the code property via rejects.toHaveProperty('code', ErrorCode.XYZ) (or expect(error).toHaveProperty('code', ErrorCode.XYZ)).
Import ErrorCode from @calcom/lib/errorCodes.
Apply to all occurrences at lines 179–184, 447–454, 670–676, 906–910, and 1078–1083.

🤖 Prompt for AI Agents
In
packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts
around lines 101–107 and additionally at 179–184, 447–454, 670–676, 906–910,
1078–1083, replace assertions that check error messages (e.g.,
.toThrow(<message>) or expect(error.message).toEqual(...)) with assertions on
the error.code property: import ErrorCode from '@calcom/lib/errorCodes' at the
top of the test file, then for promise rejections use await
expect(...).rejects.toHaveProperty('code', ErrorCode.<APPROPRIATE_CODE>) or for
caught errors use expect(error).toHaveProperty('code',
ErrorCode.<APPROPRIATE_CODE>); ensure you choose the matching ErrorCode constant
for each assertion and remove the string message checks.

Move guests array from top-level booking data into responses object
to match expected structure in getBookingData.ts which looks for
responses.guests (line 74).

Fixes three failing tests:
- should filter out guest that requires verification
- should filter out secondary email with verification when added as guest
- should filter only guests requiring verification from multiple guests

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts (1)

129-136: Assert error.code, not brittle messages

Previous feedback still applies: these tests need to check the new ErrorWithCode values instead of string messages. Import ErrorCode and switch each .rejects.toThrow("…") / expect(error.message) to rejects.toHaveProperty("code", ErrorCode.BookerEmailRequiresLogin) (and the appropriate constant per case). Please update all occurrences in this file.

Also applies to: 208-213, 303-307, 396-407, 476-483, 699-705, 951-956, 1136-1141

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed356e and 20aafa1.

📒 Files selected for processing (2)
  • apps/web/test/utils/bookingScenario/getMockRequestDataForBooking.ts (1 hunks)
  • packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts (10 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • apps/web/test/utils/bookingScenario/getMockRequestDataForBooking.ts
  • packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • apps/web/test/utils/bookingScenario/getMockRequestDataForBooking.ts
  • packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • apps/web/test/utils/bookingScenario/getMockRequestDataForBooking.ts
  • packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts
🔇 Additional comments (1)
apps/web/test/utils/bookingScenario/getMockRequestDataForBooking.ts (1)

30-30: LGTM! Clean extension for guest verification testing.

The optional guests field appropriately extends the mock data type to support testing scenarios with multiple guest emails, aligning with the PR's Booker email verification feature.

Comment on lines +412 to +415
beforeEach(() => {
vi.clearAllMocks();
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not clear repository mocks without restoring defaults

This inner beforeEach runs after the outer one, so calling vi.clearAllMocks() here wipes out the default mockResolvedValue([]/null) stubs set above. Every test in this block now gets undefined back from the repository methods, which breaks the booking code that expects arrays. Drop this clear or reapply the default stubs immediately after it; otherwise the verification tests will fail nondeterministically.

🤖 Prompt for AI Agents
In
packages/features/bookings/lib/handleNewBooking/test/booking-validations.test.ts
around lines 412 to 415, the inner beforeEach calling vi.clearAllMocks() wipes
the default repository mockResolvedValue stubs established in the outer
beforeEach and causes repository calls to return undefined; remove this
vi.clearAllMocks() or replace it with a non-destructive call (e.g.,
vi.resetHistory()) OR if you must clear mocks, immediately reapply the default
mockResolvedValue([]/null) stubs after clearing so repository methods continue
returning the expected arrays/nulls for the tests.

Copy link
Member

@CarinaWolli CarinaWolli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good and works as expected 👏🏻

@github-actions
Copy link
Contributor

github-actions bot commented Oct 15, 2025

E2E results are ready!

Copy link
Contributor

@emrysal emrysal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, minor issue is that emailVerified doesn't have an index on the secondary email table but I don't think that's a problem.

@keithwillcode keithwillcode merged commit 64297f0 into main Oct 15, 2025
59 of 64 checks passed
@keithwillcode keithwillcode deleted the devin/booker-email-verification-setting-1759751444 branch October 15, 2025 11:36
sean-brydon pushed a commit that referenced this pull request Oct 27, 2025
## What does this PR do?

<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->

Removes and replaces the singular query that was still using the slow OR query from prisma. It was mainly used on the public booking page and triggered after someone entered their email (600ms debounce delay).

## Visual Demo (For contributors especially)

Internal change only nothing changes on the UI or business logic.

#### Video Demo (if applicable):

N/A

#### Image Demo (if applicable):

N/A

## Mandatory Tasks (DO NOT REMOVE)

- [x] I have self-reviewed the code (A decent size PR without self-review might be rejected).
- [x] I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://cal.com/docs). If N/A, write N/A here and check the checkbox.
- [x] I confirm automated tests are in place that prove my fix is effective or that my feature works.

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->

Basically the same as #24298 since it touches relevant code.

1. Have a user that has the setting to prevent impersonation on.
2. Go to another users public booking page of any meeting.
3. Try to enter the email of the first user into the booking for.
4. Wait 1 second and make sure the button shows "Verify email".

## Checklist

<!-- Remove bullet points below that don't apply to you -->

- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
- My code doesn't follow the style guidelines of this project
- I haven't commented my code, particularly in hard-to-understand areas
- I haven't checked if my changes generate no new warnings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bookings area: bookings, availability, timezones, double booking core area: core, team members only ✨ feature New feature or request foundation ❗️ migrations contains migration files ready-for-e2e size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants