Skip to content

fix(oauth): support Basic Auth token exchange for Notion - #711

Merged
kody-bot merged 1 commit into
mainfrom
cursor/fix-notion-oauth-basic-auth-exchange-284f
Jul 10, 2026
Merged

kody-bot merged 1 commit into
mainfrom
cursor/fix-notion-oauth-basic-auth-exchange-284f

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 10, 2026 •

Copy link
Copy Markdown
Owner

Summary

Notion OAuth via /connect/oauth failed after the user approved access: Kody exchanged the code with a form-urlencoded client_secret body (no Basic Auth), Notion returned HTTP 401, and the connect UI treated that 401 as a Kody session expiry ("Session expired.") instead of showing the provider error. Tokens were never saved.

Fix

  • Add tokenExchangeStyle: form (default, existing confidential body-secret flows) and basic-json (HTTP Basic + JSON body).
  • Auto-detect basic-json for api.notion.com; allow explicit override via request body / integration metadata.
  • On provider token-exchange failure, return 502 with error / error_description / providerStatus instead of passthrough 401.
  • Connect UI distinguishes real session 401 (Unauthorized.) from provider failures and surfaces error_description.

Test plan

  • Unit: Notion-style Basic Auth + JSON exchange
  • Unit: confidential form-body exchange still works
  • Unit: provider 401 mapped to 502 (not session expiry)
  • Unit: connect-oauth messaging for provider failure vs session expiry
  • npm run typecheck
  • oxlint on touched files
System recap β€” extends existing primitives (medium risk)

Mode: recap Β· Base: main @ 0e95e1a8 Β· Head: 620a996d

Classification: extends β€” outbound OAuth token exchange and integration config gain a tokenExchangeStyle contract; connect UI error handling for /account/secrets.json oauth_exchange changes.

Primitives touched

Primitive Group Impact
app-ui surfaces extends β€” /connect/oauth exchange + failure messaging
secrets assistant extends β€” oauth_exchange Basic Auth/JSON styles; provider errors β†’ 502
values assistant extends β€” optional tokenExchangeStyle on integration config

System map

Connect OAuth token exchange now chooses form vs Notion-style Basic+JSON before calling the provider, and provider auth failures no longer look like Kody session expiry.

Legend: green = composes (wiring only) Β· amber = extended by this PR Β· red = new primitive Β· gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	appUi["app-ui<br/>Browser app"]:::extended
	secrets["secrets<br/>Secret references"]:::extended
	values["values<br/>Values"]:::extended
	appUi -->|"oauth_exchange + tokenExchangeStyle"| secrets
	secrets -->|"Basic Auth or form body"| providerToken["Provider token endpoint"]:::untouched
	appUi -->|"connect_oauth persists style"| values
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Change flow

sequenceDiagram
	participant UI as connect-oauth
	participant API as account-secrets oauth_exchange
	participant P as Provider token URL
	UI->>API: oauth_exchange (params, flow, style)
	API->>P: Basic+JSON or form body
	alt provider 401/4xx
		P-->>API: error JSON
		API-->>UI: 502 + error_description
		UI-->>UI: show provider error (not Session expired)
	else success
		P-->>API: tokens
		API-->>UI: 200 token payload
	end
Loading

Before / after

Before After
Notion exchange form body + client_secret Basic Auth + JSON body
Provider 401 HTTP 401 to browser HTTP 502 + provider details
Connect UI "Session expired." Provider error_description

Invariants

  • Per-user isolation unchanged: secret resolution and integration saves remain scoped by userId.
Open in WebΒ Open in CursorΒ 

Summary by CodeRabbit

  • New Features
    • Added support for OAuth providers using form-based or JSON-based token exchange.
    • Automatically selects the appropriate exchange format for supported providers, with saved configurations preserved.
    • Improved OAuth error handling to distinguish provider failures from actual session expiration.
    • Provider token-exchange failures now return clearer error details without incorrectly redirecting users to login.

Notion's token endpoint requires HTTP Basic Auth and a JSON body, but
oauth_exchange only sent form-urlencoded client_secret. Provider 401s
were also passed through and misread as session expiry in /connect/oauth.

Add tokenExchangeStyle (form | basic-json) with Notion host auto-detect,
map provider failures to 502 with error details, and show the real
provider error in the connect UI.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

πŸ“ Walkthrough

Walkthrough

OAuth token exchange now supports form and basic-json request styles, persists the selected style in integration configuration, and standardizes provider failure handling so only genuine session-expired responses redirect to login.

Changes

OAuth token exchange

