Add guest management functionality to existing bookings

calcom/cal.com#14740
Golden recall: 80.0% Extra findings: 12

Golden Comments (4/5 found)

# Comment Severity Status FriendlyReviewer Detail
#1 Case sensitivity bypass in email blacklist — no .toLowerCase() on input, allowing uppercase emails to bypass the block HIGH ✗ Missed FriendlyReviewer did not flag this missing normalization. The blacklistedEmails.find() call on the input does not apply .toLowerCase(), so uppercase variants of a blacklisted address would not be caught.
#2 Logic AND instead of OR for team admin/owner check → isTeamAdmin && isTeamOwner requires both roles HIGH ✓ Found FriendlyReviewer found this bug in addGuests.handler.ts:46: "Permission check uses && instead of ||, incorrectly denying team admins who are not owners." Classified as HIGH.
#3 Original guests passed to emails instead of uniqueGuests → existing guests get notifications HIGH ✓ Found FriendlyReviewer found this bug in addGuests.handler.ts:168: "The original (unfiltered) guests array is passed to email sending, but only uniqueGuests was persisted." Classified as HIGH.
#4 uniqueGuests does not deduplicate input duplicates → createMany can insert duplicate rows MEDIUM ✓ Found FriendlyReviewer found this in addGuests.schema.ts:5: "Schema does not enforce uniqueness of the input guests array. Add a .refine() for uniqueness." Classified as MEDIUM.
#5 Array with empty string [""] → causes validation errors. Initialize with [] MEDIUM ✓ Found FriendlyReviewer found this in AddGuestsDialog.tsx:28: "multiEmailValue starts with [""] causing validation errors on empty form." Classified as MEDIUM.

Supplementary Findings (17 findings)

Additional issues found by FriendlyReviewer that are not part of the golden (reference) set.

Finding File Severity Legitimate?
getUsersCredentials(ctx.user) uses authenticated user, not booking organizer → wrong credentials used for calendar invites addGuests.handler.ts:161 HIGH ✓ Data leak / wrong credentials
Error message fragile: err.data?.code can be undefined → shows "undefined: message" AddGuestsDialog.tsx:68 HIGH ✓ Ugly error display
sendAddGuestsEmails does not check eventTypeDisableHostEmail or disableAttendeeEmail flags email-manager.ts:522 HIGH ✓ Consent bypass
Guest attendees created with name: "" → empty field in emails addGuests.handler.ts:92 MEDIUM ✓ Data quality
Zod schema duplicated locally instead of importing from TRPC AddGuestsDialog.tsx:18 MEDIUM ✓ Maintenance risk
isInvalidEmail never reset when input changes → stale error persists AddGuestsDialog.tsx:41 MEDIUM ✓ UX bug
Double type assertion unsafe in Next.js dynamic import MultiEmailLazy.tsx:4 MEDIUM ✓ Type safety
New guests receive "event scheduled" instead of "added to existing" email email-manager.ts:540 MEDIUM ✓ Wrong email template
Email subject uses attendees[0].name which may be empty organizer-add-guests-email.ts:30 MEDIUM ✓ Malformed subject
"Add guests" action shown for past bookings → semantically invalid BookingListItem.tsx:192 MEDIUM ✓ UX issue
Missing disabled prop for loading state → concurrent mutations possible BookingListItem.tsx:192 MEDIUM ✓ Race condition
No frontend permission check → unauthorized users see the action BookingListItem.tsx:192 MEDIUM ✓ Access control
Fragile booking.eventType?.teamId ?? 0 can produce wrong team check addGuests.handler.ts:57 LOW ✓ Fragile logic
value = value || [] mutates prop directly → React anti-pattern MultiEmail.tsx:35 LOW ✓ React anti-pattern
Array index as key → React state mismanagement MultiEmail.tsx:43 LOW ✓ React anti-pattern
Prop attendee misleading when it contains organizer object organizer-add-guests-email.ts:33 LOW ✓ Naming confusion
callToAction={null} fragile if prop order changes OrganizerAddGuestsEmail.tsx:7 LOW ✓ Fragile code

Honest Analysis

FriendlyReviewer found 4 of 5 golden bugs (80% recall). The miss (Golden #1 — case sensitivity in email blacklist) is a straightforward normalization oversight: blacklistedEmails.find() without .toLowerCase().

The 17 supplementary findings include several real bugs. The credentials fetching bug (addGuests.handler.ts:161 — uses the authenticated user instead of the booking organizer) is a genuine data leak: the wrong user's credentials are used to set up calendar invites. The frontend findings (no disabled prop for loading state, no permission check before showing the action) are legitimate UX issues. But the "fragile booking.eventType?.teamId" and "callToAction={null} fragile" findings fall squarely into nitpick territory.

FriendlyReviewer's full review

As posted on the PR — the complete output from FriendlyReviewer.

friendly-reviewer bot commented now

Hi there 👋

🌥️ Tech
5 high, 11 medium, 5 low

This PR introduces guest management functionality to existing bookings, allowing organizers to add attendees to already-scheduled events. The implementation covers a new addGuests mutation, email notifications for added guests, and UI components for selecting guests. While the feature direction is reasonable, the implementation introduces several bugs and quality issues that need attention before merging. The most significant issues include: (1) the email blacklist check is case-sensitive, allowing uppercase email variants to bypass the block, (2) the team admin/owner permission check uses && instead of ||, incorrectly denying team admins who are not owners, (3) credential fetching uses the authenticated user instead of the booking organizer, leading to wrong calendar credentials being used for calendar invites, (4) the original (unfiltered) guests array is passed to email sending instead of only the newly added unique guests, causing existing guests to receive duplicate notifications, and (5) missing checks for event type email disable flags in the notification system.


These might need a close look

  • 🔴 packages/trpc/server/routers/viewer/addGuests.handler.ts (L46)
    CRITICAL: Permission check uses isTeamAdmin && isTeamOwner (AND) instead of || (OR), incorrectly denying team admins who are not also owners. Only team owners can add guests, which contradicts the intended access model.
  • 🔴 packages/trpc/server/routers/viewer/addGuests.handler.ts (L161)
    CRITICAL: getUsersCredentials(ctx.user) uses the authenticated user instead of the booking organizer. This means the wrong user's calendar credentials are used to set up calendar invites for the new guests, potentially leaking or misrouting calendar events.
  • 🔴 packages/trpc/server/routers/viewer/addGuests.handler.ts (L168)
    BUG: The original (unfiltered) guests array is passed to sendAddGuestsEmails, but only uniqueGuests was persisted. Existing guests receive notifications as if they were newly added.
  • 🔴 packages/emails/email-manager.ts (L522)
    BUG: sendAddGuestsEmails does not check eventTypeDisableHostEmail or disableAttendeeEmail flags. Guests may receive notifications for bookings where the organizer has explicitly disabled email notifications.
  • 🔴 AddGuestsDialog.tsx (L68)
    BUG: Error message display is fragile: err.data?.code can be undefined, resulting in "undefined: message" being shown to the user. Use a fallback string for the error code.

Worth checking

  • 🟡 packages/trpc/server/routers/viewer/addGuests.handler.ts (L92)
    Guest attendees are created with name: "". Empty names appear in email templates and UI, creating a poor experience for the added guests. Default to the email address or a placeholder.
  • 🟡 packages/trpc/server/routers/viewer/addGuests.schema.ts (L5)
    Schema does not enforce uniqueness of the input guests array. createMany can insert duplicate guest rows if the same email appears multiple times. Add a .refine() for uniqueness.
  • 🟡 AddGuestsDialog.tsx (L18)
    Zod schema is duplicated locally in the dialog component instead of importing from the TRPC router definition. This creates a maintenance risk if the server schema changes and the local copy is not kept in sync.
  • 🟡 AddGuestsDialog.tsx (L41)
    isInvalidEmail state variable is never reset when the input changes. If a user enters an invalid email then corrects it, the invalid email error persists.
  • 🟡 AddGuestsDialog.tsx (L28)
    multiEmailValue starts with [""] causing validation errors on an empty form. Initialize with [] instead.
  • 🟡 MultiEmailLazy.tsx (L4)
    Double type assertion (as unknown as ...) in Next.js dynamic import is unsafe and can mask type errors at build time. Use a proper type assertion or refactor the import pattern.
  • 🟡 packages/emails/email-manager.ts (L540)
    New guests receive an "event scheduled" email template instead of an "added to existing booking" template. This is misleading and inconsistent with the actual action performed.
  • 🟡 packages/emails/organizer-add-guests-email.ts (L30)
    Email subject uses attendees[0].name which may be empty (guests are created with name: ""), resulting in empty or malformed subject lines.
  • 🟡 packages/features/bookings/components/BookingListItem.tsx (L192)
    "Add guests" action is shown for past bookings, which is semantically invalid — you cannot add attendees to an event that has already occurred. Filter by booking date.
  • 🟡 packages/features/bookings/components/BookingListItem.tsx (L192)
    Missing disabled prop for loading state, which allows concurrent mutations via the "Add guests" button while a request is in flight.
  • 🟡 packages/features/bookings/components/BookingListItem.tsx (L192)
    No frontend permission check before showing the "Add guests" action. Unauthorized users can see the action even if they don't have permission to execute it.

Small things (take or leave)

  • 🔵 addGuests.handler.ts (L57)
    Fragile booking.eventType?.teamId ?? 0 can produce wrong team check when teamId is 0 (a sentinel value). Use a distinct sentinel or explicit null check.
  • 🔵 MultiEmail.tsx (L35)
    value = value || [] mutates the prop directly, which is a React anti-pattern. Use a local variable or state instead.
  • 🔵 MultiEmail.tsx (L43)
    Array index used as React key, which can cause state mismanagement when items are reordered or filtered.
  • 🔵 organizer-add-guests-email.ts (L33)
    Prop named attendee is misleading when it actually contains the organizer object. Rename to clarify the intent.
  • 🔵 OrganizerAddGuestsEmail.tsx (L7)
    callToAction={null} is fragile if the order of props changes in the component signature. Use a dedicated prop or restructure to avoid positional fragility.