Skip to content

fix: drop incorrect redirect_uri from Basecamp3 token refresh#29670

Open
apoorva-01 wants to merge 2 commits into
calcom:mainfrom
apoorva-01:fix/29585-basecamp3-refresh-redirect-uri
Open

fix: drop incorrect redirect_uri from Basecamp3 token refresh#29670
apoorva-01 wants to merge 2 commits into
calcom:mainfrom
apoorva-01:fix/29585-basecamp3-refresh-redirect-uri

Conversation

@apoorva-01

@apoorva-01 apoorva-01 commented Jun 29, 2026

Copy link
Copy Markdown

What does this PR do?

The Basecamp3 token-refresh request appended a redirect_uri parameter set to the app root URL (WEBAPP_URL). That's not the /api/integrations/basecamp3/callback URL used during the initial OAuth authorization, and Basecamp's documented refresh request only expects type=refresh, refresh_token, client_id and client_secret. Sending an unexpected redirect_uri can make the refresh fail once the stored access token expires.

This drops the redirect_uri parameter (and the now-unused WEBAPP_URL import) so the refresh request matches Basecamp's documented parameters. The initial OAuth exchange, which legitimately uses the callback redirect_uri, is left alone. Reference: https://github.com/basecamp/api/blob/master/sections/authentication.md

Visual Demo (For contributors especially)

N/A. This is a backend-only change to the Basecamp3 token-refresh helper, so there's nothing visual to show.

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 if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox. N/A
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

No env vars or test data needed. The helper is unit-tested in isolation.

TZ=UTC yarn vitest run packages/app-store/basecamp3/lib/helpers.test.ts

The new helpers.test.ts mocks getBasecampKeys, fetch, and Prisma, then asserts the refresh request:

  • is sent to https://launchpad.37signals.com/authorization/token
  • includes type=refresh, refresh_token, client_id, client_secret
  • does not include redirect_uri
  • persists the refreshed token back onto the existing credential

Restore the redirect_uri line and the first test fails on the redirect_uri assertion; with the fix it passes.

Checklist

I've read the contributing guide, the code follows the style guidelines, and there are no new warnings. This is a small PR (2 files).

The Basecamp3 refresh request appended redirect_uri set to the app
root URL (WEBAPP_URL), which differs from the callback URL used during
the initial OAuth flow. Basecamp's token endpoint only expects type,
refresh_token, client_id and client_secret on a refresh, so sending an
unexpected redirect_uri can make the refresh fail once the access token
expires.

Send only the documented refresh parameters and add a unit test around
refreshAccessToken covering the request shape.
@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @apoorva-01! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@github-actions github-actions Bot added the 🐛 bug Something isn't working label Jun 29, 2026
@apoorva-01 apoorva-01 marked this pull request as ready for review July 1, 2026 17:57
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The Basecamp3 helper's token refresh logic no longer includes a redirect_uri query parameter when requesting a new access token from Basecamp's OAuth endpoint, and the now-unused WEBAPP_URL import was removed. A new test file was added covering refreshAccessToken, verifying the refresh request URL parameters exclude redirect_uri and that the refreshed token is correctly persisted to the credential via Prisma.

Changes

Cohort / File(s) Summary
Basecamp3 refresh token fix and tests
packages/app-store/basecamp3/lib/helpers.ts Removed redirect_uri from the refresh token fetch URL; removed unused WEBAPP_URL import
packages/app-store/basecamp3/lib/helpers.test.ts Added new test suite verifying refresh request URL params and credential persistence

Sequence Diagram(s)

sequenceDiagram
  participant refreshAccessToken
  participant BasecampOAuth
  participant Prisma
  refreshAccessToken->>BasecampOAuth: fetch token endpoint with refresh_token, client_id, client_secret
  BasecampOAuth-->>refreshAccessToken: new access_token
  refreshAccessToken->>Prisma: credential.update with new key
Loading

Related PRs: None identified.

Suggested labels: app-store, tests, bugfix

Suggested reviewers: None identified.

Poem
A rabbit hopped through OAuth's gate,
Trimmed a param it deemed too late,
No redirect_uri, just token and key,
Tests now guard this fix with glee,
Basecamp3 refreshed, clean and straight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main fix: removing the incorrect Basecamp3 redirect_uri from token refresh.
Linked Issues check ✅ Passed The code and tests address #29585 by removing redirect_uri, keeping the expected refresh params, and persisting the refreshed credential.
Out of Scope Changes check ✅ Passed The changes are limited to the Basecamp3 refresh helper and its unit test, with no unrelated scope added.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The description matches the code changes by explaining the removed redirect_uri and added refresh-token tests.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
packages/app-store/basecamp3/lib/helpers.test.ts (1)

69-77: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the exact fetch contract, not just substrings.

These checks still pass if another unexpected query param gets appended later. Parsing the URL and asserting the full searchParams (and request options) would pin the regression much more tightly.

Possible tightening
   expect(fetchMock).toHaveBeenCalledOnce();
-  const requestedUrl = fetchMock.mock.calls[0][0] as string;
+  const [requestedUrl, requestInit] = fetchMock.mock.calls[0];
+  const url = new URL(requestedUrl as string);
 
-  expect(requestedUrl).toContain("https://launchpad.37signals.com/authorization/token");
-  expect(requestedUrl).toContain("type=refresh");
-  expect(requestedUrl).toContain(`refresh_token=${basecampKey.refresh_token}`);
-  expect(requestedUrl).toContain("client_id=test-client-id");
-  expect(requestedUrl).toContain("client_secret=test-client-secret");
-  expect(requestedUrl).not.toContain("redirect_uri");
+  expect(url.origin + url.pathname).toBe("https://launchpad.37signals.com/authorization/token");
+  expect(Object.fromEntries(url.searchParams.entries())).toEqual({
+    type: "refresh",
+    refresh_token: basecampKey.refresh_token,
+    client_id: "test-client-id",
+    client_secret: "test-client-secret",
+  });
+  expect(requestInit).toMatchObject({
+    method: "POST",
+    headers: { "User-Agent": "Cal.com (test@example.com)" },
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app-store/basecamp3/lib/helpers.test.ts` around lines 69 - 77, The
token refresh test in helpers.test.ts is too loose because it only checks URL
substrings, so unexpected query params or request options could slip through.
Update the assertion around fetchMock/requestedUrl to parse the called URL from
the helpers test and verify the full searchParams exactly for the refresh flow,
using the relevant token-refresh helper path and request setup. Also assert the
request options contract (method/body/headers as applicable) so the fetch call
from the refresh-token logic is pinned down by the helper’s expected behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/app-store/basecamp3/lib/helpers.ts`:
- Around line 9-10: The refresh request in the Basecamp 3 helper is building the
token URL by string interpolation, which can break when credential values
contain reserved characters. Update the fetch call in helpers.ts to construct
the query string with URLSearchParams for the refresh flow, using the existing
refresh_token, client_id, and client_secret values so the request is encoded
correctly regardless of credential contents.

---

Nitpick comments:
In `@packages/app-store/basecamp3/lib/helpers.test.ts`:
- Around line 69-77: The token refresh test in helpers.test.ts is too loose
because it only checks URL substrings, so unexpected query params or request
options could slip through. Update the assertion around fetchMock/requestedUrl
to parse the called URL from the helpers test and verify the full searchParams
exactly for the refresh flow, using the relevant token-refresh helper path and
request setup. Also assert the request options contract (method/body/headers as
applicable) so the fetch call from the refresh-token logic is pinned down by the
helper’s expected behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df013015-94af-46f6-b117-db89df15d8a1

📥 Commits

Reviewing files that changed from the base of the PR and between 53e32a5 and 803ce99.

📒 Files selected for processing (2)
  • packages/app-store/basecamp3/lib/helpers.test.ts
  • packages/app-store/basecamp3/lib/helpers.ts
Comment on lines 9 to +10
const tokenInfo = await fetch(
`https://launchpad.37signals.com/authorization/token?type=refresh&refresh_token=${credentialKey.refresh_token}&client_id=${clientId}&redirect_uri=${WEBAPP_URL}&client_secret=${clientSecret}`,
`https://launchpad.37signals.com/authorization/token?type=refresh&refresh_token=${credentialKey.refresh_token}&client_id=${clientId}&client_secret=${clientSecret}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Encode the refresh params instead of interpolating them into the URL.

Line 10 builds the query string by hand, so valid refresh_token / client_secret values containing +, =, or & will be parsed incorrectly and the refresh can still fail. Use URLSearchParams here so the request shape stays correct for every credential.

Proposed fix
-  const tokenInfo = await fetch(
-    `https://launchpad.37signals.com/authorization/token?type=refresh&refresh_token=${credentialKey.refresh_token}&client_id=${clientId}&client_secret=${clientSecret}`,
+  const params = new URLSearchParams({
+    type: "refresh",
+    refresh_token: credentialKey.refresh_token,
+    client_id: clientId,
+    client_secret: clientSecret,
+  });
+  const tokenInfo = await fetch(
+    `https://launchpad.37signals.com/authorization/token?${params.toString()}`,
     { method: "POST", headers: { "User-Agent": userAgent } }
   );
📝 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 tokenInfo = await fetch(
`https://launchpad.37signals.com/authorization/token?type=refresh&refresh_token=${credentialKey.refresh_token}&client_id=${clientId}&redirect_uri=${WEBAPP_URL}&client_secret=${clientSecret}`,
`https://launchpad.37signals.com/authorization/token?type=refresh&refresh_token=${credentialKey.refresh_token}&client_id=${clientId}&client_secret=${clientSecret}`,
const params = new URLSearchParams({
type: "refresh",
refresh_token: credentialKey.refresh_token,
client_id: clientId,
client_secret: clientSecret,
});
const tokenInfo = await fetch(
`https://launchpad.37signals.com/authorization/token?${params.toString()}`,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app-store/basecamp3/lib/helpers.ts` around lines 9 - 10, The refresh
request in the Basecamp 3 helper is building the token URL by string
interpolation, which can break when credential values contain reserved
characters. Update the fetch call in helpers.ts to construct the query string
with URLSearchParams for the refresh flow, using the existing refresh_token,
client_id, and client_secret values so the request is encoded correctly
regardless of credential contents.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐛 bug Something isn't working size/M

1 participant