Add guest management functionality to existing bookings
calcom/cal.com#14740Golden 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's full review
As posted on the PR — the complete output from FriendlyReviewer.
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 usesisTeamAdmin && 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)guestsarray is passed tosendAddGuestsEmails, but onlyuniqueGuestswas persisted. Existing guests receive notifications as if they were newly added. - 🔴 packages/emails/email-manager.ts (L522)
BUG:sendAddGuestsEmailsdoes not checkeventTypeDisableHostEmailordisableAttendeeEmailflags. 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?.codecan beundefined, 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 withname: "". 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.createManycan 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)
isInvalidEmailstate 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)
multiEmailValuestarts 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 usesattendees[0].namewhich may be empty (guests are created withname: ""), 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)
Missingdisabledprop 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)
Fragilebooking.eventType?.teamId ?? 0can produce wrong team check whenteamIdis0(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 namedattendeeis 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.
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 (nodisabledprop for loading state, no permission check before showing the action) are legitimate UX issues. But the "fragilebooking.eventType?.teamId" and "callToAction={null}fragile" findings fall squarely into nitpick territory.