Skip to content

Add guest management functionality to existing bookings#2

Open
CodingKylo wants to merge 1 commit into
guest-management-basefrom
guest-management-enhanced
Open

Add guest management functionality to existing bookings#2
CodingKylo wants to merge 1 commit into
guest-management-basefrom
guest-management-enhanced

Conversation

@CodingKylo

Copy link
Copy Markdown

Martian Code Review Benchmark PR (mirrored from source #10)

* feat: ability to add guests via app.cal.com/bookings

* fix: some update

* fix: minor issue

* fix: final update

* update

* update

* add requested changes

* fix type error

* small update

* final update

* fix type error

* fix location

* update calender event

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>

@re-entry-ai re-entry-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ re-entry.ai Code Review

@CodingKylo — Please review the following assessment:

🚨 Risk Score: 94/100 · CRITICAL

Dimension Level
Likelihood 🔴 Critical
Impact 🔴 Critical
Detectability 🟠 High

Intent

Add an end-to-end “additional guests” feature for existing bookings, including a UI dialog, a new TRPC mutation, and organizer/attendee email notifications.

Summary

Behaviorally, this PR introduces a new authenticated TRPC mutation to add booking attendees, wires it into a new UI dialog for entering multiple guest emails, and sends new “add guests” emails with generated ICS content. The top risks are (1) a likely authorization bug in the server-side permission gate (using && where || is expected), and (2) email/guest matching logic that is case-sensitive and can cause incorrect recipient selection and duplicate/blacklist bypass behavior. You must verify the permission logic is correct and that email normalization (case/whitespace) is consistent across UI validation, backend uniqueness/blacklist checks, and email recipient selection. Also confirm the email payload/template assumptions are safe for all booking states (null/empty attendee arrays).

🎯 Review Focus

The server-side authorization and email comparison logic in packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts—specifically the isTeamAdminOrOwner boolean gate and all email equality/uniqueness checks must be correct and consistently normalized.

Key Findings

  • 🚨 [packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts:L42] CRITICAL: const isTeamAdminOrOwner = (await isTeamAdmin(user.id, booking.eventType?.teamId ?? 0)) && (await isTeamOwner(user.id, booking.eventType?.teamId ?? 0)); — This is almost certainly the wrong boolean operator for an “admin OR owner” permission gate. With &&, a user must be both team admin and team owner to pass, which will incorrectly deny legitimate access (and could also lead to inconsistent behavior if other parts of the system assume “admin OR owner”). Fix: change to || and also avoid defaulting teamId to 0 (see next finding). Example: const teamId = booking.eventType?.teamId; const isTeamAdminOrOwner = teamId ? (await isTeamAdmin(user.id, teamId)) || (await isTeamOwner(user.id, teamId)) : false;
  • 🚨 [packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts:L42] CRITICAL: booking.eventType?.teamId ?? 0 — Defaulting teamId to 0 can cause permission checks to run against an unintended team context (or a “team 0” edge case), potentially granting/denying access incorrectly. Fix: do not coerce missing teamId to a sentinel; instead short-circuit: const teamId = booking.eventType?.teamId; const isTeamAdminOrOwner = teamId ? ((await isTeamAdmin(user.id, teamId)) || (await isTeamOwner(user.id, teamId))) : false;
  • ⚠️ [packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts:L70] WARNING: const isAttendee = !!booking.attendees.find((attendee) => attendee.email === user.email); — This is case-sensitive and does not normalize whitespace. If stored attendee emails are normalized differently than user.email, the user may fail the attendee check and be blocked (or, depending on other logic, could create inconsistent authorization outcomes). Fix: normalize both sides consistently: const normalize = (e: string) => e.trim().toLowerCase(); const isAttendee = booking.attendees.some(a => normalize(a.email) === normalize(user.email)); and apply the same normalization everywhere emails are compared.
  • ⚠️ [packages/emails/templates/organizer-add-guests-email.ts:L16] WARNING: `to: toAddresses.join(
  • ⚠️ [apps/web/components/dialog/AddGuestsDialog.tsx:L33] WARNING: const ZAddGuestsInputSchema = z.array(z.string().email()).refine((emails) => { const uniqueEmails = new Set(emails); return uniqueEmails.size === emails.length; }); — Uniqueness is case-sensitive and does not trim. Users can enter Test@x.com and test@x.com as “unique” and bypass dedupe/blacklist logic if the backend compares case-sensitively. Fix: normalize before uniqueness and before sending to the backend: const normalize = (e:string)=>e.trim().toLowerCase(); const ZAddGuestsInputSchema = z.array(z.string().email()).transform(emails => emails.map(normalize)).refine(emails => new Set(emails).size === emails.length); and send the normalized list to the mutation.

✅ Action Checklist

  • [ ] CRITICAL [packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts:L42] — const isTeamAdminOrOwner = (await isTeamAdmin(user.id, booking.eventType?.teamId ?? 0)) && (await isTeamOwner(user.id, booking.eventType?.teamId ?? 0)); — This is almost certainly the wrong boolean operator for an “admin OR owner” permission gate. With &&, a user must be both team admin and team owner to pass, which will incorrectly deny legitimate access (and could also lead to inconsistent behavior if other parts of the system assume “admin OR owner”). Fix: change to || and also avoid defaulting teamId to 0 (see next finding). Example: const teamId = booking.eventType?.teamId; const isTeamAdminOrOwner = teamId ? (await isTeamAdmin(user.id, teamId)) || (await isTeamOwner(user.id, teamId)) : false;
  • [ ] CRITICAL [packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts:L42] — booking.eventType?.teamId ?? 0 — Defaulting teamId to 0 can cause permission checks to run against an unintended team context (or a “team 0” edge case), potentially granting/denying access incorrectly. Fix: do not coerce missing teamId to a sentinel; instead short-circuit: const teamId = booking.eventType?.teamId; const isTeamAdminOrOwner = teamId ? ((await isTeamAdmin(user.id, teamId)) || (await isTeamOwner(user.id, teamId))) : false;
  • [ ] WARNING [packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts:L70] — const isAttendee = !!booking.attendees.find((attendee) => attendee.email === user.email); — This is case-sensitive and does not normalize whitespace. If stored attendee emails are normalized differently than user.email, the user may fail the attendee check and be blocked (or, depending on other logic, could create inconsistent authorization outcomes). Fix: normalize both sides consistently: const normalize = (e: string) => e.trim().toLowerCase(); const isAttendee = booking.attendees.some(a => normalize(a.email) === normalize(user.email)); and apply the same normalization everywhere emails are compared.
  • [ ] WARNING [packages/emails/templates/organizer-add-guests-email.ts:L16] — `to: toAddresses.join(
  • [ ] WARNING [apps/web/components/dialog/AddGuestsDialog.tsx:L33] — const ZAddGuestsInputSchema = z.array(z.string().email()).refine((emails) => { const uniqueEmails = new Set(emails); return uniqueEmails.size === emails.length; }); — Uniqueness is case-sensitive and does not trim. Users can enter Test@x.com and test@x.com as “unique” and bypass dedupe/blacklist logic if the backend compares case-sensitively. Fix: normalize before uniqueness and before sending to the backend: const normalize = (e:string)=>e.trim().toLowerCase(); const ZAddGuestsInputSchema = z.array(z.string().email()).transform(emails => emails.map(normalize)).refine(emails => new Set(emails).size === emails.length); and send the normalized list to the mutation.
  • [ ] SUGGESTION — Verify and align the permission contract end-to-end: BookingListItem.tsx adds the UI action, but the server gate must be correct. After fixing the &&|| bug in packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts, add a small UI capability check to avoid showing the dialog to users who will be denied (UX + reduces information leakage). Example: hide the menu item unless the booking viewer role indicates organizer/attendee/team-admin-or-owner.
  • [ ] SUGGESTION — Make email normalization a single source of truth across UI + backend. Implement a shared helper (e.g., normalizeEmail) and use it in: AddGuestsDialog.tsx (transform before mutate), addGuests.handler.ts (uniqueness, blacklist, attendee checks, and recipient selection), and any email template payload builders.
  • [ ] SUGGESTION — In packages/emails/email-manager.ts, ensure sendAddGuestsEmails doesn’t assume calendarEvent.attendees[0] exists. The organizer template uses this.calEvent.attendees[0].name in the subject; if attendees is empty, this will throw. Fix by guarding in the template or (better) ensuring the TRPC handler always supplies at least one attendee when calling sendAddGuestsEmails.
  • [ ] SUGGESTION — Add regression tests for the permission gate and email normalization. At minimum: (1) team admin-only can add guests, (2) team owner-only can add guests, (3) attendee-only can add guests, (4) case-insensitive dedupe/blacklist behavior. This should be unit tests around the handler logic and an integration test for the mutation.

Suggestions

  • Verify and align the permission contract end-to-end: BookingListItem.tsx adds the UI action, but the server gate must be correct. After fixing the &&|| bug in packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts, add a small UI capability check to avoid showing the dialog to users who will be denied (UX + reduces information leakage). Example: hide the menu item unless the booking viewer role indicates organizer/attendee/team-admin-or-owner.
  • Make email normalization a single source of truth across UI + backend. Implement a shared helper (e.g., normalizeEmail) and use it in: AddGuestsDialog.tsx (transform before mutate), addGuests.handler.ts (uniqueness, blacklist, attendee checks, and recipient selection), and any email template payload builders.
  • In packages/emails/email-manager.ts, ensure sendAddGuestsEmails doesn’t assume calendarEvent.attendees[0] exists. The organizer template uses this.calEvent.attendees[0].name in the subject; if attendees is empty, this will throw. Fix by guarding in the template or (better) ensuring the TRPC handler always supplies at least one attendee when calling sendAddGuestsEmails.
  • Add regression tests for the permission gate and email normalization. At minimum: (1) team admin-only can add guests, (2) team owner-only can add guests, (3) attendee-only can add guests, (4) case-insensitive dedupe/blacklist behavior. This should be unit tests around the handler logic and an integration test for the mutation.

📝 This review includes 3 inline comments (2 warnings, 1 note)


Posted by re-entry.ai · Risk governance for autonomous engineering teams

onClick: () => {
setIsOpenAddGuestsDialog(true);
},
icon: "user-plus" as const,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ WARNING

This new action opens the guest-editing dialog for any rendered booking item, but the actual authorization boundary lives in the TRPC handler. The handler only checks organizer/attendee/team-admin-or-owner, so the UI entry point itself is fine; the real risk is that this path now exposes a mutation that must be enforced server-side for every caller. In particular, the handler’s team-admin/owner check uses && instead of ||, which makes the permission gate much stricter than intended and can incorrectly deny legitimate team admins or owners.


re-entry.ai

@@ -0,0 +1,96 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 NOTE

This file now imports Icon and Tooltip from @calcom/ui and is re-exported through the package barrel, so the build concern is not about missing exports. The real issue is that the component mutates its value prop locally (value = value || []), which hides the fact that the parent is expected to own the array state and can make the component’s behavior harder to reason about. This is a correctness/maintainability smell rather than a build break.


re-entry.ai

const { isOpenDialog, setIsOpenDialog, bookingId } = props;
const utils = trpc.useUtils();
const [multiEmailValue, setMultiEmailValue] = useState<string[]>([""]);
const [isInvalidEmail, setIsInvalidEmail] = useState(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ WARNING

Starting state with `[


re-entry.ai

@re-entry-ai re-entry-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ re-entry.ai Code Review

@CodingKylo — Please review the following assessment:

🚨 Risk Score: 91/100 · CRITICAL

Dimension Level
Likelihood 🔴 Critical
Impact 🔴 Critical
Detectability 🟠 High

Summary

Code Review Summary

Risk Level: CRITICAL (Score: 91/100)
Files Changed: 0
High Risk Areas: 3

Key Concerns

  • CHANGEMAGNITUDE (high): This PR adds a new end-to-end feature across 15 files (UI dialog, booking list action, tRPC router/handler/schema, and multiple email templates), increasing integration risk.
  • CONFIGURATION (high): Guest addition semantics include filtering blacklisted emails, deduping against existing attendees, creating attendee records, and sending different email types, any of which could be logically incorrect.
  • CONFIGURATION (high): The PR touches multiple rarely-changed core areas (tRPC booking router/handler, email manager/templates, and booking UI), which increases the chance of subtle integration issues.

Posted by re-entry.ai · Risk governance for autonomous engineering teams

@re-entry-ai re-entry-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ re-entry.ai Code Review

@CodingKylo — Please review the following assessment:

🚨 Risk Score: 91/100 · CRITICAL

Dimension Level
Likelihood 🔴 Critical
Impact 🔴 Critical
Detectability 🟠 High

Intent

Add an end-to-end “add guests to booking” flow that updates attendees in the backend and notifies organizer/attendees via email.

Summary

This PR introduces a new tRPC mutation (viewer.bookings.addGuests) plus a UI dialog to collect guest emails, persist attendee records, and send organizer/attendee email notifications (including an ICS attachment). The highest risk is a correctness/security bug in the authorization gate: the handler currently requires the user to be both team admin AND team owner due to a boolean &&, which will incorrectly deny legitimate users (and the permission logic is security-sensitive). There are also multiple cross-file contract risks (router export wiring and duplicated Zod validation between UI and backend) that can cause the endpoint to be missing or behave inconsistently. Finally, the email/ICS generation path should be reviewed for correctness and operational behavior (e.g., whether failures are swallowed and whether recipient lists are correct).

🎯 Review Focus

Verify the authorization gate logic and the tRPC router/handler wiring for viewer.bookings.addGuests—specifically that team admin/owner permissions use correct OR semantics and that the procedure is actually registered under the expected name.

Key Findings

  • 🚨 [packages/trpc/server/routers/viewer/bookings/_router.tsx:L1] CRITICAL: Contract mismatch risk between router wiring and handler export — the diff analysis indicates _router.tsx expects addGuests?: typeof import("./addGuests.handler").addGuestsHandler while the handler file exports addGuestsHandler (or a differently named symbol). If the named export doesn’t match exactly, the procedure may not be registered at runtime (or may be registered under a different key), leading to 404/undefined procedure errors. Fix: verify the exact named export in addGuests.handler.ts and the exact import/type in _router.tsx, then add a type-level assertion/unit test that router exposes addGuests (e.g., a small test that calls appRouter.createCaller(...).viewer.bookings.addGuests or a compile-time check using keyof).

✅ Action Checklist

  • [ ] CRITICAL [packages/trpc/server/routers/viewer/bookings/_router.tsx:L1] — Contract mismatch risk between router wiring and handler export — the diff analysis indicates _router.tsx expects addGuests?: typeof import("./addGuests.handler").addGuestsHandler while the handler file exports addGuestsHandler (or a differently named symbol). If the named export doesn’t match exactly, the procedure may not be registered at runtime (or may be registered under a different key), leading to 404/undefined procedure errors. Fix: verify the exact named export in addGuests.handler.ts and the exact import/type in _router.tsx, then add a type-level assertion/unit test that router exposes addGuests (e.g., a small test that calls appRouter.createCaller(...).viewer.bookings.addGuests or a compile-time check using keyof).
  • [ ] SUGGESTION — [apps/web/components/dialog/AddGuestsDialog.tsx:L1] WARNING: UI/backend validation duplication can drift — the UI defines const ZAddGuestsInputSchema = z.array(z.string().email()).refine(...) and then calls addGuestsMutation.mutate({ bookingId, guests: multiEmailValue }); while the backend has its own addGuests.schema.ts. If constraints diverge (e.g., blacklist filtering, dedupe rules, max length, or error messaging), users will see inconsistent behavior. Fix: export the Zod schema from packages/trpc/server/routers/viewer/bookings/addGuests.schema.ts (or a shared @calcom/trpc/schemas) and import it into the UI so both sides validate identically.
  • [ ] SUGGESTION — [packages/ui/form/MultiEmailLazy.tsx:L4] WARNING: as unknown as typeof import("./MultiEmail").default suppresses type safety at the dynamic import boundary — if the default export shape changes, TS won’t catch it and runtime can fail. Fix: remove the double cast by typing the lazy import correctly, e.g. const MultiEmailLazy = lazy(() => import('./MultiEmail').then(m => ({ default: m.default }))); or ensure the lazy wrapper’s generic matches the actual module type.
  • [ ] SUGGESTION — [packages/ui/form/MultiEmail.tsx:L18] INFO: Prop mutation via reassignment (value = value || []) is confusing — it doesn’t mutate the caller, but it obscures data flow and can hide future bugs if value is later assumed immutable. Fix: use a local normalized variable: const normalizedValue = value ?? []; and then render from normalizedValue.
  • [ ] SUGGESTION — [packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts:L1] WARNING: Authorization model is server-enforced only; UI may present actions that will fail — the UI likely always renders the “add guests” action, but the server gate can return FORBIDDEN. Fix: either (a) add a lightweight capability query to conditionally render the action, or (b) ensure the UI handles FORBIDDEN distinctly (e.g., show “not permitted” without implying the user can fix it).

Suggestions

  • [apps/web/components/dialog/AddGuestsDialog.tsx:L1] WARNING: UI/backend validation duplication can drift — the UI defines const ZAddGuestsInputSchema = z.array(z.string().email()).refine(...) and then calls addGuestsMutation.mutate({ bookingId, guests: multiEmailValue }); while the backend has its own addGuests.schema.ts. If constraints diverge (e.g., blacklist filtering, dedupe rules, max length, or error messaging), users will see inconsistent behavior. Fix: export the Zod schema from packages/trpc/server/routers/viewer/bookings/addGuests.schema.ts (or a shared @calcom/trpc/schemas) and import it into the UI so both sides validate identically.
  • [packages/ui/form/MultiEmailLazy.tsx:L4] WARNING: as unknown as typeof import("./MultiEmail").default suppresses type safety at the dynamic import boundary — if the default export shape changes, TS won’t catch it and runtime can fail. Fix: remove the double cast by typing the lazy import correctly, e.g. const MultiEmailLazy = lazy(() => import('./MultiEmail').then(m => ({ default: m.default }))); or ensure the lazy wrapper’s generic matches the actual module type.
  • [packages/ui/form/MultiEmail.tsx:L18] INFO: Prop mutation via reassignment (value = value || []) is confusing — it doesn’t mutate the caller, but it obscures data flow and can hide future bugs if value is later assumed immutable. Fix: use a local normalized variable: const normalizedValue = value ?? []; and then render from normalizedValue.
  • [packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts:L1] WARNING: Authorization model is server-enforced only; UI may present actions that will fail — the UI likely always renders the “add guests” action, but the server gate can return FORBIDDEN. Fix: either (a) add a lightweight capability query to conditionally render the action, or (b) ensure the UI handles FORBIDDEN distinctly (e.g., show “not permitted” without implying the user can fix it).

📝 This review includes 3 inline comments (1 critical, 1 warning, 1 note)


Posted by re-entry.ai · Risk governance for autonomous engineering teams

include: {
attendees: true,
eventType: true,
destinationCalendar: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 CRITICAL

The authorization check is wrong: it uses &&, so a user must be both team admin and team owner to pass. That makes the isTeamAdminOrOwner name misleading and will incorrectly forbid legitimate admins or owners from adding guests. The root cause is a boolean logic error in the permission gate, not just a naming issue.


re-entry.ai

import dynamic from "next/dynamic";

/** These are like 40kb that not every user needs */
const MultiEmail = dynamic(() => import("./MultiEmail")) as unknown as typeof import("./MultiEmail").default;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ WARNING

The as unknown as typeof import("./MultiEmail").default cast suppresses the actual module shape at the dynamic import boundary. If the component export changes or the lazy import resolves differently, TypeScript will not catch the mismatch and runtime failures can slip through. This is a maintainability/type-safety issue rather than an immediate bug.


re-entry.ai

const inputClassName =
"dark:placeholder:text-muted focus:border-emphasis border-subtle block w-full rounded-md border-default text-sm focus:ring-black disabled:bg-emphasis disabled:hover:cursor-not-allowed dark:selection:bg-green-500 disabled:dark:text-subtle bg-default";
return (
<>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 NOTE

Reassigning the value prop inside the component (value = value || []) is confusing and makes the component rely on prop mutation semantics. It does not change the caller’s data, but it obscures the data flow and can hide bugs if the prop is later treated as immutable elsewhere. Prefer a local normalized variable instead.


re-entry.ai

@re-entry-ai re-entry-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ re-entry.ai Code Review

@CodingKylo — Please review the following assessment:

🚨 Risk Score: 92/100 · CRITICAL

Dimension Level
Likelihood 🔴 Critical
Impact 🔴 Critical
Detectability 🟠 High

Intent

Add a UI + TRPC mutation to let authorized users add additional guest emails to an existing booking and trigger corresponding email notifications.

Summary

The PR introduces a new TRPC handler (viewer.bookings.addGuests) and a web dialog to collect multiple guest emails, then sends new-guests emails with an attached/embedded iCal payload. The highest risks are (1) a likely authorization logic bug in the handler that uses && where the intent is “admin OR owner”, and (2) a runtime crash in the organizer email template due to direct indexing into attendees[0]. You must verify the server-side authorization is correct for bookingId (IDOR resistance) and that email template rendering is safe for events with empty/undefined attendee lists.

🎯 Review Focus

Verify the TRPC handler’s authorization for the specific bookingId (IDOR resistance) and correct the admin/owner permission logic (&& vs ||) while also ensuring email template rendering is safe when calEvent.attendees is empty.

✅ Action Checklist

  • [ ] SUGGESTION — In packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts, add explicit validation/guards for booking.eventType?.teamId and for booking.userId before using them in permission checks and subsequent queries; avoid ?? 0 fallbacks because they can silently change authorization behavior. Example: compute const teamId = booking.eventType?.teamId; and if absent, set isTeamAdminOrOwner = false (or throw a clear error).
  • [ ] SUGGESTION — In packages/emails/email-manager.ts, sendAddGuestsEmails currently pushes organizer emails unconditionally and then iterates team members; ensure the template inputs always include a non-empty attendees array or update the template to not assume it. Add a unit test that renders both organizer and attendee add-guests emails for events with attendees: [] and attendees: undefined.
  • [ ] SUGGESTION — In apps/web/components/dialog/AddGuestsDialog.tsx, the Zod schema is created inside the component on every render; move it outside the component or memoize it to avoid unnecessary allocations. Also consider trimming/normalizing emails (e.g., z.string().email().transform(s => s.trim().toLowerCase())) so uniqueness checks aren’t bypassed by casing/whitespace.

Suggestions

  • In packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts, add explicit validation/guards for booking.eventType?.teamId and for booking.userId before using them in permission checks and subsequent queries; avoid ?? 0 fallbacks because they can silently change authorization behavior. Example: compute const teamId = booking.eventType?.teamId; and if absent, set isTeamAdminOrOwner = false (or throw a clear error).
  • In packages/emails/email-manager.ts, sendAddGuestsEmails currently pushes organizer emails unconditionally and then iterates team members; ensure the template inputs always include a non-empty attendees array or update the template to not assume it. Add a unit test that renders both organizer and attendee add-guests emails for events with attendees: [] and attendees: undefined.
  • In apps/web/components/dialog/AddGuestsDialog.tsx, the Zod schema is created inside the component on every render; move it outside the component or memoize it to avoid unnecessary allocations. Also consider trimming/normalizing emails (e.g., z.string().email().transform(s => s.trim().toLowerCase())) so uniqueness checks aren’t bypassed by casing/whitespace.

📝 This review includes 3 inline comments (3 critical)


Posted by re-entry.ai · Risk governance for autonomous engineering teams

from: `${APP_NAME} <${this.getMailerOptions().from}>`,
to: toAddresses.join(","),
replyTo: [this.calEvent.organizer.email, ...this.calEvent.attendees.map(({ email }) => email)],
subject: `${this.t("guests_added_event_type_subject", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 CRITICAL

Quote: name: this.calEvent.attendees[0].name,

Issue: Direct indexing into attendees[0] will throw at runtime if attendees is empty (or if attendees is undefined). This is a likely crash path for edge cases (e.g., organizer-only events or data inconsistencies).

Fix: Guard and use a safe fallback:

const firstAttendee = this.calEvent.attendees?.[0];
if (!firstAttendee) {
  // choose a fallback name or omit the field
}

(see also L26)


re-entry.ai

user: {
include: {
destinationCalendar: true,
credentials: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 CRITICAL

Quote: const isTeamAdminOrOwner = (await isTeamAdmin(user.id, booking.eventType?.teamId ?? 0)) && (await isTeamOwner(user.id, booking.eventType?.teamId ?? 0));

Issue: The variable name and intent suggest “admin OR owner”, but the code uses && (AND). This can unintentionally block legitimate users (admin-only or owner-only) and/or allow incorrect flows depending on how isTeamAdmin/isTeamOwner behave for teamId fallback to 0.

Fix: Use OR and avoid the 0 fallback if teamId can be missing. For example:

const teamId = booking.eventType?.teamId;
const isTeamAdminOrOwner = teamId
  ? (await isTeamAdmin(user.id, teamId)) || (await isTeamOwner(user.id, teamId))
  : false;

re-entry.ai

},
onError: (err) => {
const message = `${err.data?.code}: ${t(err.message)}`;
showToast(message || t("unable_to_add_guests"), "error");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 CRITICAL

Quote: const addGuestsMutation = trpc.viewer.bookings.addGuests.useMutation({ ... })

Issue (security/authorization): The UI passes an arbitrary bookingId prop to a backend mutation. If the server-side viewer.bookings.addGuests procedure does not strictly authorize that the caller can modify that booking, this becomes an IDOR (insecure direct object reference) risk.

Fix: Ensure the backend procedure enforces authorization for bookingId (e.g., verify the viewer is the booking owner/has permission) and do not rely on the client-provided bookingId.

(Verification note: this file alone can’t confirm server-side authz; the risk is that the client exposes bookingId directly.)


re-entry.ai

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants