Skip to content

Conversation

@keithwillcode
Copy link
Contributor

@keithwillcode keithwillcode commented Nov 10, 2025

What does this PR do?

Limits the date range that can be queried in the getSchedule endpoint to a maximum of 1 year from today. Previously, clients could request availability for extremely long date ranges (e.g., 100 years), causing expensive database queries, CPU-intensive slot calculations, and excessive external calendar API calls.

Changes:

  • Added date clamping in tRPC schema (packages/trpc/server/routers/viewer/slots/types.ts) to automatically limit endTime to max 1 year from today
  • Added date clamping in API v2 DTOs (packages/platform/types/slots/slots-2024-09-04/inputs/get-slots.input.ts) using plain Date (avoiding dayjs import to prevent build issues)
  • Updated API documentation to inform users about the 1-year limit
  • Added unit tests for the date clamping logic
  • Added E2E tests to verify calendar behavior with the date limit

Behavior:

  • If a client requests an endTime more than 1 year in the future, it will be silently clamped to exactly 1 year from today (UTC)
  • This applies to both the internal tRPC endpoint and the public Platform API v2
  • The clamping is transparent to prevent breaking existing clients

Updates since last revision

Latest fix (date mocking cleanup):

  • Removed redundant clear() calls from individual tests in user-event-type-slots.controller.e2e-spec.ts
  • The afterEach hook already handles date mocking cleanup, so individual tests don't need to call clear() at the end
  • This ensures consistent date mocking state across all tests and prevents potential state management issues

Previous fixes:

  • Fixed slot reservation tests that were failing with 422 errors by changing mocked "now" from 2050-09-05T12:00:00.000Z to 2050-09-05T08:00:00.000Z so that slot start time (10:00) is 2 hours in the future
  • Made booking UIDs unique in slots E2E tests by appending randomString() to prevent unique constraint violations
  • Added deleteByEventTypeId method to SelectedSlotRepositoryFixture for bulk cleanup
  • Added afterEach cleanup hooks in test files to delete slot reservations after each test
  • Added Luxon Settings.now mocking in addition to jest-date-mock's advanceTo() - required because the slots repository uses DateTime.utc() from Luxon
  • Date mocking added to 5 API v2 slots e2e test files to fix test failures caused by the 1-year date range limit

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox. N/A - API documentation is auto-generated from NestJS decorators which were updated.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Automated Tests:

  • Unit tests: yarn test packages/trpc/server/routers/viewer/slots/types.test.ts
  • E2E tests: yarn e2e apps/web/playwright/slots-date-limit.e2e.ts (requires ready-for-e2e label)
  • API v2 slots tests: Run the 5 modified e2e spec files in apps/api/v2/src/modules/slots/slots-2024-09-04/controllers/e2e/

Manual Testing:

  1. Test tRPC endpoint (internal):

    // Request slots with end date > 1 year in future
    const result = await trpc.viewer.slots.getSchedule.query({
      eventTypeId: 1,
      startTime: "2025-01-01T00:00:00Z",
      endTime: "2030-01-01T00:00:00Z", // 5 years in future
      timeZone: "UTC"
    });
    // Verify that slots are only returned up to ~1 year from today
  2. Test API v2 endpoint (public):

    curl -X GET "https://api.cal.com/v2/slots?eventTypeId=100&start=2025-01-01&end=2030-01-01" \
      -H "cal-api-version: 2024-09-04"
    # Verify response only includes slots up to ~1 year from today

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

Important Review Points

⚠️ Please verify:

  1. Date mocking cleanup pattern: The latest fix removes clear() calls from individual tests, relying on the afterEach hook for cleanup. Verify this doesn't cause state leakage between tests.

  2. Slot reservation date mocking: The mocked "now" (08:00) is BEFORE slotStartTime (10:00). Verify this pattern is consistent across all reservation tests.

  3. Unique booking UIDs: Tests use randomString() for booking UIDs. Verify this doesn't break any assertions that depend on specific UID values.

  4. Luxon Settings.now mocking: The fix mocks both jest-date-mock (for new Date()) and Luxon's Settings.now (for DateTime.utc()). Verify this properly affects the slot reservation expiry check in slots.repository.ts.

  5. afterEach cleanup pattern: The deleteByEventTypeId cleanup runs after each test. Verify this doesn't cause issues if the eventTypeId is undefined (e.g., if beforeAll fails).

  6. Silent clamping vs error response: This PR implements silent clamping (modifies the request without error). Is this the desired behavior, or should we return a 400 error when the date range exceeds 1 year?


Devin Session: https://app.devin.ai/sessions/b777d0818d2c4aaeb1bc8ab02b218c52
Requested by: [email protected] (@keithwillcode)

- Add date clamping in tRPC schema to limit endTime to max 1 year from today
- Add date clamping in API v2 DTOs using Transform decorator
- Update API documentation to mention the 1-year limit
- Prevents DoS attacks from requesting extremely long date ranges

Co-Authored-By: [email protected] <[email protected]>
@devin-ai-integration
Copy link
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' or '@devin'.
  • 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

@keithwillcode keithwillcode added core area: core, team members only foundation labels Nov 10, 2025
@keithwillcode keithwillcode changed the title fix: Limit getSchedule endpoint date range to 1 year to prevent DoS fix: Limit getSchedule endpoint date range to 1 year Nov 10, 2025
@vercel
Copy link

vercel bot commented Nov 10, 2025

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

Project Deployment Review Updated (UTC)
cal-com-v2 Building Building Preview, Comment Jan 7, 2026 3:08pm
cal-com-v2-1767797589266-uOAD Canceled Canceled Jan 7, 2026 3:08pm
cal-com-v2-5dan Canceled Canceled Jan 7, 2026 3:08pm
3 Skipped Deployments
Project Deployment Review Updated (UTC)
cal Ignored Ignored Jan 7, 2026 3:08pm
cal-companion Ignored Ignored Preview Jan 7, 2026 3:08pm
cal-eu Ignored Ignored Jan 7, 2026 3:08pm

- Add unit tests for tRPC schema date clamping logic
- Add E2E tests for slots API date range validation
- Tests verify that endTime is clamped to max 1 year from today

Co-Authored-By: [email protected] <[email protected]>
@github-actions
Copy link
Contributor

github-actions bot commented Nov 10, 2025

E2E results are ready!

@github-actions
Copy link
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

@github-actions github-actions bot added the Stale label Nov 25, 2025
@CarinaWolli CarinaWolli modified the milestones: v6.0, v6.1 Dec 16, 2025
- Wait for incrementMonth button to be ready before clicking
- Use getByTestId().click() pattern for auto-waiting
- Replace fixed 200ms timeout with proper waitFor() on enabled days
- Follow same pattern as selectFirstAvailableTimeSlotNextMonth helper

Co-Authored-By: [email protected] <[email protected]>
…te range limit

The tests were using 2049-09-05 as the mocked 'now' date while trying to
reserve slots for 2050-09-05, which is more than 1 year in the future.
Updated the mocked date to 2050-09-05 so the slot dates are within the
1-year limit.

Co-Authored-By: [email protected] <[email protected]>
…te range limit

The tests were using 2049-09-05 as the mocked 'now' date while trying to
reserve slots for 2050-09-05, which is more than 1 year in the future.
Updated the mocked date to 2050-09-05 so the slot dates are within the
1-year limit.

Co-Authored-By: [email protected] <[email protected]>
jest-date-mock only mocks new Date(), not Luxon's DateTime.utc().
The slots repository uses DateTime.utc() to check slot reservation expiry,
causing slot reservations from previous tests to persist and block
subsequent tests with 422 errors.

This fix adds Settings.now mocking in addition to advanceTo() to ensure
both new Date() and DateTime.utc() return the mocked date.

Co-Authored-By: [email protected] <[email protected]>
- Add deleteByEventTypeId method to SelectedSlotRepositoryFixture
- Add afterEach cleanup to user-event-type-slots, team-event-type-slots, and org-team-event-type-slots tests
- This ensures slot reservations are cleaned up even if a test fails mid-execution
- Prevents test interference where stale slot reservations block subsequent tests with 422 errors

Co-Authored-By: [email protected] <[email protected]>
The tests intentionally share state (reservedSlot variable) across tests,
so the afterEach cleanup was deleting slot reservations before subsequent
tests could verify them. The Luxon Settings.now mocking is still in place
to fix the date-related issues.

Co-Authored-By: [email protected] <[email protected]>
The slot reservation tests were failing with 422 errors because they were
trying to reserve slots in the past. The mocked 'now' time was set to 12:00
while the slotStartTime was 10:00, meaning the tests were attempting to
reserve slots 2 hours in the past.

Fixed by changing the mocked 'now' time to 08:00 so that the slot start
time (10:00) is 2 hours in the future, allowing the reservation to succeed.

Co-Authored-By: [email protected] <[email protected]>
@keithwillcode keithwillcode self-assigned this Jan 10, 2026
keithwillcode and others added 2 commits January 10, 2026 18:04
…E tests

The afterEach hook already handles date mocking cleanup, so individual tests
don't need to call clear() at the end. This ensures consistent date mocking
state across all tests.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Changed all test dates from 2050-09-XX to 2026-09-XX so they are within
the 1-year date range limit. Removed the global date mocking workarounds
that were added to make 2050 dates work with the 1-year limit.

Individual test date mocking for slot reservation tests is preserved
since those tests need to mock 'now' to be before the slot start time.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@vercel vercel bot temporarily deployed to Preview – cal-companion January 13, 2026 08:10 Inactive
@vercel vercel bot temporarily deployed to Preview – dev January 13, 2026 08:10 Inactive
@CarinaWolli CarinaWolli modified the milestones: v6.1, v6.2 Jan 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants