Skip to content

feat: add send & verify code flow to booker atom when email verification is turned on#23074

Merged
ibex088 merged 28 commits intomainfrom
devin/verify-hooks-platform-1755153590
Aug 21, 2025
Merged

feat: add send & verify code flow to booker atom when email verification is turned on#23074
ibex088 merged 28 commits intomainfrom
devin/verify-hooks-platform-1755153590

Conversation

@ibex088
Copy link
Copy Markdown
Contributor

@ibex088 ibex088 commented Aug 14, 2025

devin-ai-integration bot and others added 4 commits August 14, 2025 06:53
…e v2 atoms endpoints

- Extract verifyCodeUnAuthenticated, verifyCodeAuthenticated, and sendVerifyEmailCode logic from tRPC handlers into reusable platform library functions
- Create VerificationAtomsService and AtomsVerificationController following atoms pattern
- Add v2 endpoints: /atoms/verification/email/send-code, /atoms/verification/email/verify-code, /atoms/verification/email/verify-code-authenticated
- Update useVerifyEmail and useVerifyCode hooks to use new v2 endpoints instead of verified-resources endpoints
- Export verification functions from @calcom/platform-libraries following getPublicEvent pattern
- Integrate platform-specific verification hooks into BookerPlatformWrapper

Co-Authored-By: somay@cal.com <somaychauhan98@gmail.com>
- Update useVerifyEmail hook to use correct response type for v2 endpoint
- Update useVerifyCode hook to use v2 verification endpoint
- All verification functions now properly exported from platform libraries
- Type checking passes with no errors

Co-Authored-By: somay@cal.com <somaychauhan98@gmail.com>
- Change package.json dependency from pinned version to workspace version
- Add proper error handling to verification service with NestJS HTTP exceptions
- Convert generic Error objects to BadRequestException and UnauthorizedException
- Ensure verification endpoints return proper HTTP status codes instead of 500 errors

Co-Authored-By: somay@cal.com <somaychauhan98@gmail.com>
- Update OpenAPI documentation for v2 verification endpoints
- Update yarn.lock after changing platform-libraries dependency

Co-Authored-By: somay@cal.com <somaychauhan98@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Aug 14, 2025

Walkthrough

Adds an email verification flow across backend and frontend. Backend: new AtomsVerificationController and VerificationAtomsService, DTOs and outputs for send/check/verify endpoints, new exports in platform libraries, refactors that extract/send/verify TRPC handlers (sendVerifyEmailCode, verifyCodeUnAuthenticated, verifyCode, checkEmailVerificationRequired), and removal of an OAuthClientModule import. Frontend: new hooks useVerifyEmail and useVerifyCode, integration into BookerPlatformWrapper, and a changeset plus a platform-libraries version bump in apps/api/v2/package.json.

Possibly related PRs

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/verify-hooks-platform-1755153590

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@keithwillcode keithwillcode added core area: core, team members only platform Anything related to our platform plan labels Aug 14, 2025
…le functions

- Refactor sendVerifyEmailCode.handler.ts to extract core logic into sendVerifyEmailCode function
- Refactor verifyCodeUnAuthenticated.handler.ts to extract core logic into verifyCodeUnAuthenticated function
- Refactor organizations/verifyCode.handler.ts to extract core logic into verifyCodeAuthenticated function
- Update platform/libraries/index.ts to import from refactored handler files instead of standalone verification.ts
- Remove standalone verification.ts file as requested by user
- Keep original tRPC handlers as wrappers that call the extracted functions
- Maintain backward compatibility for existing tRPC endpoints

Co-Authored-By: somay@cal.com <somaychauhan98@gmail.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Aug 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Aug 21, 2025 2:28pm
cal-eu Ignored Ignored Aug 21, 2025 2:28pm

ibex088 and others added 2 commits August 14, 2025 15:21
- Add SendVerificationEmailInput and VerifyEmailCodeInput with validation decorators
- Add SendVerificationEmailOutput and VerifyEmailCodeOutput following v2 atoms patterns
- Update atoms verification controller to use custom types instead of platform types
- Auto-update openapi.json with new type definitions

Co-Authored-By: somay@cal.com <somaychauhan98@gmail.com>
- Import ZVerifyCodeInputSchema from @calcom/prisma/zod-utils
- Import VerifyCodeAuthenticatedInput from organizations handler
- Import TSendVerifyEmailCodeSchema from sendVerifyEmailCode schema
- Resolves CI build failure in API v2 tests