Layer / File(s) Summary
Integration configuration contract
packages/worker/src/mcp/capabilities/integrations/integration-shared.ts, packages/worker/src/mcp/capabilities/integrations/integration-save.ts
Schemas and normalization accept and preserve optional form or basic-json token exchange styles.
Token exchange request and failure helpers
packages/worker/src/app/oauth-token-exchange.ts, packages/worker/src/app/oauth-token-exchange.node.test.ts
Exchange styles are resolved by override or token host, requests are formatted as JSON Basic-auth or URL-encoded form, and provider failures are normalized to HTTP 502 payloads.
Worker OAuth exchange wiring
packages/worker/src/app/handlers/account-secrets.ts, packages/worker/src/app/handlers/account-secrets.node.test.ts
The handler resolves styles, constructs outbound requests, persists non-default styles, and exposes provider status and error details.
Client OAuth configuration and failure classification
packages/worker/client/routes/connect-oauth.tsx, packages/worker/client/routes/connect-oauth.node.test.ts
The client carries styles through exchange and connection requests, parses stored styles, infers defaults, and distinguishes provider 401 errors from session expiry.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client as connect-oauth
  participant Handler as account-secrets
  participant Helpers as oauth-token-exchange
  participant Provider as OAuth provider
  Client->>Handler: oauth_exchange with tokenExchangeStyle
  Handler->>Helpers: resolve and build token request
  Helpers->>Provider: form or basic-json token request
  Provider-->>Handler: tokens or provider error
  Handler-->>Client: tokens or standardized failure
  Client->>Client: redirect only for session-expired classification
Loading

Possibly related PRs

πŸš₯ Pre-merge checks | βœ… 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
βœ… 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 clearly summarizes the main change: Notion OAuth now supports Basic Auth token exchange.
Linked Issues check βœ… Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check βœ… Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
πŸ“ Generate docstrings
  • Create stacked PR
  • Commit on current branch
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/fix-notion-oauth-basic-auth-exchange-284f

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.

@kody-bot
kody-bot marked this pull request as ready for review July 10, 2026 18:32
@github-actions

Copy link
Copy Markdown
Contributor

πŸ”Ž Preview deployed: https://kody-pr-711.kody-a99.workers.dev

Worker: kody-pr-711
D1: kody-pr-711-db
KV: kody-pr-711-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/worker/client/routes/connect-oauth.tsx (1)

300-321: 🎯 Functional Correctness | πŸ”΅ Trivial | πŸ’€ Low value

isConnectOauthConfig doesn't validate the now-required tokenExchangeStyle.

ConnectOauthConfig.tokenExchangeStyle is required (Line 69), but this guard doesn't check it, so a config persisted by an older build (before this field existed) still passes and is used with tokenExchangeStyle: undefined. It's currently self-recovering β€” JSON.stringify drops the undefined key and the server re-infers the style from tokenUrl β€” but validating it here keeps the type contract honest.

♻️ Optional guard addition
 			typeof record.flow === 'string' &&
+			(record.tokenExchangeStyle === 'form' ||
+				record.tokenExchangeStyle === 'basic-json') &&
 			typeof record.scopeSeparator === 'string' &&
πŸ€– 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/worker/client/routes/connect-oauth.tsx` around lines 300 - 321, Add
validation for the required ConnectOauthConfig.tokenExchangeStyle field in
isConnectOauthConfig, ensuring it is a string (or otherwise matches the declared
TokenExchangeStyle type) before accepting the record as valid. Place the check
alongside the existing scalar property validations so persisted legacy configs
without this field are rejected.
packages/worker/src/mcp/capabilities/integrations/integration-shared.ts (1)

8-9: πŸ“ Maintainability & Code Quality | πŸ”΅ Trivial | ⚑ Quick win

Derive this from packages/worker/src/app/oauth-token-exchange.ts's tokenExchangeStyles. Keeping one source for the literals avoids the schema and resolver drifting apart.

πŸ€– 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/worker/src/mcp/capabilities/integrations/integration-shared.ts`
around lines 8 - 9, Derive tokenExchangeStyleValues from the tokenExchangeStyles
definition in oauth-token-exchange.ts instead of duplicating the literal values,
and update its type/export usage as needed so the schema and resolver share a
single source of truth.
πŸ€– 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.

Nitpick comments:
In `@packages/worker/client/routes/connect-oauth.tsx`:
- Around line 300-321: Add validation for the required
ConnectOauthConfig.tokenExchangeStyle field in isConnectOauthConfig, ensuring it
is a string (or otherwise matches the declared TokenExchangeStyle type) before
accepting the record as valid. Place the check alongside the existing scalar
property validations so persisted legacy configs without this field are
rejected.

In `@packages/worker/src/mcp/capabilities/integrations/integration-shared.ts`:
- Around line 8-9: Derive tokenExchangeStyleValues from the tokenExchangeStyles
definition in oauth-token-exchange.ts instead of duplicating the literal values,
and update its type/export usage as needed so the schema and resolver share a
single source of truth.

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a09bdb59-031f-4fab-853b-b0d7739de8ca

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 0e95e1a and 620a996.

πŸ“’ Files selected for processing (8)
  • packages/worker/client/routes/connect-oauth.node.test.ts
  • packages/worker/client/routes/connect-oauth.tsx
  • packages/worker/src/app/handlers/account-secrets.node.test.ts
  • packages/worker/src/app/handlers/account-secrets.ts
  • packages/worker/src/app/oauth-token-exchange.node.test.ts
  • packages/worker/src/app/oauth-token-exchange.ts
  • packages/worker/src/mcp/capabilities/integrations/integration-save.ts
  • packages/worker/src/mcp/capabilities/integrations/integration-shared.ts

@kody-bot
kody-bot merged commit 232fbbd into main Jul 10, 2026
7 checks passed
@kody-bot
kody-bot deleted the cursor/fix-notion-oauth-basic-auth-exchange-284f branch July 10, 2026 18:42
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.

3 participants