Skip to content

Add auto-refresh for Cal.com OAuth access tokens#57

Merged
zhyd1997 merged 7 commits intomainfrom
copilot/add-auto-refresh-token-support
Nov 6, 2025
Merged

Add auto-refresh for Cal.com OAuth access tokens#57
zhyd1997 merged 7 commits intomainfrom
copilot/add-auto-refresh-token-support

Conversation

Copy link
Contributor

Copilot AI commented Nov 5, 2025

Auto Refresh Access Token Support

Adding automatic refresh token support for Cal.com OAuth integration.

Changes:

  • Add CAL_REFRESH_TOKEN_URL constant
  • Create token refresh utilities in lib/auth/refresh-token.ts
  • Integrate auto-refresh into server actions
  • Add comprehensive documentation
  • Use default 30-minute expiry when not provided by Cal.com

Implementation:

Token Refresh Utilities (lib/auth/refresh-token.ts):

  • getValidAccessToken(userId): Returns valid token, auto-refreshing when expired or within 5min of expiry
  • refreshCalAccessToken(userId): Calls Cal.com's refresh endpoint with Zod validation
  • TokenRefreshError: Custom error class with status codes
  • Default expiry: Uses 30 minutes (1800 seconds) if Cal.com doesn't provide expires_in

Server Action Integration (app/(dashboard)/bookings/actions.ts):

  • Uses getValidAccessToken() for transparent token refresh

Constants (constants/oauth.ts):

  • Added CAL_REFRESH_TOKEN_URL constant

How It Works:

  1. Server actions call getValidAccessToken(userId)
  2. Checks if token expires within 5 minutes
  3. Auto-refreshes using Cal.com's endpoint if needed
  4. Database updated with new tokens (using 30-min default if expires_in not provided)
  5. Returns valid token transparently

Code Quality:

  • ✅ Zod validation for API responses
  • ✅ Configurable constants (TOKEN_EXPIRY_BUFFER_MS, DEFAULT_TOKEN_EXPIRY_SECONDS)
  • ✅ Comprehensive error handling
  • ✅ All tests pass
  • ✅ Security scan passed
Original prompt

This section details on the original issue you should resolve

<issue_title>Add auto refresh access_token support</issue_title>
<issue_description>https://cal.com/help/apps-and-integrations/oauth#refresh-token-request

POST https://app.cal.com/api/auth/oauth/refreshToken
```</issue_description>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Summary by CodeRabbit

  • New Features

    • Added automatic token refresh infrastructure to maintain valid sessions and prevent authentication interruptions.
  • Documentation

    • Updated documentation to describe the token refresh flow and related endpoints.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Introduces automatic token refresh infrastructure by adding a new getValidAccessToken() utility function that returns valid access tokens with automatic expiry-based refresh. The booking actions module is refactored to use this helper. Documentation and OAuth constants are updated to reflect the refresh flow.

Changes

Cohort / File(s) Change Summary
Documentation Updates
AGENTS.md, README.md
Added documentation describing the automatic token refresh infrastructure, including flow details, endpoints, and references to the new refresh-token module and getValidAccessToken utility.
OAuth Constants
constants/oauth.ts
Added public constant CAL_REFRESH_TOKEN_URL defining the refresh endpoint as ${CAL_APP_BASE_URL}/api/auth/oauth/refreshToken.
Token Refresh Module
lib/auth/refresh-token.ts
New module introducing token refresh management with exported functions refreshCalAccessToken() and getValidAccessToken(), custom error class TokenRefreshError, and response validation schema. Handles token expiry detection, refresh logic, and database updates.
Booking Actions Integration
app/(dashboard)/bookings/actions.ts
Refactored token retrieval from inline auth.api.getAccessToken() call to new getValidAccessToken() helper, enabling implicit token refresh when near expiry.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BookingAction as booking action
    participant GetValidAccessToken as getValidAccessToken()
    participant DB as Database
    participant CalComAPI as Cal.com API

    Client->>BookingAction: Request with userId
    BookingAction->>GetValidAccessToken: getValidAccessToken(userId)
    GetValidAccessToken->>DB: Fetch account token & expiry
    
    alt Token expired or expiring soon
        GetValidAccessToken->>DB: Fetch refresh token
        GetValidAccessToken->>CalComAPI: POST refreshToken endpoint
        CalComAPI-->>GetValidAccessToken: New access token + metadata
        GetValidAccessToken->>DB: Update with new token & expiry
        GetValidAccessToken-->>BookingAction: New access token
    else Token still valid
        GetValidAccessToken-->>BookingAction: Existing access token
    end
    
    BookingAction-->>Client: Response with valid token
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • lib/auth/refresh-token.ts: New module with multiple interdependent functions, error handling, database operations, and external API integration requires careful logic review, especially token expiry calculation, validation chain, and error status mapping.
  • app/(dashboard)/bookings/actions.ts: Refactoring changes behavior of token retrieval; verify that the new flow maintains backward compatibility and error handling equivalence.
  • constants/oauth.ts and documentation: Low-risk additions requiring brief verification.

Suggested labels

codex

Poem

🐰 Hops with glee, tokens so sweet,
Auto-refresh makes flows complete,
No expiry blues in sight,
Fresh access tokens, shining bright!
Cal.com integration, pure delight! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add auto-refresh for Cal.com OAuth access tokens' clearly and concisely summarizes the main objective of the pull request, which is to implement automatic token refresh functionality.
Linked Issues check ✅ Passed The pull request fully addresses issue #54 by implementing auto-refresh support for Cal.com OAuth tokens with the refresh endpoint, token validation, and integration into application flows.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue #54 objectives: new refresh token utilities, constants, integration into dashboard actions, and documentation updates are all within scope.

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel
Copy link

vercel bot commented Nov 5, 2025

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

Project Deployment Preview Comments Updated (UTC)
callytics Ready Ready Preview Comment Nov 5, 2025 7:37pm

Co-authored-by: zhyd1997 <31362988+zhyd1997@users.noreply.github.com>
Copilot AI and others added 2 commits November 5, 2025 19:16
Co-authored-by: zhyd1997 <31362988+zhyd1997@users.noreply.github.com>
Co-authored-by: zhyd1997 <31362988+zhyd1997@users.noreply.github.com>
Co-authored-by: zhyd1997 <31362988+zhyd1997@users.noreply.github.com>
Co-authored-by: zhyd1997 <31362988+zhyd1997@users.noreply.github.com>
Copilot AI changed the title [WIP] Add auto refresh access token support Add auto-refresh for Cal.com OAuth access tokens Nov 5, 2025
Copilot AI requested a review from zhyd1997 November 5, 2025 19:29
@zhyd1997
Copy link
Owner

zhyd1997 commented Nov 5, 2025

@copilot Default access_token expires_in is 30 min if not provided

Co-authored-by: zhyd1997 <31362988+zhyd1997@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 5, 2025

@copilot Default access_token expires_in is 30 min if not provided

Updated to use 30-minute default expiry when expires_in is not provided by Cal.com. Added DEFAULT_TOKEN_EXPIRY_SECONDS constant and updated the token refresh logic to always set an expiration time. Commit: b18363d

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • checkpoint.prisma.io

If you need me to access, download, or install something from one of these locations, you can either:

@zhyd1997
Copy link
Owner

zhyd1997 commented Nov 5, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 5, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
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 (1)
lib/auth/refresh-token.ts (1)

164-174: Consider race conditions for concurrent refresh attempts.

While the current implementation is functionally correct, concurrent calls to refresh the same user's token could result in race conditions where one refresh overwrites another. This is generally acceptable for token refresh scenarios, but consider adding a note or implementing optimistic locking if this becomes an issue in production.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6824ab3 and b18363d.

📒 Files selected for processing (5)
  • AGENTS.md (2 hunks)
  • README.md (1 hunks)
  • app/(dashboard)/bookings/actions.ts (2 hunks)
  • constants/oauth.ts (1 hunks)
  • lib/auth/refresh-token.ts (1 hunks)
🔇 Additional comments (10)
constants/oauth.ts (1)

20-20: LGTM!

The new constant is correctly defined and follows the established naming pattern for OAuth endpoints.

AGENTS.md (2)

26-27: LGTM!

The code map entries accurately document the new refresh token infrastructure and its integration points.

Also applies to: 32-32


65-72: LGTM!

Comprehensive documentation of the token refresh feature with clear guidance for developers integrating Cal.com API access.

app/(dashboard)/bookings/actions.ts (2)

15-15: LGTM!

The import correctly brings in the new centralized token management helper.


40-41: Clean integration with centralized token management.

The code correctly delegates token retrieval to getValidAccessToken, which handles automatic refresh transparently. Error handling is properly managed through the catch blocks in the action functions.

lib/auth/refresh-token.ts (4)

8-24: LGTM!

The constants and Zod schema are well-defined:

  • 5-minute expiry buffer provides good protection against edge cases
  • 30-minute default aligns with Cal.com's documented behavior
  • Schema correctly validates the OAuth 2.0 refresh token response format

34-44: LGTM!

Well-designed custom error class with proper Error extension and useful properties for debugging and error handling.


177-186: LGTM!

Comprehensive error handling that properly preserves custom errors and wraps unexpected exceptions with appropriate context.


240-244: No issues found with the token expiry handling.

The code correctly defaults to isExpired = false when accessTokenExpiresAt is null. This is safe and by design:

  • Initial OAuth tokens don't have accessTokenExpiresAt set (betterAuth doesn't compute it at creation time)
  • These tokens are used as-is on first call
  • Upon refresh (triggered either manually or after an API failure), accessTokenExpiresAt is set using the expires_in value from Cal.com
  • Future token checks then benefit from the 5-minute proactive refresh buffer
  • The reactive pattern is acceptable and avoids unnecessary token refreshes
README.md (1)

52-60: LGTM!

Clear and accurate documentation of the automatic token refresh feature. The explanation is user-friendly and correctly references the implementation details.

Comment on lines +110 to +118
const clientId = process.env.CAL_COM_CLIENT_ID;
const clientSecret = process.env.CAL_COM_CLIENT_SECRET;

if (!clientId || !clientSecret) {
throw new TokenRefreshError(
"Cal.com OAuth credentials not configured",
500
);
}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 5, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Environment variable name mismatch.

The code references CAL_COM_CLIENT_ID and CAL_COM_CLIENT_SECRET, but the README.md (lines 39-40) documents the environment variables as CAL_OAUTH_CLIENT_ID and CAL_OAUTH_CLIENT_SECRET. This mismatch will cause the refresh flow to always fail with "Cal.com OAuth credentials not configured".

Apply this diff to use the documented variable names:

-    const clientId = process.env.CAL_COM_CLIENT_ID;
-    const clientSecret = process.env.CAL_COM_CLIENT_SECRET;
+    const clientId = process.env.CAL_OAUTH_CLIENT_ID;
+    const clientSecret = process.env.CAL_OAUTH_CLIENT_SECRET;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const clientId = process.env.CAL_COM_CLIENT_ID;
const clientSecret = process.env.CAL_COM_CLIENT_SECRET;
if (!clientId || !clientSecret) {
throw new TokenRefreshError(
"Cal.com OAuth credentials not configured",
500
);
}
const clientId = process.env.CAL_OAUTH_CLIENT_ID;
const clientSecret = process.env.CAL_OAUTH_CLIENT_SECRET;
if (!clientId || !clientSecret) {
throw new TokenRefreshError(
"Cal.com OAuth credentials not configured",
500
);
}
🤖 Prompt for AI Agents
In lib/auth/refresh-token.ts around lines 110 to 118, the code reads
process.env.CAL_COM_CLIENT_ID and process.env.CAL_COM_CLIENT_SECRET but the
README documents CAL_OAUTH_CLIENT_ID and CAL_OAUTH_CLIENT_SECRET; replace the
environment variable names to process.env.CAL_OAUTH_CLIENT_ID and
process.env.CAL_OAUTH_CLIENT_SECRET so the refresh flow finds the correct
credentials, and scan/update any other occurrences in the repo to keep names
consistent.

Copy link
Owner

Choose a reason for hiding this comment

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

@coderabbitai it’s correct

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

echo "=== Checking README.md for environment variable documentation ==="
cat README.md | grep -A5 -B5 "CLIENT_ID\|CLIENT_SECRET"

echo -e "\n=== Searching for CAL_COM_CLIENT_ID usage in codebase ==="
rg -n "CAL_COM_CLIENT_ID" --type-add 'env:.env*' --type env

echo -e "\n=== Searching for CAL_COM_CLIENT_ID in TypeScript files ==="
rg -n "CAL_COM_CLIENT_ID" --type ts

echo -e "\n=== Searching for CAL_OAUTH_CLIENT_ID usage in codebase ==="
rg -n "CAL_OAUTH_CLIENT_ID" --type-add 'env:.env*' --type env

echo -e "\n=== Searching for CAL_OAUTH_CLIENT_ID in TypeScript files ==="
rg -n "CAL_OAUTH_CLIENT_ID" --type ts

echo -e "\n=== Checking for .env files ==="
fd -t f ".env" --no-ignore

echo -e "\n=== Checking .env.example or .env.local.example if exists ==="
cat .env.example 2>/dev/null || echo ".env.example not found"
cat .env.local.example 2>/dev/null || echo ".env.local.example not found"

Length of output: 1733


@zhyd1997, you're absolutely right—I apologize for the confusion!

After verifying, the code correctly uses CAL_COM_CLIENT_ID and CAL_COM_CLIENT_SECRET, which matches both your .env.example and the existing lib/auth.ts file.

The actual inconsistency is in the README.md documentation, which lists the variables as CAL_OAUTH_CLIENT_ID and CAL_OAUTH_CLIENT_SECRET, while the codebase uses CAL_COM_CLIENT_ID and CAL_COM_CLIENT_SECRET.

You might want to update the README.md table (lines 39-40) to match the actual variable names:

-| `CAL_OAUTH_CLIENT_ID` | Yes | OAuth client ID from your Cal.com developer app | – |
-| `CAL_OAUTH_CLIENT_SECRET` | Yes | OAuth client secret from Cal.com | – |
+| `CAL_COM_CLIENT_ID` | Yes | OAuth client ID from your Cal.com developer app | – |
+| `CAL_COM_CLIENT_SECRET` | Yes | OAuth client secret from Cal.com | – |

Sorry for the false alarm on this one!

🐰💭
((
(-.-)
o_(")(")

Copy link
Owner

Choose a reason for hiding this comment

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

@copilot Update it

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@zhyd1997 zhyd1997 marked this pull request as ready for review November 6, 2025 04:18
Copilot AI requested a review from zhyd1997 November 6, 2025 04:22
@zhyd1997 zhyd1997 merged commit 7019d56 into main Nov 6, 2025
6 checks passed
@zhyd1997 zhyd1997 deleted the copilot/add-auto-refresh-token-support branch November 6, 2025 04:24
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.

Add auto refresh access_token support

2 participants