Co-Authored-By: somay@cal.com <somaychauhan98@gmail.com>
@ibex088 ibex088 changed the title feat: implement platform-specific verification hooks with v2 atoms endpoints feat: add send & verify code flow to booker atom when email verification is turned on Aug 19, 2025
@Post("/verification/email/send-code")
@Version(VERSION_NEUTRAL)
@HttpCode(HttpStatus.OK)
@Throttle({ limit: 3, ttl: 60000, blockDuration: 60000, name: "atoms_verification_email_send_code" })
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added this for rate limiting

@ibex088 ibex088 marked this pull request as ready for review August 19, 2025 09:14
@ibex088 ibex088 requested a review from a team August 19, 2025 09:14
@ibex088 ibex088 requested review from a team as code owners August 19, 2025 09:14
supalarry
supalarry previously approved these changes Aug 21, 2025
Copy link
Copy Markdown
Contributor

@supalarry supalarry left a comment

Choose a reason for hiding this comment

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

Great stuff Somay!

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts (1)

1-1: Replace MD5 with HMAC-SHA256 and fail closed when encryption key is missing.

MD5 is deprecated for security-sensitive derivations. Use a keyed HMAC (SHA-256) and normalize the email. Also, if CALENDSO_ENCRYPTION_KEY is absent, verification silently degrades to a weak, predictable secret — fail closed.

Apply this diff:

- import { createHash } from "crypto";
+ import { createHmac } from "crypto";
@@
-  await checkRateLimitAndThrowError({
-    rateLimitingType: "core",
-    identifier: hashEmail(email),
-  });
-
-  const secret = createHash("md5")
-    .update(email + process.env.CALENDSO_ENCRYPTION_KEY)
-    .digest("hex");
+  const normalizedEmail = email.trim().toLowerCase();
+
+  const encryptionKey = process.env.CALENDSO_ENCRYPTION_KEY;
+  if (!encryptionKey) {
+    logger.error("Missing CALENDSO_ENCRYPTION_KEY; refusing to verify email code");
+    throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
+  }
+
+  await checkRateLimitAndThrowError({
+    rateLimitingType: "core",
+    identifier: hashEmail(normalizedEmail),
+  });
+
+  const secret = createHmac("sha256", encryptionKey)
+    .update(normalizedEmail)
+    .digest("hex");

Optional hardening (not shown in diff): also bind the rate-limit to a secondary key when available (e.g., ctx.user.id or request IP) to reduce brute-force attempts per identity.

Also applies to: 49-56

🧹 Nitpick comments (5)
packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts (5)

28-36: Make verifyCode parameters required and annotate return type.

All three inputs are mandatory at runtime; model that in the type to catch mistakes at compile time and make the function easier to consume.

Apply this diff to tighten the signature:

-export const verifyCode = async ({
+export const verifyCode = async ({
   user,
   email,
   code,
-}: {
-  user?: Pick<NonNullable<TrpcSessionUser>, "role">;
-  email?: string;
-  code?: string;
-}) => {
+}: {
+  user: Pick<NonNullable<TrpcSessionUser>, "role">;
+  email: string;
+  code: string;
+}): Promise<boolean> => {

62-63: Simplify return; it’s always true at this point.

You throw on invalid codes; returning the variable adds no value.

-  return isValidToken;
+  return true;

39-42: Revise server-side E2E flag usage and lower log level

We’ve confirmed that NEXT_PUBLIC_IS_E2E is currently used in dozens of server-side modules (e.g. verifyCode.handler.ts, validateLicense.handler.ts, LicenseKeyService.ts, telemetry, CF Turnstile check, username checks, etc.), and only one place already reads a server-only process.env.IS_E2E (in twilioProvider.ts). Since NEXT_PUBLIC_* vars get baked into client bundles, it’s safer to switch all server logic to a dedicated flag (e.g. IS_E2E) and reserve NEXT_PUBLIC_IS_E2E solely for front-end feature toggles.

• Replace all process.env.NEXT_PUBLIC_IS_E2E checks in server code with process.env.IS_E2E === "1".
• In packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts, change

logger.warn(`Skipping code verification in dev/E2E environment`);

to

logger.debug(`Skipping code verification in dev/E2E environment`);

to reduce noise.
• Update your CI/E2E startup command (e.g. in playwright.config.ts) to export IS_E2E=1 alongside NEXT_PUBLIC_IS_E2E=1.

Before merging, please verify which env var your CI sets for E2E runs and ensure IS_E2E is enabled there.


65-65: Remove the default export in favor of the existing named export

I confirmed that verifyCodeHandler is only ever referenced via its named export in this repo—there are no default‐import sites to update. You can safely drop the final export default and rely solely on the named export for better tree-shaking and future refactors.

• File to update:

  • packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts, remove line 65 (export default verifyCodeHandler;)
    • No importers need adjustment—there are zero occurrences of import … from '…/verifyCode.handler' pulling in the default export.
-export default verifyCodeHandler;

58-58: Extract the TOTP step into a constant and trim incoming codes

To avoid “magic numbers” and guard against stray whitespace in user-entered codes, factor out the 900-second window into a named constant and apply .trim() to the input before validation. We’ve confirmed that the email sender (packages/features/auth/lib/verifyEmail.ts) uses the same { step: 900 } setting and identical secret derivation (createHash("md5").update(email + process.env.CALENDSO_ENCRYPTION_KEY).digest("hex")), so this change stays in sync.

• packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts @ line 58
• packages/trpc/server/routers/viewer/workflows/verifyEmailCode.handler.ts @ line 28
• packages/trpc/server/routers/viewer/auth/verifyCodeUnAuthenticated.handler.ts @ line 25

Apply this diff to each handler:

-  const isValidToken = totpRawCheck(code, secret, { step: 900 });
+  const isValidToken = totpRawCheck(code.trim(), secret, { step: EMAIL_VERIFICATION_TOTP_STEP_SECONDS });

And add near the top of the file (alongside your other imports):

// TOTP validity window in seconds; must match the sender’s totp.options.step
const EMAIL_VERIFICATION_TOTP_STEP_SECONDS = 900 as const;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7c277f6 and 22869ff.

📒 Files selected for processing (1)
  • packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/trpc/server/routers/viewer/organizations/verifyCode.handler.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/trpc/server/routers/viewer/organizations/verifyCode.handler.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/trpc/server/routers/viewer/organizations/verifyCode.handler.ts
🧬 Code graph analysis (1)
packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts (1)
packages/platform/libraries/index.ts (1)
  • verifyCode (125-125)
⏰ 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). (5)
  • GitHub Check: Production builds / Build Docs
  • GitHub Check: Production builds / Build Atoms
  • GitHub Check: Production builds / Build API v1
  • GitHub Check: Tests / Unit
  • GitHub Check: Atoms E2E Tests
🔇 Additional comments (3)
packages/trpc/server/routers/viewer/organizations/verifyCode.handler.ts (3)

6-6: Good call using PII-safe identifier.

Importing and using hashEmail is the right direction to avoid leaking raw emails in rate-limit identifiers. See my later comment about normalizing emails before hashing for consistency.


21-26: Nice delegation and separation of concerns.

Having the handler delegate to a reusable verifyCode function improves testability and cross-surface reuse.


44-47: Confirm ADMIN bypass is intended and scoped.

Bypassing verification for ADMIN is a policy choice. If you only intend to bypass for instance-level admins, verify that "ADMIN" can’t be assigned at a narrower scope. Also consider downgrading to info/debug log to avoid “warn” spam.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Aug 21, 2025

E2E results are ready!

@supalarry supalarry self-requested a review August 21, 2025 14:28
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts (3)

261-264: Make the guests check resilient

Same rationale as above; prefer asserting array type over strict emptiness.

-                guests: [],
+                guests: expect.any(Array),

276-279: Resilience: guests field

Mirror the resilient matcher here as well.

-                guests: [],
+                guests: expect.any(Array),

516-519: Consistent guests assertion across tests

Use the same asymmetric matcher for consistency and to reduce brittleness.

-                    guests: [],
+                    guests: expect.any(Array),
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 208a6d4 and c849eb2.

📒 Files selected for processing (1)
  • apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.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:

  • apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.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:

  • apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts
🧠 Learnings (1)
📚 Learning: 2025-08-21T13:44:06.784Z
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.784Z
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:

  • apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts
⏰ 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). (2)
  • GitHub Check: Install dependencies / Yarn install & cache
  • GitHub Check: Atoms E2E Tests
🔇 Additional comments (1)
apps/api/v2/src/ee/bookings/2024-08-13/controllers/e2e/seated-bookings.e2e-spec.ts (1)

36-36: LGTM: narrowed Prisma import to Team only

The import is scoped to just Team and aligns with actual usage in this spec. No issues.

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

Labels

⚛️ atoms Created by Linear-GitHub Sync booking-page area: booking page, public booking page, booker core area: core, team members only ✨ feature New feature or request platform Anything related to our platform plan ready-for-e2e

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants