Skip to content

Fix OAuth App picker showing apps from other integrations (part of #1120)#1139

Open
RhysSullivan wants to merge 1 commit into
mainfrom
fix/1120-oauth-cross-integration-leak
Open

Fix OAuth App picker showing apps from other integrations (part of #1120)#1139
RhysSullivan wants to merge 1 commit into
mainfrom
fix/1120-oauth-cross-integration-leak

Conversation

@RhysSullivan

Copy link
Copy Markdown
Owner

Part of #1120 (1 of 4: foundational).

Problem

The "OAuth App" picker listed apps registered for other integrations, e.g. Datadog clients showing up under Atlassian. A discovery-only OAuth method declares no endpoints, so the picker fell into its "no endpoints, show every app" branch and never consulted which integration an app had been registered for.

Fix

Record the integration each OAuth app was registered for (both DCR and manual), threaded sdk -> api -> react as origin.integration. The picker now matches by integration first, falling back to endpoint-domain matching only when endpoints are actually declared.

Tests

Adds a selfhost e2e (oauth-picker-cross-integration): registers a Datadog app, opens the Atlassian picker, and asserts the Atlassian app is shown while the Datadog one is not.

Existing react unit + typecheck stay green (lint 0/0, typecheck 41/41, add-account-modal 21/21).

This is the base for the dedup-DCR PR, which stacks on this branch.

The "OAuth App" picker listed apps registered for other integrations
(e.g. Datadog clients showing up under Atlassian). A discovery-only
OAuth method declares no endpoints, so the picker hit its "no endpoints,
show every app" branch and never consulted which integration an app was
registered for.

Record the integration each OAuth app was registered for (DCR and
manual), threaded sdk -> api -> react via origin.integration, and match
the picker by integration first, falling back to endpoint-domain
matching only when endpoints are actually declared.

Adds a selfhost e2e (oauth-picker-cross-integration) that registers a
Datadog app, then opens the Atlassian picker and asserts the Atlassian
app shows while the Datadog one does not.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 4da05aa Commit Preview URL

Branch Preview URL
Jun 26 2026, 05:40 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 26, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 4da05aa Jun 26 2026, 05:40 AM

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare preview

Console https://executor-preview-pr-1139.executor-e2e.workers.dev
MCP https://executor-preview-pr-1139.executor-e2e.workers.dev/mcp
Deployed commit 4da05aa

Sign-in is Cloudflare Access (one-time PIN to an allowed email). The preview has its own database and encryption key; it is destroyed when this PR closes.

@pkg-pr-new

pkg-pr-new Bot commented Jun 26, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1139

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1139

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1139

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1139

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1139

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1139

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1139

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1139

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1139

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1139

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1139

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1139

executor

npm i https://pkg.pr.new/executor@1139

commit: 4da05aa

@RhysSullivan RhysSullivan changed the title Fix OAuth App picker leaking apps across integrations (part of #1120) Fix OAuth App picker showing apps from other integrations (part of #1120) Jun 26, 2026
@greptile-apps

greptile-apps Bot commented Jun 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes the OAuth app picker showing apps from unrelated integrations by recording origin.integration on every OAuth client (both DCR and manual) and using it as the primary filter signal in selectClientsForEndpoints. Previously, discovery-only OAuth methods (e.g. MCP sources that declare no static endpoints) caused the picker to fall through to a "show every app" branch, leaking Datadog clients into Atlassian pickers.

  • SDK → API → React threading: origin.integration is persisted on oauth_client rows for both manual and DCR registrations, surfaced through the API schema, and consumed by the React picker hook — the integration slug now acts as the first-class filter, with endpoint-domain matching retained as a fallback for integrations that do declare static endpoints.
  • selectClientsForEndpoints fix: the "no signal → show all" early return now requires both integration and endpoints to be absent; when an integration slug is known but endpoints are not, only apps explicitly registered for that integration are surfaced — unrelated providers no longer leak in.
  • Selfhost e2e regression test: registers a Datadog app and an Atlassian app under the same owner, opens the Atlassian MCP picker, and asserts the Datadog app does not appear.

Confidence Score: 4/5

Safe to merge — the fix is well-scoped, the logic change is guarded by a new selfhost e2e regression test, and all existing unit/typecheck suites pass.

The core logic change in selectClientsForEndpoints is straightforward and correctly narrows the "show everything" early return to require both integration and endpoints to be absent. The UseOAuthClientsResult.endpointMatched field JSDoc is now partially stale — it describes only the endpoint-matching case but the field now also reflects integration-matching outcomes — leaving the interface docs misleading for future readers. No functional regressions are introduced.

use-effective-oauth-client.tsx — the endpointMatched and displayRegisterCTA field docs should be updated to reflect the broadened semantics introduced by this change.

Important Files Changed

Filename Overview
packages/react/src/plugins/use-effective-oauth-client.tsx Core fix: selectClientsForEndpoints now uses integration slug as a first-class filter signal; updated docs on UseOAuthClientsResult.endpointMatched are partially stale after the semantic broadening.
packages/core/sdk/src/oauth-service.ts Refactors parseOAuthClientOrigin to share the integration-parsing logic between the DCR and manual origin branches; also updates the write path to persist origin.integration for manual apps.
packages/core/api/src/oauth/api.ts Adds optional originIntegration to CreateClientPayload and integration to the manual-origin variant of OAuthClientSummaryResponse schema.
packages/react/src/components/add-account-modal.tsx Passes integration slug to useOAuthClientsForIntegration and threads originIntegration into OAuthClientForm; uses allClients for slug-uniqueness checks instead of the filtered subset.
packages/core/sdk/src/oauth-client.ts Adds integration field to the manual OAuthClientOrigin variant and exports oauthClientOriginIntegration helper for normalized integration reads across origin kinds.
e2e/selfhost/oauth-picker-cross-integration.test.ts New selfhost e2e regression: registers apps for two different integrations, drives the Atlassian picker via the BYO fallback path, and asserts cross-integration app leakage is gone; follows existing selfhost test patterns.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["selectClientsForEndpoints(all, {integration, tokenUrl, authorizationUrl})"] --> B{wantedIntegration\nAND/OR endpoints\npresent?}
    B -- "neither present\n(no context at all)" --> C["Early return:\nall apps matched\n(legacy fall-through)"]
    B -- "at least one signal" --> D["For each app:"]
    D --> E{fitsIntegration?\norigin.integration\n=== wantedIntegration}
    D --> F{fitsEndpoint?\ntokenRoot or\nauthRoot match}
    E -- yes --> G["→ matched"]
    F -- yes --> G
    E -- no --> H{either fits?}
    F -- no --> H
    H -- no --> I["→ unmatched"]
    G --> J{matched.length > 0?}
    J -- yes --> K["endpointMatched=true\nclients=matched\notherClients=[]"]
    J -- no --> L["endpointMatched=false\nclients=[]\notherClients=unmatched\ndisplayRegisterCTA=true"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["selectClientsForEndpoints(all, {integration, tokenUrl, authorizationUrl})"] --> B{wantedIntegration\nAND/OR endpoints\npresent?}
    B -- "neither present\n(no context at all)" --> C["Early return:\nall apps matched\n(legacy fall-through)"]
    B -- "at least one signal" --> D["For each app:"]
    D --> E{fitsIntegration?\norigin.integration\n=== wantedIntegration}
    D --> F{fitsEndpoint?\ntokenRoot or\nauthRoot match}
    E -- yes --> G["→ matched"]
    F -- yes --> G
    E -- no --> H{either fits?}
    F -- no --> H
    H -- no --> I["→ unmatched"]
    G --> J{matched.length > 0?}
    J -- yes --> K["endpointMatched=true\nclients=matched\notherClients=[]"]
    J -- no --> L["endpointMatched=false\nclients=[]\notherClients=unmatched\ndisplayRegisterCTA=true"]
Loading

Comments Outside Diff (1)

  1. packages/react/src/plugins/use-effective-oauth-client.tsx, line 79-95 (link)

    P2 Stale endpointMatched / displayRegisterCTA docs after semantic broadening

    The endpointMatched field doc still says true means "no endpoint filter was requested" and false means "the integration declared endpoint(s) but NO registered app matched." After this change, endpointMatched is false whenever any discriminating signal exists (integration slug or declared endpoints) but nothing matched — including the new MCP case where an integration is known but has zero apps registered for it and no endpoints are declared. Likewise, displayRegisterCTA's doc says "an endpoint was declared and nothing matched," but it now also fires when an integration slug is known and no app has that integration recorded. Readers reasoning about the empty-state / register-CTA path from the interface docs will draw the wrong mental model.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "Fix #1120: stop the OAuth App picker lea..." | Re-trigger Greptile

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.

1 participant