feat: Add async spam check and decoy booking response#24326
feat: Add async spam check and decoy booking response#24326alishaz-polymath merged 6 commits intomainfrom
Conversation
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
WalkthroughAdds spam-blocking into the booking flow: a new SpamCheckService with DI bindings and a factory is introduced and invoked from handleNewBooking to start and await a spam check. When blocked, handlers return a short-circuited decoy booking (flagged with isShortCircuitedBooking) instead of persisting the real booking. Client-side support stores decoy data in localStorage (store/get/remove), exposes a useDecoyBooking hook, and renders decoy success UI via DecoyBookingSuccessCard and a booking-successful/[uid] page. useBookings persists decoys and redirects to the success route. Tests and minor DB select changes were added. Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (3)**/*.ts📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Files:
**/*.{ts,tsx,js,jsx}⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (1)📚 Learning: 2025-09-03T09:52:51.182ZApplied to files:
🧬 Code graph analysis (1)packages/features/bookings/lib/handleNewBooking.ts (4)
⏰ 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). (8)
🔇 Additional comments (4)
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. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
57db386 to
eb9900b
Compare
89258f0 to
649e0c2
Compare
…am exploitation
- Generate unique UID for localStorage key on booking success
- Store booking display data in localStorage instead of query params
- Redirect to /booking-successful/{uid} route
- Read from localStorage and redirect to actual booking page
- Clean up localStorage after reading to prevent stale data
- Add 24-hour expiration for localStorage data
- Preserve custom successRedirectUrl behavior for external redirects
- Support embed mode with dedicated embed route
This prevents malicious actors from crafting fake booking confirmation
pages by manipulating query parameters.
Stacked on PR #24326
Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>
66c1811 to
1ed7cc6
Compare
- Integrate SpamCheckService with handleNewBooking workflow - Implement parallel spam check execution for minimal performance impact - Add decoy booking response with localStorage-based success page - Extract organization ID from event type for org-specific blocking - Add comprehensive test coverage for spam detection scenarios - Create reusable components for booking success cards - Implement fail-open behavior to never block legitimate bookings This builds on the spam blocker DI infrastructure from PR #24040 by adding the actual integration into the booking flow and implementing the decoy response mechanism to avoid revealing spam detection to malicious actors. Related: #24040 Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>
7a5f6f9 to
bd229ca
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (4)
packages/features/bookings/lib/client/decoyBookingStore.ts (2)
26-33: Type safety concern: overly permissive signature allows arbitrary data storage.The
Record<string, unknown>type accepts any object structure, making it easy to accidentally store sensitive data beyond what's needed for the decoy UI. While the past review comment addressed limiting stored fields in the calling code (useBookings.ts), the store function itself provides no safeguards.Consider tightening the signature to match DecoyBookingData or creating a dedicated input type:
+interface StoreDecoyBookingInput { + uid: string; + title: string | null; + startTime: string; + endTime: string; + booker: { name: string; timeZone?: string; email: string } | null; + host: { name: string; timeZone?: string } | null; + location: string | null; +} + -export function storeDecoyBooking(booking: Record<string, unknown> & { uid: string }): void { +export function storeDecoyBooking(booking: StoreDecoyBookingInput): void {
56-65: PII retention risk: 24-hour TTL and missing consume-on-read.Storing names/emails in localStorage for 24 hours poses a privacy risk (XSS exfiltration). The data should be removed immediately upon successful retrieval, and the TTL should be reduced to minimize exposure.
This mirrors the past review comment. Apply the suggested changes to reduce TTL to 1 hour (or less) and implement consume-on-read:
- const maxAge = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + const maxAge = 60 * 60 * 1000; // 1 hour in milliseconds if (dataAge > maxAge) { localStorage.removeItem(storageKey); return null; } - return data; + // Consume-on-read to minimize PII exposure + localStorage.removeItem(storageKey); + return data;packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx (1)
37-42: Accessibility blocker: aria-hidden hides dialog from screen readers.The past review comment marked this as addressed, but
aria-hidden="true"is still present on line 37, hiding the entire dialog and its children from assistive technology. This prevents screen reader users from accessing the booking confirmation content.Apply the previously suggested fix:
- <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0" aria-hidden="true"> + <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0"> <div className="bg-default dark:bg-muted border-booker border-booker-width inline-block transform overflow-hidden rounded-lg px-8 pb-4 pt-5 text-left align-bottom transition-all sm:my-8 sm:w-full sm:max-w-xl sm:py-8 sm:align-middle" role="dialog" aria-modal="true" aria-labelledby="modal-headline">If a backdrop dimming effect is needed, add a separate overlay div with aria-hidden instead of hiding the dialog container.
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (1)
374-399: Mock doesn't intercept the service instance used by handler.This test creates a spy on a single service instance, but
handleNewBookingcallsgetSpamCheckService()again, creating a new instance that bypasses your spy. The fail-open behavior is not actually being tested.As noted in the previous review, you need to either:
- Option A: Mock the container module so
getSpamCheckService()returns your mocked service- Option B: Spy on
SpamCheckService.prototypeto intercept all instancesThe current approach creates a false positive—the test passes even though it's not actually verifying fail-open behavior.
Refer to the detailed solutions in the previous review comment above.
🧹 Nitpick comments (2)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
7-10: Remove redundant type assertions.The type assertions
as GlobalBlockingServiceandas OrganizationBlockingServiceare unnecessary since the getter functions already return the correct types (as declared in watchlist.ts lines 41 and 48).Apply this diff:
export const getSpamCheckService = (): SpamCheckService => { - const globalBlockingService = getGlobalBlockingService() as GlobalBlockingService; - const organizationBlockingService = getOrganizationBlockingService() as OrganizationBlockingService; + const globalBlockingService = getGlobalBlockingService(); + const organizationBlockingService = getOrganizationBlockingService(); return new SpamCheckService(globalBlockingService, organizationBlockingService); };packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (1)
39-39: Simplify boolean logic.The ternary expression can be simplified to a direct comparison.
Apply this diff:
- isGlobal: overrides.organizationId !== null ? false : true, + isGlobal: overrides.organizationId === null,
📜 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.
📒 Files selected for processing (13)
apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx(1 hunks)apps/web/test/utils/bookingScenario/bookingScenario.ts(1 hunks)packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx(1 hunks)packages/features/bookings/Booker/components/hooks/useBookings.ts(7 hunks)packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts(1 hunks)packages/features/bookings/components/BookingSuccessCard.tsx(1 hunks)packages/features/bookings/lib/client/decoyBookingStore.ts(1 hunks)packages/features/bookings/lib/handleNewBooking.ts(4 hunks)packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts(1 hunks)packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts(1 hunks)packages/features/di/watchlist/containers/SpamCheckService.container.ts(1 hunks)packages/features/di/watchlist/containers/watchlist.ts(2 hunks)packages/features/watchlist/lib/service/SpamCheckService.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web/test/utils/bookingScenario/bookingScenario.ts
- packages/features/bookings/components/BookingSuccessCard.tsx
- packages/features/watchlist/lib/service/SpamCheckService.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 useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/features/di/watchlist/containers/SpamCheckService.container.tspackages/features/di/watchlist/containers/watchlist.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.tspackages/features/bookings/Booker/components/hooks/useDecoyBooking.tspackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.tspackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/bookings/Booker/components/hooks/useBookings.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/di/watchlist/containers/SpamCheckService.container.tspackages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsxpackages/features/di/watchlist/containers/watchlist.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.tspackages/features/bookings/Booker/components/hooks/useDecoyBooking.tspackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.tspackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/bookings/Booker/components/hooks/useBookings.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/di/watchlist/containers/SpamCheckService.container.tspackages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsxpackages/features/di/watchlist/containers/watchlist.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.tspackages/features/bookings/Booker/components/hooks/useDecoyBooking.tspackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.tspackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/bookings/Booker/components/hooks/useBookings.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Always use
t()for text localization in frontend code; direct text embedding should trigger a warning
Files:
packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx
🧠 Learnings (4)
📚 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
📚 Learning: 2025-08-21T13:44:06.805Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.805Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
📚 Learning: 2025-09-03T11:54:05.409Z
Learnt from: supalarry
PR: calcom/cal.com#23514
File: apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts:579-582
Timestamp: 2025-09-03T11:54:05.409Z
Learning: In calcom/cal.com bookings repository methods, when Prisma include uses `eventType: true`, all eventType fields including seatsShowAttendees are automatically included in the selection. Explicit field selection is not required when using `true` for nested relations.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
📚 Learning: 2025-09-03T11:54:05.409Z
Learnt from: supalarry
PR: calcom/cal.com#23514
File: apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts:579-582
Timestamp: 2025-09-03T11:54:05.409Z
Learning: In calcom/cal.com bookings repository methods, when Prisma select uses `eventType: true`, all eventType fields including seatsShowAttendees are automatically included in the selection. Explicit field selection is not required when using `true` for nested relations.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
🧬 Code graph analysis (8)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (4)
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
SpamCheckService(14-57)packages/features/di/watchlist/containers/watchlist.ts (2)
getGlobalBlockingService(41-46)getOrganizationBlockingService(48-53)packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)
apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx (2)
packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts (1)
useDecoyBooking(14-30)packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx (1)
DecoyBookingSuccessCard(18-114)
packages/features/di/watchlist/containers/watchlist.ts (6)
packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/watchlist/lib/interface/IWatchlistRepositories.ts (2)
IGlobalWatchlistRepository(9-36)IOrganizationWatchlistRepository(42-79)packages/features/di/watchlist/Watchlist.tokens.ts (1)
WATCHLIST_DI_TOKENS(1-12)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)packages/features/watchlist/lib/service/WatchlistAuditService.ts (1)
WatchlistAuditService(13-44)packages/features/watchlist/lib/interface/IAuditRepository.ts (1)
IAuditRepository(20-33)
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (3)
packages/features/bookings/lib/handleNewBooking/test/getNewBookingHandler.ts (1)
getNewBookingHandler(15-17)apps/web/test/utils/bookingScenario/bookingScenario.ts (9)
getBooker(2250-2264)getOrganizer(1539-1598)TestData(1258-1530)getGoogleCalendarCredential(1211-1219)createBookingScenario(997-1028)getScenarioData(1600-1688)mockCalendarToHaveNoBusySlots(1989-2002)mockCalendarToCrashOnGetAvailability(2004-2006)createOrganization(1040-1079)packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)
packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts (1)
packages/features/bookings/lib/client/decoyBookingStore.ts (2)
DecoyBookingData(86-86)getDecoyBooking(40-71)
packages/features/bookings/lib/handleNewBooking.ts (1)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)
packages/features/bookings/lib/client/decoyBookingStore.ts (1)
packages/lib/webstorage.ts (1)
localStorage(6-36)
packages/features/bookings/Booker/components/hooks/useBookings.ts (2)
packages/features/bookings/lib/client/decoyBookingStore.ts (1)
storeDecoyBooking(26-33)packages/ui/components/toast/showToast.tsx (1)
showToast(77-101)
⏰ 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: Tests / Unit
- GitHub Check: Linters / lint
- GitHub Check: Type check / check-types
🔇 Additional comments (5)
packages/features/di/watchlist/containers/watchlist.ts (1)
41-57: LGTM! DI wiring correctly implements repository-based service construction.The refactoring from service tokens to repository-based construction aligns with the facade pattern mentioned in the PR summary. Each getter correctly:
- Retrieves the appropriate repository from the container
- Constructs the service with the expected dependency structure
- Returns a properly typed instance
The dependency keys match the service constructors' expectations (globalRepo, orgRepo, auditRepository).
packages/features/bookings/lib/handleNewBooking.ts (2)
509-513: LGTM! Spam check correctly initiated with organizational context.The spam check is started early in the booking flow (before expensive operations) and correctly determines the organizational context from the event type's team hierarchy. The organizationId derivation (
team?.parentId ?? null) appropriately reflects either the team's parent organization or null for non-organizational events.
1441-1447: Consider privacy implications: spammer's email stored in decoy attendees.The decoy booking includes the blocked user's email in the attendees array (line 1443), which flows through to localStorage via
storeDecoyBooking. While this provides a realistic decoy experience, it means potentially malicious emails are persisted client-side for up to 24 hours.Please confirm the design intent:
- Is storing the blocked email client-side acceptable for the decoy strategy?
- Should the email be masked (e.g.,
b***@domain.com) or omitted entirely in the decoy response?- Does client-side storage of blocked emails create any compliance concerns (e.g., logging PII of users who trigger spam detection)?
The current implementation prioritizes a realistic decoy (showing the booker's own email back to them), but consider:
- Spammers could inspect localStorage and discover their email was stored without a real booking
- If the email is already blocked server-side, the client doesn't need it for the decoy UI
Based on learnings: The broader PII-in-localStorage concern has been flagged multiple times in this PR. Evaluate whether the attendee email is necessary in the decoy booking data stored client-side.
packages/features/bookings/Booker/components/hooks/useBookings.ts (1)
287-295: PII minimization confirmed:booking.attendees[0]andbooking.userinclude onlyname, andtimeZone, so no additional PII is stored.packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (1)
405-788: LGTM! Comprehensive organization watchlist test coverage.The organization watchlist tests are well-structured and cover critical scenarios:
- Email and domain blocking within org context
- Cross-organization isolation (ensuring one org's watchlist doesn't block another org's bookings)
- Managed event types (child events inherit parent's org context)
The tests properly verify both the decoy response shape and the absence of database records for blocked bookings.
| BookingLocations, | ||
| createOrganization, | ||
| } from "@calcom/web/test/utils/bookingScenario/bookingScenario"; | ||
| import { prisma } from "@calcom/prisma" |
There was a problem hiding this comment.
Add missing semicolon.
Add semicolon after the import statement for consistency.
Apply this diff:
-import { prisma } from "@calcom/prisma"
+import { prisma } from "@calcom/prisma";📝 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.
| import { prisma } from "@calcom/prisma" | |
| import { prisma } from "@calcom/prisma"; |
🤖 Prompt for AI Agents
In
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts
around line 13, the import statement "import { prisma } from \"@calcom/prisma\""
is missing a trailing semicolon; add a semicolon at the end of that import line
to match project formatting and consistency.
| action: "BLOCK", | ||
| }); | ||
|
|
||
| console.log("watchlists", await prisma.watchlist.findMany()); |
There was a problem hiding this comment.
Remove debug console.log statement.
This appears to be a debugging artifact that should be removed before merging.
Apply this diff:
- console.log("watchlists", await prisma.watchlist.findMany());
-📝 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.
| console.log("watchlists", await prisma.watchlist.findMany()); |
🤖 Prompt for AI Agents
In
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts
around line 124, remove the debug console.log("watchlists", await
prisma.watchlist.findMany()); statement; delete the line entirely (or replace
with an appropriate test assertion or use the test logger if you need
visibility) so no debug console output remains in the committed test.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (7)
packages/features/bookings/Booker/components/hooks/useBookings.ts (1)
281-300: Minimize PII in localStorage for decoy bookings; store only needed fieldsDon’t persist full
attendees[0]anduserobjects. Store minimal fields (title, times, host/attendee names/emails, location) to reduce PII exposure. Also type the payload.Apply this diff:
- const bookingData = { - uid: booking.uid, - title: booking.title ?? null, - startTime: booking.startTime, - endTime: booking.endTime, - booker: booking.attendees?.[0] ?? null, - host: booking.user ?? null, - location: booking.location ?? null, - }; - - storeDecoyBooking(bookingData); + const bookingData: { + uid: string; + title: string | null; + startTime: string | Date; + endTime: string | Date; + hostName: string | null; + hostEmail: string | null; + attendeeName: string | null; + attendeeEmail: string | null; + location: string | null; + } = { + uid: booking.uid, + title: booking.title ?? null, + startTime: booking.startTime, + endTime: booking.endTime, + hostName: booking.user?.name ?? null, + hostEmail: booking.user?.email ?? null, + attendeeName: booking.attendees?.[0]?.name ?? null, + attendeeEmail: booking.attendees?.[0]?.email ?? null, + location: booking.location ?? null, + }; + + storeDecoyBooking(bookingData as unknown as Record<string, unknown> & { uid: string }); router.push(`/booking-successful/${booking.uid}`);Additionally, ensure decoy entry is removed after rendering on the success page/hook. Based on learnings
packages/features/bookings/components/BookingSuccessCard.tsx (1)
37-43: Accessibility: remove aria-hidden from dialog wrapper; add a separate overlay
aria-hidden="true"on the wrapper hides the dialog subtree from screen readers. Keep overlay as separate element and removearia-hiddenfrom the wrapper.Apply:
- <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0" aria-hidden="true"> + {/* Overlay */} + <div className="fixed inset-0 bg-black/50" aria-hidden="true" /> + <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0"> <div className="bg-default dark:bg-muted border-booker border-booker-width inline-block transform overflow-hidden rounded-lg px-8 pb-4 pt-5 text-left align-bottom transition-all sm:my-8 sm:w-full sm:max-w-xl sm:py-8 sm:align-middle" role="dialog" aria-modal="true" aria-labelledby="modal-headline">apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx (1)
31-34: Misleading comment; attendee email is displayedComment claims email isn’t stored, but you render
booking.booker?.email. Fix the comment or setattendeeEmailto null if intentionally not showing.- const hostEmail = null; // Email not stored for spam decoy bookings + const hostEmail = null; // Host email intentionally omitted from decoy displaypackages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx (1)
37-43: Accessibility: remove aria-hidden from dialog wrapper; add an overlaySame issue as the main card:
aria-hiddenon the wrapper hides the dialog.Apply:
- <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0" aria-hidden="true"> + {/* Overlay */} + <div className="fixed inset-0 bg-black/50" aria-hidden="true" /> + <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0"> <div className="bg-default dark:bg-muted border-booker border-booker-width inline-block transform overflow-hidden rounded-lg px-8 pb-4 pt-5 text-left align-bottom transition-all sm:my-8 sm:w-full sm:max-w-xl sm:py-8 sm:align-middle" role="dialog" aria-modal="true" aria-labelledby="modal-headline">packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (2)
13-13: Add missing semicolon on import.Update the prisma import to include a semicolon for consistency.
-import { prisma } from "@calcom/prisma" +import { prisma } from "@calcom/prisma";
124-124: Remove debug console.log from tests.Leftover debug output should be removed.
- console.log("watchlists", await prisma.watchlist.findMany());packages/features/watchlist/lib/service/SpamCheckService.ts (1)
29-36: Enforce fail-open with timeout; don’t throw when not started.waitForCheck can hang and currently throws if startCheck wasn’t called. Race with a short timeout and default to not-blocked; return fail‑open when no promise.
- async waitForCheck(): Promise<BlockingResult> { - if (!this.spamCheckPromise) { - throw new Error( - "waitForCheck() called before startCheck(). You must call startCheck() first to initialize spam checking." - ); - } - return await this.spamCheckPromise; - } + async waitForCheck(timeoutMs = 300): Promise<BlockingResult> { + if (!this.spamCheckPromise) return { isBlocked: false }; + const timeout = new Promise<BlockingResult>((resolve) => + setTimeout(() => resolve({ isBlocked: false }), timeoutMs) + ); + return Promise.race([this.spamCheckPromise, timeout]); + }
🧹 Nitpick comments (4)
packages/features/di/watchlist/modules/Watchlist.module.ts (1)
14-20: Bindings look correct; minor style nitBindings to blocking services with named deps align with service constructors. Consider spacing for consistency.
-watchlistModule.bind(WATCHLIST_DI_TOKENS.ORGANIZATION_BLOCKING_SERVICE).toClass(OrganizationBlockingService,{ +watchlistModule + .bind(WATCHLIST_DI_TOKENS.ORGANIZATION_BLOCKING_SERVICE) + .toClass(OrganizationBlockingService, { orgRepo: DI_TOKENS.ORGANIZATION_WATCHLIST_REPOSITORY, });packages/features/bookings/lib/handleNewBooking.ts (1)
1414-1453: Decoy short‑circuit before side effectsYou return before DB writes, emails, webhooks. This prevents side effects for blocked bookings. Small suggestions:
- Consider localizing
DECOY_ORGANIZER_NAMESor moving to a shared constant.- Ensure the returned shape stays stable (uid, status, attendees) for client handling.
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
48-53: Safer orgId check.Use explicit null/undefined check to avoid edge cases with falsy numeric IDs.
- if (organizationId) { + if (organizationId != null) { const orgResult = await this.organizationBlockingService.isBlocked(email, organizationId); if (orgResult.isBlocked) { return orgResult; } }packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (1)
167-169: Mask or omit attendee email in decoy booking storage and tests
storeDecoyBooking persists full attendee.email to localStorage—which embeds PII client-side; refactor to mask or remove the email and update tests to assert on attendee shape or masked value instead.
📜 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.
📒 Files selected for processing (13)
apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx(1 hunks)apps/web/test/utils/bookingScenario/bookingScenario.ts(1 hunks)packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx(1 hunks)packages/features/bookings/Booker/components/hooks/useBookings.ts(7 hunks)packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts(1 hunks)packages/features/bookings/components/BookingSuccessCard.tsx(1 hunks)packages/features/bookings/lib/client/decoyBookingStore.ts(1 hunks)packages/features/bookings/lib/handleNewBooking.ts(4 hunks)packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts(1 hunks)packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts(1 hunks)packages/features/di/watchlist/containers/SpamCheckService.container.ts(1 hunks)packages/features/di/watchlist/modules/Watchlist.module.ts(1 hunks)packages/features/watchlist/lib/service/SpamCheckService.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- packages/features/bookings/lib/client/decoyBookingStore.ts
- apps/web/test/utils/bookingScenario/bookingScenario.ts
- packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
- packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts
- packages/features/di/watchlist/containers/SpamCheckService.container.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.tspackages/features/di/watchlist/modules/Watchlist.module.tspackages/features/bookings/lib/handleNewBooking.tspackages/features/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/Booker/components/hooks/useBookings.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/spam-booking.integration-test.tspackages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxpackages/features/di/watchlist/modules/Watchlist.module.tspackages/features/bookings/components/BookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsxpackages/features/bookings/lib/handleNewBooking.tspackages/features/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/Booker/components/hooks/useBookings.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/spam-booking.integration-test.tspackages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxpackages/features/di/watchlist/modules/Watchlist.module.tspackages/features/bookings/components/BookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsxpackages/features/bookings/lib/handleNewBooking.tspackages/features/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/Booker/components/hooks/useBookings.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Always use
t()for text localization in frontend code; direct text embedding should trigger a warning
Files:
packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxpackages/features/bookings/components/BookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx
**/*Service.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Service files must include
Servicesuffix, use PascalCase matching exported class, and avoid generic names (e.g.,MembershipService.ts)
Files:
packages/features/watchlist/lib/service/SpamCheckService.ts
🧠 Learnings (2)
📚 Learning: 2025-08-08T09:29:11.681Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts:118-143
Timestamp: 2025-08-08T09:29:11.681Z
Learning: In calcom/cal.com PR #22919, packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts (TypeScript), the AIPhoneServiceAgentListItem is required to include user.email in listAgents responses (per maintainer Udit-takkar). Future reviews should not flag this as unnecessary PII unless requirements change.
Applied to files:
apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx
📚 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 (6)
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (3)
packages/features/bookings/lib/handleNewBooking/test/getNewBookingHandler.ts (1)
getNewBookingHandler(15-17)apps/web/test/utils/bookingScenario/bookingScenario.ts (9)
getBooker(2250-2264)getOrganizer(1539-1598)TestData(1258-1530)getGoogleCalendarCredential(1211-1219)createBookingScenario(997-1028)getScenarioData(1600-1688)mockCalendarToHaveNoBusySlots(1989-2002)mockCalendarToCrashOnGetAvailability(2004-2006)createOrganization(1040-1079)packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)
packages/features/di/watchlist/modules/Watchlist.module.ts (4)
packages/features/di/watchlist/Watchlist.tokens.ts (1)
WATCHLIST_DI_TOKENS(1-12)packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/di/tokens.ts (1)
DI_TOKENS(5-62)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)
apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx (2)
packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts (1)
useDecoyBooking(14-30)packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx (1)
DecoyBookingSuccessCard(18-114)
packages/features/bookings/lib/handleNewBooking.ts (1)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)
packages/features/watchlist/lib/service/SpamCheckService.ts (3)
packages/features/watchlist/lib/interface/IBlockingService.ts (1)
BlockingResult(3-7)packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)
packages/features/bookings/Booker/components/hooks/useBookings.ts (1)
packages/features/bookings/lib/client/decoyBookingStore.ts (1)
storeDecoyBooking(26-33)
🔇 Additional comments (4)
packages/features/bookings/Booker/components/hooks/useBookings.ts (2)
396-399: LGTM: safer error scrollingGuarding
scrollIntoViewbehind a ref check is good.
431-435: LGTM: consistent error UX for instant bookingScroll into view on error is appropriate.
packages/features/bookings/lib/handleNewBooking.ts (1)
509-514: Good: start spam check early and run in parallelStarting the check before heavy work reduces latency; passing orgId when available is right.
Please confirm SpamCheckService is fail-open on errors and timeouts so bookings aren’t blocked by infra issues.
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
14-27: Overall structure looks good.Naming, DI shape, and fail‑open catch in startCheck align with the design.
Ensure the DI container constructs a fresh SpamCheckService per request (no singleton). Based on container snippet, getSpamCheckService returns a new instance each call; confirm no global caching elsewhere.
| return ( | ||
| <DecoyBookingSuccessCard | ||
| title={booking.title || "Booking"} | ||
| formattedDate={formattedDate} | ||
| formattedTime={formattedTime} | ||
| endTime={formattedEndTime} | ||
| formattedTimeZone={formattedTimeZone} | ||
| hostName={hostName} | ||
| hostEmail={hostEmail} | ||
| attendeeName={attendeeName} | ||
| attendeeEmail={attendeeEmail} | ||
| location={booking.location || null} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
Clear PII after render and localize fallback title
- Remove decoy entry after reading to minimize PII retention.
- Localize the fallback "Booking" string.
+import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { removeDecoyBooking } from "@calcom/features/bookings/lib/client/decoyBookingStore";
@@
export default function BookingSuccessful() {
const params = useParams();
+ const { t } = useLocale();
@@
const bookingData = useDecoyBooking(uid);
@@
- <DecoyBookingSuccessCard
- title={booking.title || "Booking"}
+ {(() => {
+ // Clear decoy data after first render
+ removeDecoyBooking(uid);
+ return (
+ <DecoyBookingSuccessCard
+ title={booking.title || t("booking")}
formattedDate={formattedDate}
formattedTime={formattedTime}
endTime={formattedEndTime}
formattedTimeZone={formattedTimeZone}
hostName={hostName}
hostEmail={hostEmail}
attendeeName={attendeeName}
attendeeEmail={attendeeEmail}
location={booking.location || null}
- />
+ />
+ );
+ })()}Run to verify TTL and removal behavior in the decoy store:
🏁 Script executed:
#!/bin/bash
# Inspect decoyBookingStore for TTL enforcement and removal
fd -t f 'decoyBookingStore.ts' | xargs -I{} sh -c 'echo "=== {} ==="; sed -n "1,200p" "{}"'
rg -n 'getDecoyBooking|removeDecoyBooking|timestamp|expiration|24' -C3Length of output: 284658
🏁 Script executed:
#!/bin/bash
sed -n '1,240p' apps/web/app/\(booking-page-wrapper\)/booking-successful/\[uid\]/page.tsx || trueLength of output: 1759
Clear PII after render and localize fallback title
- Replace the literal fallback "Booking" with localized text via useLocale (const { t } = useLocale(); title={booking.title || t("booking")}).
- Delete stored decoy data after reading by calling removeDecoyBooking(uid) in a useEffect once bookingData is present (avoid performing removal during render).
- File: apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx — update imports and add the useEffect around the existing render logic. Decoy store already enforces a 24h TTL and exposes removeDecoyBooking (verified).
🤖 Prompt for AI Agents
In apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx around
lines 36 to 49, the component uses a hardcoded fallback title and performs no
cleanup of stored decoy PII; update imports to include useLocale and useEffect
(and removeDecoyBooking from your decoy store), call const { t } = useLocale()
and replace title={booking.title || "Booking"} with title={booking.title ||
t("booking")}, and add a useEffect that, when bookingData (or booking) is
present, calls removeDecoyBooking(uid) once to delete stored decoy data (do not
call removal during render).
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts
Show resolved
Hide resolved
753737b to
db1b5f4
Compare
db1b5f4 to
c773994
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (4)
packages/features/bookings/components/BookingSuccessCard.tsx (1)
37-42: Accessibility blocker: aria-hidden hides dialog from screen readersThe container at line 37 has
aria-hidden="true", which hides all its children (including the dialog withrole="dialog"andaria-modal="true") from assistive technology.Apply this fix:
- <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0" aria-hidden="true"> + <div className="fixed inset-0 bg-black/50" aria-hidden="true" /> + <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0">packages/features/bookings/lib/handleNewBooking/test/spam-booking.test.ts (1)
376-401: Mocking pattern won’t affect handler’s instance; stub container or prototype instead.You spy on a fresh SpamCheckService instance, but handler() resolves its own via getSpamCheckService(); this spy won’t intercept it. Risk: false positives. Replace with module mock at top-level or prototype spy.
Apply this diff to remove the ineffective instance spy in this test body:
- // Mock the SpamCheckService to throw an error during isBlocked check - const { getSpamCheckService } = await import( - "@calcom/features/di/watchlist/containers/SpamCheckService.container" - ); - const spamCheckService = getSpamCheckService(); - const originalIsBlocked = spamCheckService["isBlocked"].bind(spamCheckService); - - // Use vi.spyOn to mock the private method - const isBlockedSpy = vi.spyOn(spamCheckService as never, "isBlocked"); - isBlockedSpy.mockRejectedValue(new Error("Database connection failed")); - - try { + { const createdBooking = await handleNewBooking({ bookingData: mockBookingData, }); // Should NOT be a decoy response - booking should succeed due to fail-open behavior expect(createdBooking).not.toHaveProperty("isShortCircuitedBooking"); expect(createdBooking.status).toBe(BookingStatus.ACCEPTED); expect(createdBooking.id).not.toBe(0); expect(createdBooking.attendees[0].email).toBe(bookerEmail); - } finally { - // Restore original implementation and clean up spy - isBlockedSpy.mockRestore(); - spamCheckService["isBlocked"] = originalIsBlocked; }Add one of the following at the top of the file (outside tests) so it intercepts handler’s construction:
Option A — mock the container (preferred)
vi.mock("@calcom/features/di/watchlist/containers/SpamCheckService.container", () => { const mockService = { startCheck: vi.fn(), waitForCheck: vi.fn(async () => ({ isBlocked: false })), // Force fail-open path by rejecting isBlocked isBlocked: vi.fn().mockRejectedValue(new Error("Database connection failed")), }; return { getSpamCheckService: () => mockService }; });Option B — spy on the class prototype
import { SpamCheckService } from "@calcom/features/watchlist/lib/service/SpamCheckService"; const protoSpy = vi .spyOn(SpamCheckService.prototype as unknown as { isBlocked: (email: string, orgId?: number) => Promise<unknown> }, "isBlocked") .mockRejectedValue(new Error("Database connection failed")); // ... run test and finally: protoSpy.mockRestore();packages/features/bookings/lib/client/decoyBookingStore.ts (1)
59-69: Reduce TTL and consume-on-read to minimize PII exposure.24h retention in localStorage is high risk; also clear data after a successful read.
- // Check if the data is too old (24 hours) + // Check if the data is too old (30 minutes) const dataAge = Date.now() - data.timestamp; - const maxAge = 24 * 60 * 60 * 1000; // 24 hours in milliseconds + const maxAge = 30 * 60 * 1000; // 30 minutes in milliseconds @@ - if (dataAge > maxAge) { + if (dataAge > maxAge) { // Remove the data from localStorage if expired localStorage.removeItem(storageKey); return null; } - return data; + // One-time consume on successful read + localStorage.removeItem(storageKey); + return data;Optional: Prefer sessionStorage for even shorter persistence if UX allows.
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
29-36: Enforce fail-open: add timeout and avoid throwing when not started.Without a timeout, waitForCheck can hang and delay legit bookings; throwing if not started is brittle.
- async waitForCheck(): Promise<BlockingResult> { - if (!this.spamCheckPromise) { - throw new Error( - "waitForCheck() called before startCheck(). You must call startCheck() first to initialize spam checking." - ); - } - return await this.spamCheckPromise; - } + async waitForCheck(timeoutMs = 300): Promise<BlockingResult> { + if (!this.spamCheckPromise) return { isBlocked: false }; + const timeout = new Promise<BlockingResult>((resolve) => + setTimeout(() => resolve({ isBlocked: false }), timeoutMs) + ); + return await Promise.race([this.spamCheckPromise, timeout]); + }Note: getSpamCheckService returns a new instance per call, so no cross-request leakage. Based on learnings.
🧹 Nitpick comments (5)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
7-11: Remove redundant type casts.Lines 8-9 cast to
GlobalBlockingServiceandOrganizationBlockingService, but the getter functions already return these exact types (seewatchlist.tslines 41 and 48).Apply this diff:
export const getSpamCheckService = (): SpamCheckService => { - const globalBlockingService = getGlobalBlockingService() as GlobalBlockingService; - const organizationBlockingService = getOrganizationBlockingService() as OrganizationBlockingService; + const globalBlockingService = getGlobalBlockingService(); + const organizationBlockingService = getOrganizationBlockingService(); return new SpamCheckService(globalBlockingService, organizationBlockingService); };apps/web/app/(booking-page-wrapper)/booking-successful/[localStorageUid]/page.tsx (1)
29-70: Consider localizing fallback and cleaning up PII.Two recommended improvements:
- Localize the fallback title: Replace
"Booking"at line 58 witht("booking")usinguseLocale().- Remove decoy data after reading: Add a
useEffectto callremoveDecoyBooking(localStorageUid)once data is loaded, reducing PII retention.The store already enforces 24h TTL, but explicit cleanup is a privacy best practice.
+"use client"; + +import { useParams, useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; + +import dayjs from "@calcom/dayjs"; +import { DecoyBookingSuccessCard } from "@calcom/features/bookings/components/DecoyBookingSuccessCard"; +import { getDecoyBooking, removeDecoyBooking } from "@calcom/features/bookings/lib/client/decoyBookingStore"; +import type { DecoyBookingData } from "@calcom/features/bookings/lib/client/decoyBookingStore"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; + +function useDecoyBooking(localStorageUid: string) { + const router = useRouter(); + const [bookingData, setBookingData] = useState<DecoyBookingData | null>(null); + + useEffect(() => { + const data = getDecoyBooking(localStorageUid); + + if (!data) { + router.push("/404"); + return; + } + + setBookingData(data); + // Clean up PII after reading + removeDecoyBooking(localStorageUid); + }, [localStorageUid, router]); + + return bookingData; +} + +export default function BookingSuccessful() { + const params = useParams(); + const { t } = useLocale(); + + const localStorageUid = params?.localStorageUid as string; + const bookingData = useDecoyBooking(localStorageUid); + + if (!bookingData) { + return null; + } + + const { booking } = bookingData; + + // ... rest of the component + + return ( + <DecoyBookingSuccessCard + title={booking.title || t("booking")} + // ... rest of props + /> + ); +}packages/features/bookings/lib/handleNewBooking/test/spam-booking.test.ts (2)
126-127: Remove debug logging from tests.Drop console.log to keep CI output clean and avoid leaking data.
- console.log("watchlists", await prismaMock.watchlist.findMany());
165-172: Assert no side effects for decoy bookings (no DB writes, no external calls).Beyond findMany=0, assert booking.create wasn’t called to guarantee no writes; add similar checks for references, workflows, webhooks if applicable.
// after creating the booking and asserting decoy: expect(prismaMock.booking.create).not.toHaveBeenCalled(); // Optional if these exist in prismaMock: expect(prismaMock.bookingReference?.create).not.toHaveBeenCalled(); expect(prismaMock.workflowRun?.create).not.toHaveBeenCalled(); // And any email/webhook sender mocks if available.packages/features/bookings/lib/client/decoyBookingStore.ts (1)
28-37: Store a sanitized shape; avoid persisting raw names/emails.Write a minimal, redacted payload to storage instead of the full booking object.
-export function storeDecoyBooking(booking: Record<string, unknown>): string { +export function storeDecoyBooking(booking: Record<string, unknown>): string { const localStorageUid = uuidv4(); - const bookingSuccessData = { - booking, + // Build a minimal, sanitized snapshot for display only + const safeBooker = (booking as any)?.booker ?? null; + const redactedEmail = + safeBooker && typeof safeBooker.email === "string" + ? safeBooker.email.replace(/(^.).*(@.*$)/, "$1***$2") + : undefined; + const sanitized = { + title: (booking as any)?.title ?? null, + startTime: (booking as any)?.startTime, + endTime: (booking as any)?.endTime, + // Drop name; keep redacted email if present + booker: safeBooker ? { timeZone: safeBooker.timeZone, email: redactedEmail } : null, + host: (booking as any)?.host ? { name: (booking as any).host.name, timeZone: (booking as any).host.timeZone } : null, + location: (booking as any)?.location ?? null, + }; + const bookingSuccessData: DecoyBookingData = { + booking: sanitized, timestamp: Date.now(), }; const storageKey = getStorageKey(localStorageUid); localStorage.setItem(storageKey, JSON.stringify(bookingSuccessData)); return localStorageUid; }If you prefer, we can make email optional in DecoyBookingData and omit it entirely.
📜 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.
📒 Files selected for processing (12)
apps/web/app/(booking-page-wrapper)/booking-successful/[localStorageUid]/page.tsx(1 hunks)apps/web/test/utils/bookingScenario/bookingScenario.ts(2 hunks)packages/features/bookings/Booker/components/hooks/useBookings.ts(7 hunks)packages/features/bookings/components/BookingSuccessCard.tsx(1 hunks)packages/features/bookings/components/DecoyBookingSuccessCard.tsx(1 hunks)packages/features/bookings/lib/client/decoyBookingStore.ts(1 hunks)packages/features/bookings/lib/handleNewBooking.ts(3 hunks)packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts(2 hunks)packages/features/bookings/lib/handleNewBooking/test/spam-booking.test.ts(1 hunks)packages/features/di/watchlist/containers/SpamCheckService.container.ts(1 hunks)packages/features/di/watchlist/containers/watchlist.ts(2 hunks)packages/features/watchlist/lib/service/SpamCheckService.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/test/utils/bookingScenario/bookingScenario.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/features/di/watchlist/containers/SpamCheckService.container.tspackages/features/di/watchlist/containers/watchlist.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.test.tspackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.tspackages/features/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/Booker/components/hooks/useBookings.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/di/watchlist/containers/SpamCheckService.container.tsapps/web/app/(booking-page-wrapper)/booking-successful/[localStorageUid]/page.tsxpackages/features/di/watchlist/containers/watchlist.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.test.tspackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.tspackages/features/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/components/DecoyBookingSuccessCard.tsxpackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/bookings/components/BookingSuccessCard.tsxpackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/Booker/components/hooks/useBookings.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/di/watchlist/containers/SpamCheckService.container.tsapps/web/app/(booking-page-wrapper)/booking-successful/[localStorageUid]/page.tsxpackages/features/di/watchlist/containers/watchlist.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.test.tspackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.tspackages/features/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/components/DecoyBookingSuccessCard.tsxpackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/bookings/components/BookingSuccessCard.tsxpackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/Booker/components/hooks/useBookings.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Always use
t()for text localization in frontend code; direct text embedding should trigger a warning
Files:
apps/web/app/(booking-page-wrapper)/booking-successful/[localStorageUid]/page.tsxpackages/features/bookings/components/DecoyBookingSuccessCard.tsxpackages/features/bookings/components/BookingSuccessCard.tsx
**/*Service.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Service files must include
Servicesuffix, use PascalCase matching exported class, and avoid generic names (e.g.,MembershipService.ts)
Files:
packages/features/watchlist/lib/service/SpamCheckService.ts
🧠 Learnings (6)
📚 Learning: 2025-08-08T09:29:11.681Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts:118-143
Timestamp: 2025-08-08T09:29:11.681Z
Learning: In calcom/cal.com PR #22919, packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts (TypeScript), the AIPhoneServiceAgentListItem is required to include user.email in listAgents responses (per maintainer Udit-takkar). Future reviews should not flag this as unnecessary PII unless requirements change.
Applied to files:
apps/web/app/(booking-page-wrapper)/booking-successful/[localStorageUid]/page.tsx
📚 Learning: 2025-08-21T13:44:06.805Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.805Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
📚 Learning: 2025-09-03T11:54:05.409Z
Learnt from: supalarry
PR: calcom/cal.com#23514
File: apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts:579-582
Timestamp: 2025-09-03T11:54:05.409Z
Learning: In calcom/cal.com bookings repository methods, when Prisma select uses `eventType: true`, all eventType fields including seatsShowAttendees are automatically included in the selection. Explicit field selection is not required when using `true` for nested relations.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
📚 Learning: 2025-09-03T11:54:05.409Z
Learnt from: supalarry
PR: calcom/cal.com#23514
File: apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts:579-582
Timestamp: 2025-09-03T11:54:05.409Z
Learning: In calcom/cal.com bookings repository methods, when Prisma include uses `eventType: true`, all eventType fields including seatsShowAttendees are automatically included in the selection. Explicit field selection is not required when using `true` for nested relations.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
📚 Learning: 2025-09-08T07:27:42.903Z
Learnt from: vijayraghav-io
PR: calcom/cal.com#16878
File: packages/app-store/feishucalendar/api/callback.ts:72-79
Timestamp: 2025-09-08T07:27:42.903Z
Learning: Four calendar integrations in Cal.com still use direct prisma.selectedCalendar.create instead of SelectedCalendarRepository.create: feishucalendar, zohocalendar, office365calendar, and larkcalendar. These bypass repository hooks and won't trigger reconnection logic for BookingReferences.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
📚 Learning: 2025-09-08T07:27:42.903Z
Learnt from: vijayraghav-io
PR: calcom/cal.com#16878
File: packages/app-store/feishucalendar/api/callback.ts:72-79
Timestamp: 2025-09-08T07:27:42.903Z
Learning: In the Cal.com codebase, some calendar integrations like google-calendar already use SelectedCalendarRepository.create for selectedCalendar creation, which automatically triggers reconnection logic, while others like feishucalendar use direct prisma.selectedCalendar.create calls that bypass the repository hooks.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
🧬 Code graph analysis (9)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (2)
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
SpamCheckService(14-57)packages/features/di/watchlist/containers/watchlist.ts (2)
getGlobalBlockingService(41-46)getOrganizationBlockingService(48-53)
apps/web/app/(booking-page-wrapper)/booking-successful/[localStorageUid]/page.tsx (2)
packages/features/bookings/lib/client/decoyBookingStore.ts (2)
DecoyBookingData(90-90)getDecoyBooking(44-75)packages/features/bookings/components/DecoyBookingSuccessCard.tsx (1)
DecoyBookingSuccessCard(18-114)
packages/features/di/watchlist/containers/watchlist.ts (6)
packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/watchlist/lib/interface/IWatchlistRepositories.ts (2)
IGlobalWatchlistRepository(9-36)IOrganizationWatchlistRepository(42-79)packages/features/di/watchlist/Watchlist.tokens.ts (1)
WATCHLIST_DI_TOKENS(1-12)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)packages/features/watchlist/lib/service/WatchlistAuditService.ts (1)
WatchlistAuditService(13-44)packages/features/watchlist/lib/interface/IAuditRepository.ts (1)
IAuditRepository(20-33)
packages/features/bookings/lib/handleNewBooking/test/spam-booking.test.ts (4)
packages/features/watchlist/lib/types.ts (1)
WatchlistType(3-3)packages/features/bookings/lib/handleNewBooking/test/getNewBookingHandler.ts (1)
getNewBookingHandler(15-17)apps/web/test/utils/bookingScenario/bookingScenario.ts (8)
getBooker(2250-2264)getOrganizer(1539-1598)TestData(1258-1530)getGoogleCalendarCredential(1211-1219)createBookingScenario(997-1028)getScenarioData(1600-1688)mockCalendarToHaveNoBusySlots(1989-2002)mockCalendarToCrashOnGetAvailability(2012-2014)packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)
packages/features/watchlist/lib/service/SpamCheckService.ts (3)
packages/features/watchlist/lib/interface/IBlockingService.ts (1)
BlockingResult(3-7)packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)
packages/features/bookings/lib/client/decoyBookingStore.ts (1)
packages/lib/webstorage.ts (1)
localStorage(6-36)
packages/features/bookings/components/BookingSuccessCard.tsx (1)
packages/ui/components/badge/index.ts (1)
Badge(1-1)
packages/features/bookings/lib/handleNewBooking.ts (1)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)
packages/features/bookings/Booker/components/hooks/useBookings.ts (2)
packages/features/bookings/lib/client/decoyBookingStore.ts (1)
storeDecoyBooking(28-37)packages/ui/components/toast/showToast.tsx (1)
showToast(77-101)
⏰ 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). (1)
- GitHub Check: Install dependencies / Yarn install & cache
🔇 Additional comments (11)
packages/features/di/watchlist/containers/watchlist.ts (1)
41-46: LGTM! Facade pattern correctly implemented.The getter now constructs a new service instance on each call rather than caching. This ensures fresh dependencies but creates a new instance per call.
If these services are called frequently, consider whether instance caching would be beneficial for performance. However, for the spam-check use case (called once per booking), fresh instantiation is acceptable.
packages/features/bookings/components/BookingSuccessCard.tsx (1)
1-114: Localization correctly implemented.All user-facing strings use the
t()function for proper internationalization.packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts (1)
5-5: LGTM! Changes support spam check organization ID derivation.The import reordering at line 5 is a cleanup, and the
parentIdaddition at line 100 correctly supports the fallback pattern inhandleNewBooking.tswhereeventOrganizationId = team?.parentId ?? null.Based on past review discussion confirming this field is consumed.
Also applies to: 100-100
packages/features/bookings/components/DecoyBookingSuccessCard.tsx (2)
37-42: Accessibility issue addressed in previous commit.Per past review comments, the
aria-hidden="true"issue on the dialog container was addressed in commits b01ceb4 to fe86a8c.
1-114: Localization correctly implemented.All user-facing strings properly use
t()for internationalization.packages/features/bookings/lib/handleNewBooking.ts (3)
509-511: LGTM! Spam check correctly initiated early for parallel execution.The spam check starts immediately after the blocker check, allowing it to run concurrently with availability checks and event processing. The
eventOrganizationIdderivation aligns with theparentIdselection added ingetEventTypesFromDB.ts.
1439-1445: Privacy consideration: attendee email included in decoy response.The decoy includes the booker's email in the attendees array (line 1441), allowing them to see their own email in the success UI. This is stored in localStorage via the decoy store.
Per PR objectives, this is intentional - the attendee sees confirmation that their email was received. The host email remains hidden (line 1435 sets
user.email: null).
1412-1451: Early return on spam-blocked path prevents downstream side effects
All side-effecting logic (createBooking, seat handling, eventManager calls, emails, webhooks, analytics) occurs after this guard and is skipped whenspamCheckResult.isBlockedis true.packages/features/bookings/Booker/components/hooks/useBookings.ts (3)
14-18: LGTM! Modular imports improve tree-shaking.The switch from aggregated imports to individual function imports is a best practice for better bundle optimization.
281-294: Verify minimal PII is stored per past review guidance.Past review comment (marked "✅ Addressed in commit aa82e15") suggested storing only minimal fields (title, times, names, emails, location) rather than full
attendees[0]anduserobjects. However, lines 282-289 still store complete objects.Please confirm whether:
- The fix was applied but not in this PR branch
- Storing full objects is now intentional
- The suggested minimal fields approach should still be implemented
If minimal PII storage is desired, apply the diff from the past review to store only necessary fields.
390-393: LGTM! Error handling improvements add safety.The changes properly guard
scrollIntoViewwith a null check (bookerFormErrorRef?.current) and remove unused error handler parameters.Also applies to: 425-429
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
29-36: Add timeout and don’t throw when not started; enforce fail‑openCurrently, waitForCheck awaits indefinitely and throws if startCheck wasn’t called. Add a small timeout (e.g., 300ms) and return non‑blocked when not started.
Apply:
- async waitForCheck(): Promise<BlockingResult> { - if (!this.spamCheckPromise) { - throw new Error( - "waitForCheck() called before startCheck(). You must call startCheck() first to initialize spam checking." - ); - } - return await this.spamCheckPromise; - } + async waitForCheck(timeoutMs = 300): Promise<BlockingResult> { + if (!this.spamCheckPromise) return { isBlocked: false }; + const timeout = new Promise<BlockingResult>((resolve) => + setTimeout(() => resolve({ isBlocked: false }), timeoutMs) + ); + return Promise.race([this.spamCheckPromise, timeout]); + }This upholds “fail‑open” without delaying legitimate bookings. Based on learnings
🧹 Nitpick comments (5)
packages/features/bookings/lib/handleNewBooking.ts (4)
1414-1458: Short‑circuit earlier to avoid wasted workYou await spam check after loading workflows and doing heavy prep (availability, event building). If blocked, all that work is discarded. Move the await + decoy return to just before any side‑effects (DB/email/webhooks) and before expensive availability/users/workflows loading to save CPU/IO and reduce attack surface from spammers. Extract a small buildDecoyBooking() helper so you can reuse it in both places.
37-37: Prefer DI over container in handlerResolve SpamCheckService via deps for testability and consistency with other services; avoid calling the container directly from the domain logic.
Minimal changes:
- import { getSpamCheckService } from "@calcom/features/di/watchlist/containers/SpamCheckService.container"; + import type { SpamCheckService } from "@calcom/features/watchlist/lib/service/SpamCheckService";Add to IBookingServiceDependencies (see separate comment), then:
- const spamCheckService = getSpamCheckService(); + const { spamCheckService } = deps; // Either it is a team event or a managed child event of a managed event const team = eventType.team ?? eventType.parent?.team ?? null; const eventOrganizationId = team?.parentId ?? null; spamCheckService.startCheck({ email: bookerEmail, organizationId: eventOrganizationId });Also applies to: 509-514
414-426: Wire SpamCheckService through depsAdd SpamCheckService to IBookingServiceDependencies so the handler doesn’t reach into the container.
Apply:
export interface IBookingServiceDependencies { cacheService: CacheService; checkBookingAndDurationLimitsService: CheckBookingAndDurationLimitsService; prismaClient: PrismaClient; bookingRepository: BookingRepository; featuresRepository: FeaturesRepository; checkBookingLimitsService: CheckBookingLimitsService; luckyUserService: LuckyUserService; hostRepository: HostRepository; oooRepository: OooRepository; userRepository: UserRepository; attributeRepository: AttributeRepository; + spamCheckService: SpamCheckService; }And import its type:
+import type { SpamCheckService } from "@calcom/features/watchlist/lib/service/SpamCheckService";
1414-1415: Fail‑open locally if spam wait throwsGuard against unexpected throws (e.g., future changes to waitForCheck). Default to non‑blocked.
- const spamCheckResult = await spamCheckService.waitForCheck(); + const spamCheckResult = await spamCheckService.waitForCheck().catch(() => ({ isBlocked: false }));packages/features/watchlist/lib/service/SpamCheckService.ts (1)
45-47: Truthy check can skip organizationId=0Guard explicitly for null/undefined to avoid accidental skips.
- if (organizationId) { + if (organizationId !== null && organizationId !== undefined) { checks.push(this.organizationBlockingService.isBlocked(email, organizationId)); }
📜 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.
📒 Files selected for processing (2)
packages/features/bookings/lib/handleNewBooking.ts(4 hunks)packages/features/watchlist/lib/service/SpamCheckService.ts(1 hunks)
🧰 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 useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/features/bookings/lib/handleNewBooking.tspackages/features/watchlist/lib/service/SpamCheckService.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.tspackages/features/watchlist/lib/service/SpamCheckService.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.tspackages/features/watchlist/lib/service/SpamCheckService.ts
**/*Service.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Service files must include
Servicesuffix, use PascalCase matching exported class, and avoid generic names (e.g.,MembershipService.ts)
Files:
packages/features/watchlist/lib/service/SpamCheckService.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 (2)
packages/features/bookings/lib/handleNewBooking.ts (2)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)packages/features/eventtypes/lib/eventNaming.ts (1)
getEventName(31-113)
packages/features/watchlist/lib/service/SpamCheckService.ts (3)
packages/features/watchlist/lib/interface/IBlockingService.ts (1)
BlockingResult(3-7)packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)
emrysal
left a comment
There was a problem hiding this comment.
Happy for this decoy flow to go in.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
29-36: Add timeout and fail‑open default to waitForCheckGuarantee fail‑open even if dependencies stall; also avoid throwing when startCheck wasn’t called.
Apply:
- async waitForCheck(): Promise<BlockingResult> { - if (!this.spamCheckPromise) { - throw new Error( - "waitForCheck() called before startCheck(). You must call startCheck() first to initialize spam checking." - ); - } - return await this.spamCheckPromise; - } + async waitForCheck(timeoutMs = 300): Promise<BlockingResult> { + if (!this.spamCheckPromise) return { isBlocked: false }; + const timeout = new Promise<BlockingResult>((resolve) => + setTimeout(() => resolve({ isBlocked: false }), timeoutMs) + ); + return Promise.race([this.spamCheckPromise, timeout]); + }
🧹 Nitpick comments (2)
packages/features/bookings/lib/handleNewBooking.ts (1)
509-514: Inject SpamCheckService via deps instead of importing containerImporting the container here couples domain logic to wiring and hinders testability. Prefer adding spamCheckService to IBookingServiceDependencies and pass it in from the module/container.
Based on learnings
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
22-27: Tighten log message; keep semantics the sameMessage better reflects runtime errors during the check.
- logger.error("Error starting spam check", safeStringify(error)); + logger.error("Spam check error", safeStringify(error));
📜 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.
📒 Files selected for processing (2)
packages/features/bookings/lib/handleNewBooking.ts(4 hunks)packages/features/watchlist/lib/service/SpamCheckService.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*Service.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Service files must include
Servicesuffix, use PascalCase matching exported class, and avoid generic names (e.g.,MembershipService.ts)
Files:
packages/features/watchlist/lib/service/SpamCheckService.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/features/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/lib/handleNewBooking.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/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/lib/handleNewBooking.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/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/lib/handleNewBooking.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 (2)
packages/features/watchlist/lib/service/SpamCheckService.ts (3)
packages/features/watchlist/lib/interface/IBlockingService.ts (1)
BlockingResult(3-7)packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)
packages/features/bookings/lib/handleNewBooking.ts (2)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)packages/features/eventtypes/lib/eventNaming.ts (1)
getEventName(31-113)
🔇 Additional comments (1)
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
42-62: Parallel checks with global precedence look goodRunning global and org checks in parallel and preferring global results is sound.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (8)
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (2)
13-13: Add missing semicolonThe import statement is missing a trailing semicolon for consistency with project formatting.
Apply this diff:
-import { prisma } from "@calcom/prisma" +import { prisma } from "@calcom/prisma";
124-124: Remove debug console.log statementThis debug statement should be removed before merging to production.
Apply this diff:
- console.log("watchlists", await prisma.watchlist.findMany()); -packages/features/bookings/components/BookingSuccessCard.tsx (1)
37-42: Accessibility blocker: aria-hidden hides the dialog subtree
aria-hidden="true"on the container wrapping the dialog hides all its children from screen readers, including the dialog itself. This prevents screen reader users from accessing the booking confirmation content.Apply this diff to fix the accessibility issue:
- <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0" aria-hidden="true"> + {/* Overlay for background dimming */} + <div className="fixed inset-0 bg-black/50" aria-hidden="true" /> + <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0">Based on coding guidelines: Always use
t()for text localization in frontend code.packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx (1)
37-42: Accessibility blocker: aria-hidden still present despite past fix claimThe past review comment indicated this issue was addressed in commits b01ceb4 to fe86a8c, but
aria-hidden="true"is still present on the container wrapping the dialog. This hides all children from screen readers, including the dialog itself.Apply this diff:
- <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0" aria-hidden="true"> + {/* Overlay for background dimming */} + <div className="fixed inset-0 bg-black/50" aria-hidden="true" /> + <div className="main inset-0 my-4 flex flex-col transition-opacity sm:my-0">apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx (1)
38-38: Localize the fallback titleThe fallback string "Booking" should be localized using
t()for consistency with the rest of the codebase and to support internationalization.Apply this diff:
+import { useLocale } from "@calcom/lib/hooks/useLocale"; + export default function BookingSuccessful() { const params = useParams(); + const { t } = useLocale(); const uid = params?.uid as string; const bookingData = useDecoyBooking(uid); // ... rest of the code ... return ( <DecoyBookingSuccessCard - title={booking.title || "Booking"} + title={booking.title || t("booking")}As per coding guidelines: Always use
t()for text localization in frontend code.packages/features/bookings/Booker/components/hooks/useBookings.ts (1)
281-300: Minimize PII before persisting decoy booking to localStorageYou’re storing entire attendee and user objects. Persist only the minimal fields the decoy UI needs; sanitize the shape and type it to prevent accidental PII leakage.
Apply:
@@ - const bookingData = { - uid: booking.uid, - title: booking.title ?? null, - startTime: booking.startTime, - endTime: booking.endTime, - booker: booking.attendees?.[0] ?? null, - host: booking.user ?? null, - location: booking.location ?? null, - }; - - storeDecoyBooking(bookingData); + const bookingData: DecoyBookingData["booking"] = { + uid: booking.uid, + title: booking.title ?? null, + startTime: booking.startTime, + endTime: booking.endTime, + booker: booking.attendees?.[0] + ? { + name: booking.attendees?.[0]?.name ?? "", + email: booking.attendees?.[0]?.email ?? "", + timeZone: booking.attendees?.[0]?.timeZone, + } + : null, + host: booking.user + ? { + name: booking.user?.name ?? "", + timeZone: booking.user?.timeZone, + } + : null, + location: booking.location ?? null, + }; + + storeDecoyBooking(bookingData);Also add the type import near other imports:
+import type { DecoyBookingData } from "@calcom/features/bookings/lib/client/decoyBookingStore";packages/features/bookings/lib/client/decoyBookingStore.ts (1)
55-66: Consume-on-read to further reduce PII exposure; confirm TTL aligns with product requirement
- Remove the entry after a successful read to minimize persistence window.
- TTL is 5 minutes; PR summary mentions 24 hours. Please confirm the intended TTL and adjust if needed.
- // Check if the data is too old (5 min) + // Check if the data is too old (5 min) const dataAge = Date.now() - data.timestamp; const maxAge = 5 * 60 * 1000; // 5 minutes in milliseconds if (dataAge > maxAge) { // Remove the data from localStorage if expired localStorage.removeItem(storageKey); return null; } - return data; + // Consume-on-read: remove immediately to reduce PII exposure + localStorage.removeItem(storageKey); + return data;If 24h is required, consider exporting a DEC0Y_BOOKING_TTL_MS constant and setting it centrally.
packages/features/watchlist/lib/service/SpamCheckService.ts (1)
29-36: Enforce fail‑open with a short timeout in waitForCheckAvoid stalling booking creation if deps hang. Race the promise with a small timeout.
- async waitForCheck(): Promise<BlockingResult> { + async waitForCheck(timeoutMs = 300): Promise<BlockingResult> { if (!this.spamCheckPromise) { throw new Error( "waitForCheck() called before startCheck(). You must call startCheck() first to initialize spam checking." ); } - return await this.spamCheckPromise; + const timeout = new Promise<BlockingResult>((resolve) => + setTimeout(() => resolve({ isBlocked: false }), timeoutMs) + ); + return Promise.race([this.spamCheckPromise, timeout]); }
🧹 Nitpick comments (2)
packages/features/di/watchlist/modules/Watchlist.module.ts (1)
18-20: Minor formatting inconsistencyMissing space before the opening brace in the
toClasscall. Add a space for consistency with the binding above (line 14).Apply this diff:
-watchlistModule.bind(WATCHLIST_DI_TOKENS.ORGANIZATION_BLOCKING_SERVICE).toClass(OrganizationBlockingService,{ +watchlistModule.bind(WATCHLIST_DI_TOKENS.ORGANIZATION_BLOCKING_SERVICE).toClass(OrganizationBlockingService, {packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx (1)
1-115: Consider consolidating duplicate success card components
DecoyBookingSuccessCard.tsxandBookingSuccessCard.tsxare nearly identical (same props interface, same rendering logic). Consider extracting a shared base component to reduce duplication and maintenance burden.Example approach:
// BookingSuccessCardBase.tsx export function BookingSuccessCardBase({ title, formattedDate, ... }: BookingSuccessCardProps) { // shared rendering logic } // DecoyBookingSuccessCard.tsx export function DecoyBookingSuccessCard(props: DecoyBookingSuccessCardProps) { return <BookingSuccessCardBase {...props} />; } // BookingSuccessCard.tsx export function BookingSuccessCard(props: BookingSuccessCardProps) { return <BookingSuccessCardBase {...props} />; }
📜 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.
📒 Files selected for processing (13)
apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx(1 hunks)apps/web/test/utils/bookingScenario/bookingScenario.ts(1 hunks)packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx(1 hunks)packages/features/bookings/Booker/components/hooks/useBookings.ts(7 hunks)packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts(1 hunks)packages/features/bookings/components/BookingSuccessCard.tsx(1 hunks)packages/features/bookings/lib/client/decoyBookingStore.ts(1 hunks)packages/features/bookings/lib/handleNewBooking.ts(4 hunks)packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts(1 hunks)packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts(1 hunks)packages/features/di/watchlist/containers/SpamCheckService.container.ts(1 hunks)packages/features/di/watchlist/modules/Watchlist.module.ts(1 hunks)packages/features/watchlist/lib/service/SpamCheckService.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/features/di/watchlist/containers/SpamCheckService.container.ts
- apps/web/test/utils/bookingScenario/bookingScenario.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*Service.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Service files must include
Servicesuffix, use PascalCase matching exported class, and avoid generic names (e.g.,MembershipService.ts)
Files:
packages/features/watchlist/lib/service/SpamCheckService.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/features/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/Booker/components/hooks/useBookings.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.tspackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/Booker/components/hooks/useDecoyBooking.tspackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/di/watchlist/modules/Watchlist.module.tspackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.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/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/Booker/components/hooks/useBookings.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.tspackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/Booker/components/hooks/useDecoyBooking.tspackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsxpackages/features/di/watchlist/modules/Watchlist.module.tspackages/features/bookings/components/BookingSuccessCard.tsxpackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.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/watchlist/lib/service/SpamCheckService.tspackages/features/bookings/Booker/components/hooks/useBookings.tspackages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.tspackages/features/bookings/lib/handleNewBooking.tspackages/features/bookings/Booker/components/hooks/useDecoyBooking.tspackages/features/bookings/lib/client/decoyBookingStore.tspackages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsxpackages/features/di/watchlist/modules/Watchlist.module.tspackages/features/bookings/components/BookingSuccessCard.tsxpackages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Always use
t()for text localization in frontend code; direct text embedding should trigger a warning
Files:
packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsxapps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsxpackages/features/bookings/components/BookingSuccessCard.tsx
🧠 Learnings (5)
📚 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
📚 Learning: 2025-08-08T09:29:11.681Z
Learnt from: Udit-takkar
PR: calcom/cal.com#22919
File: packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts:118-143
Timestamp: 2025-08-08T09:29:11.681Z
Learning: In calcom/cal.com PR #22919, packages/features/calAIPhone/interfaces/AIPhoneService.interface.ts (TypeScript), the AIPhoneServiceAgentListItem is required to include user.email in listAgents responses (per maintainer Udit-takkar). Future reviews should not flag this as unnecessary PII unless requirements change.
Applied to files:
apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx
📚 Learning: 2025-08-21T13:44:06.805Z
Learnt from: supalarry
PR: calcom/cal.com#23217
File: apps/api/v2/src/ee/event-types/event-types_2024_06_14/services/output-event-types.service.ts:93-94
Timestamp: 2025-08-21T13:44:06.805Z
Learning: In apps/api/v2/src/ee/event-types/event-types_2024_06_14/event-types.repository.ts, repository functions that use explicit Prisma select clauses (like getEventTypeWithSeats) are used for specific purposes and don't need to include all EventType fields like bookingRequiresAuthentication. These methods don't feed into the general OutputEventTypesService_2024_06_14 flow.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
📚 Learning: 2025-09-03T11:54:05.409Z
Learnt from: supalarry
PR: calcom/cal.com#23514
File: apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts:579-582
Timestamp: 2025-09-03T11:54:05.409Z
Learning: In calcom/cal.com bookings repository methods, when Prisma select uses `eventType: true`, all eventType fields including seatsShowAttendees are automatically included in the selection. Explicit field selection is not required when using `true` for nested relations.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
📚 Learning: 2025-09-03T11:54:05.409Z
Learnt from: supalarry
PR: calcom/cal.com#23514
File: apps/api/v2/src/ee/bookings/2024-08-13/services/bookings.service.ts:579-582
Timestamp: 2025-09-03T11:54:05.409Z
Learning: In calcom/cal.com bookings repository methods, when Prisma include uses `eventType: true`, all eventType fields including seatsShowAttendees are automatically included in the selection. Explicit field selection is not required when using `true` for nested relations.
Applied to files:
packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts
🧬 Code graph analysis (8)
packages/features/watchlist/lib/service/SpamCheckService.ts (3)
packages/features/watchlist/lib/interface/IBlockingService.ts (1)
BlockingResult(3-7)packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)
packages/features/bookings/Booker/components/hooks/useBookings.ts (2)
packages/features/bookings/lib/client/decoyBookingStore.ts (1)
storeDecoyBooking(26-33)packages/ui/components/toast/showToast.tsx (1)
showToast(77-101)
packages/features/bookings/lib/handleNewBooking/test/spam-booking.integration-test.ts (2)
apps/web/test/utils/bookingScenario/bookingScenario.ts (9)
getBooker(2250-2264)getOrganizer(1539-1598)TestData(1258-1530)getGoogleCalendarCredential(1211-1219)createBookingScenario(997-1028)getScenarioData(1600-1688)mockCalendarToHaveNoBusySlots(1989-2002)mockCalendarToCrashOnGetAvailability(2004-2006)createOrganization(1040-1079)packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)
packages/features/bookings/lib/handleNewBooking.ts (2)
packages/features/di/watchlist/containers/SpamCheckService.container.ts (1)
getSpamCheckService(7-11)packages/features/eventtypes/lib/eventNaming.ts (1)
getEventName(31-113)
packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts (1)
packages/features/bookings/lib/client/decoyBookingStore.ts (2)
DecoyBookingData(86-86)getDecoyBooking(40-71)
packages/features/bookings/lib/client/decoyBookingStore.ts (1)
packages/lib/webstorage.ts (1)
localStorage(6-36)
apps/web/app/(booking-page-wrapper)/booking-successful/[uid]/page.tsx (2)
packages/features/bookings/Booker/components/hooks/useDecoyBooking.ts (1)
useDecoyBooking(14-30)packages/features/bookings/Booker/components/DecoyBookingSuccessCard.tsx (1)
DecoyBookingSuccessCard(18-114)
packages/features/di/watchlist/modules/Watchlist.module.ts (4)
packages/features/di/watchlist/Watchlist.tokens.ts (1)
WATCHLIST_DI_TOKENS(1-12)packages/features/watchlist/lib/service/GlobalBlockingService.ts (1)
GlobalBlockingService(14-49)packages/features/di/tokens.ts (1)
DI_TOKENS(5-62)packages/features/watchlist/lib/service/OrganizationBlockingService.ts (1)
OrganizationBlockingService(14-59)
🔇 Additional comments (1)
packages/features/bookings/lib/handleNewBooking.ts (1)
1414-1458: Decoy branch skips all side effects
The early return in the blocked branch occurs before any DB writes, email/webhook dispatches, calendar syncs, payment handling, or workflow scheduling.
2fb5a86
E2E results are ready! |
What does this PR do?
This PR implements an async spam check feature that runs in parallel with booking creation to detect and block spam bookings with zero performance impact for legitimate users. When spam is detected, the system generates a fake booking response and redirects spammers to a convincing decoy success page.
Requested by: @hariombalhara
Link to Devin run: https://app.devin.ai/sessions/52c340ed352645dca2bf9d218111b36d
Key Features
1. Parallel Spam Check (Zero Delay)
handleNewBookingbegins2. Decoy Success Page for Spammers
/booking-successful/[localStorageUid]3. SpamCheckService Implementation
startCheck()andwaitForCheck()async methods4. DI Container Architecture Fixes
IAuditRepository)Files Changed
New Files
apps/web/app/(booking-page-wrapper)/booking-successful/[localStorageUid]/page.tsx- Decoy success pagepackages/features/watchlist/lib/service/SpamCheckService.ts- Spam check orchestration servicespam-check-architecture.md- Architecture documentationspam-check-flow.mermaid- Flow diagramModified Files
packages/features/bookings/lib/handleNewBooking.ts- Async spam check integrationpackages/features/bookings/Booker/components/hooks/useBookings.ts- Decoy page redirect logicpackages/features/di/watchlist/containers/watchlist.ts- Facade pattern service creationpackages/features/di/watchlist/containers/SpamCheckService.container.ts- DI wiringpackages/features/di/watchlist/modules/Watchlist.module.ts- Removed incorrect service bindings1. Fake Booking Generation
When spam is detected,
handleNewBooking.tsgenerates a fake booking response:Review: Ensure no side effects occur (no emails sent, no webhooks triggered, no DB writes).
2. localStorage Data Storage
The decoy success page stores booking data in localStorage:
Review: Consider privacy implications of storing attendee email in localStorage.
3. Async Timing
The spam check runs in parallel:
Review: Verify no race conditions or timing issues.
How should this be tested?
Testing Spam Detection
spam@example.com/booking-successful/[uuid]Testing Organization Watchlist
Testing Legitimate Users
Testing Fail-Open Behavior
Mandatory Tasks
Checklist