-
Notifications
You must be signed in to change notification settings - Fork 10.9k
feat: implement guest invite confirmation #24684
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@husniabad is attempting to deploy a commit to the cal Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
7 issues found across 14 files
Prompt for AI agents (all 7 issues)
Understand the root cause of the following 7 issues and fix them.
<file name="apps/web/pages/guest-verification/error.tsx">
<violation number="1" location="apps/web/pages/guest-verification/error.tsx:10">
Please wrap these user-facing strings with the t() translation helper instead of hardcoding English text so the new guest verification error view remains localized.</violation>
</file>
<file name="packages/prisma/migrations/20251025001217_implement_guest_confirmation_flow/migration.sql">
<violation number="1" location="packages/prisma/migrations/20251025001217_implement_guest_confirmation_flow/migration.sql:24">
The non-unique index on token is redundant because the preceding unique index already provides the same B-tree structure; this wastes storage and slows writes without improving reads.</violation>
</file>
<file name="apps/web/pages/guest-verification/success.tsx">
<violation number="1" location="apps/web/pages/guest-verification/success.tsx:8">
Please wrap the success-page copy in the localization helper instead of hardcoding strings so translations can be applied consistently across the UI.</violation>
</file>
<file name="packages/features/bookings/lib/handlePendingGuests.ts">
<violation number="1" location="packages/features/bookings/lib/handlePendingGuests.ts:24">
Recreating a pending guest unconditionally will throw once a pending record already exists because PendingGuest has a unique (email, bookingId) constraint. Handle the existing record (e.g., update or upsert it) instead of always calling create.</violation>
</file>
<file name="apps/web/pages/api/guest-verification/confirm.ts">
<violation number="1" location="apps/web/pages/api/guest-verification/confirm.ts:35">
Switch this Prisma lookup to use `select` instead of `include`; we only need the booking metadata (status/userId/bookingId), but the current include pulls every attendee record unnecessarily, increasing query cost and widening data exposure.</violation>
</file>
<file name="packages/prisma/schema.prisma">
<violation number="1" location="packages/prisma/schema.prisma:2754">
Rule violated: **Prevent Direct NOW() Usage in Database Queries**
Replace now() with a timezone-aware default to comply with the Prevent Direct NOW() Usage in Database Queries rule.</violation>
</file>
<file name="packages/features/bookings/lib/service/RegularBookingService.ts">
<violation number="1" location="packages/features/bookings/lib/service/RegularBookingService.ts:2607">
Rule violated: **Avoid Logging Sensitive Information**
Please avoid logging guest email addresses; logging the guestsToVerify array exposes PII and violates the Avoid Logging Sensitive Information policy.</violation>
</file>
React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.
| const getErrorMessage = () => { | ||
| switch (reason) { | ||
| case "expired": | ||
| return "The verification link has expired. Please contact the meeting organizer."; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please wrap these user-facing strings with the t() translation helper instead of hardcoding English text so the new guest verification error view remains localized.
Prompt for AI agents
Address the following comment on apps/web/pages/guest-verification/error.tsx at line 10:
<comment>Please wrap these user-facing strings with the t() translation helper instead of hardcoding English text so the new guest verification error view remains localized.</comment>
<file context>
@@ -0,0 +1,34 @@
+ const getErrorMessage = () => {
+ switch (reason) {
+ case "expired":
+ return "The verification link has expired. Please contact the meeting organizer.";
+ case "invalid":
+ return "The verification link is invalid. Please check your email for the correct link.";
</file context>
| CREATE INDEX "PendingGuest_bookingId_idx" ON "public"."PendingGuest"("bookingId"); | ||
|
|
||
| -- CreateIndex | ||
| CREATE INDEX "PendingGuest_token_idx" ON "public"."PendingGuest"("token"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The non-unique index on token is redundant because the preceding unique index already provides the same B-tree structure; this wastes storage and slows writes without improving reads.
Prompt for AI agents
Address the following comment on packages/prisma/migrations/20251025001217_implement_guest_confirmation_flow/migration.sql at line 24:
<comment>The non-unique index on token is redundant because the preceding unique index already provides the same B-tree structure; this wastes storage and slows writes without improving reads.</comment>
<file context>
@@ -0,0 +1,33 @@
+CREATE INDEX "PendingGuest_bookingId_idx" ON "public"."PendingGuest"("bookingId");
+
+-- CreateIndex
+CREATE INDEX "PendingGuest_token_idx" ON "public"."PendingGuest"("token");
+
+-- CreateIndex
</file context>
✅ Addressed in c22181a
| <svg className="mx-auto mb-4 h-16 w-16 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | ||
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||
| </svg> | ||
| <h1 className="mb-2 text-2xl font-bold text-gray-900">Email Verified!</h1> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please wrap the success-page copy in the localization helper instead of hardcoding strings so translations can be applied consistently across the UI.
Prompt for AI agents
Address the following comment on apps/web/pages/guest-verification/success.tsx at line 8:
<comment>Please wrap the success-page copy in the localization helper instead of hardcoding strings so translations can be applied consistently across the UI.</comment>
<file context>
@@ -0,0 +1,15 @@
+ <svg className="mx-auto mb-4 h-16 w-16 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
+ </svg>
+ <h1 className="mb-2 text-2xl font-bold text-gray-900">Email Verified!</h1>
+ <p className="text-gray-600">
+ You&apos;ve been successfully added to the meeting. Check your email for the meeting details.
</file context>
✅ Addressed in c22181a
| const token = generateToken(); | ||
| const expiresAt = dayjs().add(48, "hours").toDate(); | ||
|
|
||
| await prisma.pendingGuest.create({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recreating a pending guest unconditionally will throw once a pending record already exists because PendingGuest has a unique (email, bookingId) constraint. Handle the existing record (e.g., update or upsert it) instead of always calling create.
Prompt for AI agents
Address the following comment on packages/features/bookings/lib/handlePendingGuests.ts at line 24:
<comment>Recreating a pending guest unconditionally will throw once a pending record already exists because PendingGuest has a unique (email, bookingId) constraint. Handle the existing record (e.g., update or upsert it) instead of always calling create.</comment>
<file context>
@@ -0,0 +1,51 @@
+ const token = generateToken();
+ const expiresAt = dayjs().add(48, "hours").toDate();
+
+ await prisma.pendingGuest.create({
+ data: {
+ email: guestEmail,
</file context>
✅ Addressed in c22181a
| expiresAt: { gte: new Date() }, | ||
| }, | ||
| include: { | ||
| booking: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switch this Prisma lookup to use select instead of include; we only need the booking metadata (status/userId/bookingId), but the current include pulls every attendee record unnecessarily, increasing query cost and widening data exposure.
Prompt for AI agents
Address the following comment on apps/web/pages/api/guest-verification/confirm.ts at line 35:
<comment>Switch this Prisma lookup to use `select` instead of `include`; we only need the booking metadata (status/userId/bookingId), but the current include pulls every attendee record unnecessarily, increasing query cost and widening data exposure.</comment>
<file context>
@@ -0,0 +1,163 @@
+ expiresAt: { gte: new Date() },
+ },
+ include: {
+ booking: {
+ include: {
+ attendees: true,
</file context>
✅ Addressed in c22181a
| token String @unique | ||
| verified Boolean @default(false) | ||
| expiresAt DateTime | ||
| createdAt DateTime @default(now()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rule violated: Prevent Direct NOW() Usage in Database Queries
Replace now() with a timezone-aware default to comply with the Prevent Direct NOW() Usage in Database Queries rule.
Prompt for AI agents
Address the following comment on packages/prisma/schema.prisma at line 2754:
<comment>Replace now() with a timezone-aware default to comply with the Prevent Direct NOW() Usage in Database Queries rule.</comment>
<file context>
@@ -2741,3 +2742,20 @@ model CalendarCacheEvent {
+ token String @unique
+ verified Boolean @default(false)
+ expiresAt DateTime
+ createdAt DateTime @default(now())
+
+ @@unique([email, bookingId])
</file context>
| createdAt DateTime @default(now()) | |
| createdAt DateTime @default(dbgenerated("now() AT TIME ZONE 'UTC'")) |
| }, | ||
| bookerUrl, | ||
| }); | ||
| log.info("Sent verification emails to pending guests", guestsToVerify); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rule violated: Avoid Logging Sensitive Information
Please avoid logging guest email addresses; logging the guestsToVerify array exposes PII and violates the Avoid Logging Sensitive Information policy.
Prompt for AI agents
Address the following comment on packages/features/bookings/lib/service/RegularBookingService.ts at line 2607:
<comment>Please avoid logging guest email addresses; logging the guestsToVerify array exposes PII and violates the Avoid Logging Sensitive Information policy.</comment>
<file context>
@@ -2588,6 +2590,25 @@ async function handler(
+ },
+ bookerUrl,
+ });
+ log.info("Sent verification emails to pending guests", guestsToVerify);
+ } catch (error) {
+ log.error("Error sending verification emails to pending guests", error);
</file context>
| log.info("Sent verification emails to pending guests", guestsToVerify); | |
| log.info("Sent verification emails to pending guests", { count: guestsToVerify.length }); |
✅ Addressed in c22181a
|
@husniabad This issue is assigned to somebody. Have you got confirmation from them before working on it? |
What does this PR do?
This PR implements a complete guest email verification flow for the impersonation prevention feature introduced in PR #24298. Previously, when a user with
requiresBookerEmailVerificationenabled was added as a guest to a booking, they were silently filtered out with no notification. This PR changes that behavior by:PendingGuestdatabase model to track guests awaiting verificationrequiresBookerEmailVerification=trueis silently filtered out when added to bookingVisual Demo (For contributors especially)
A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).
Video Demo (if applicable):
Image Demo (if applicable):
Create booking and add guest:

Created booking without the guest:

Receive guest invite request:

Confirm the invitation:

Receive guest attending email notification:


Booking updated:

Link expired or invalid token:

Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Happy Path - Email Verification:
Error Cases:
Cleanup Job:
curl http://localhost:3000/api/cron/cleanup-pending-guestsChecklist
Summary by cubic
Implements a full guest email verification flow to prevent impersonation. Guests who require verification now confirm via a secure link before being added to a booking, and all parties are notified. Addresses CAL-6586.
New Features
Migration