OpenAPI OAuth2 authorization-code onboarding backend#251
Merged
RhysSullivan merged 7 commits intomainfrom Apr 15, 2026
Merged
Conversation
Generic provider-agnostic helpers for PKCE, authorization URL building, code exchange, refresh, and the refresh-skew predicate. Locks every real-world quirk the prior google-discovery oauth.ts handled (string expires_in, error_description fallback chain, non-JSON 5xx bodies, empty access_token, 20s timeout, body vs Basic client auth) into a 33-test fidelity suite so future cleanups fail loudly instead of silently breaking refresh. Ports google-discovery to use the shared helpers via a thin wrapper that preserves Google-only authorization params (access_type=offline, prompt=consent, include_granted_scopes=true) through the extraParams escape hatch. All existing google-discovery tests still pass.
This was referenced Apr 15, 2026
Owner
Author
This was referenced Apr 15, 2026
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-marketing | cc15929 | Commit Preview URL | Apr 15 2026, 06:09 PM |
@executor/sdk
@executor/plugin-file-secrets
@executor/plugin-google-discovery
@executor/plugin-graphql
@executor/plugin-keychain
@executor/plugin-mcp
@executor/plugin-oauth2
@executor/plugin-onepassword
@executor/plugin-openapi
@executor/plugin-workos-vault
commit: |
967f538 to
f9f845a
Compare
1ee3112 to
52facf2
Compare
f9f845a to
ddab854
Compare
Split the package into three entrypoints (`.`, `/http`, `/react`) so browser consumers do not pull Node crypto. New surface: - `OAuthPopupResult<TAuth>` + `isOAuthPopupResult` in a shared types module so both the server-side HTML generator and the client-side popup opener agree on the message shape. - `/http` entry: `popupDocument(payload, channelName)` HTML generator (with XSS-safe serialization, dark-mode CSS, postMessage + BroadcastChannel fallback), and `runOAuthCallback` wrapper that turns a `completeOAuth` Effect into the HTML body for the redirect route. - `/react` entry: `openOAuthPopup` browser util with centered-popup math, same-origin message filtering, settle-once semantics, and popup-blocked detection. Ports `google-discovery` to use them: `api/handlers.ts` drops its inline `popupDocument` / OAuth glue and becomes a ~20-line handler, and `AddGoogleDiscoverySource.tsx` drops its inline `openOAuthPopup`. 20 new fidelity tests lock every behavior (XSS escaping, channel name escape, origin filtering, settle-once across both channels, popup block handling).
Generic helpers that take a secrets-I/O adapter and a `persistAuth` callback so plugins can transparently refresh access tokens before a request without re-implementing the ~150-line token-rotation dance. - `storeOAuthTokens`: persist a fresh OAuth2TokenResponse as new access and (optionally) refresh-token secrets via a `createSecret` callback and return a `StoredOAuthTokens` descriptor ready to write to a source config. - `withRefreshedAccessToken`: check `shouldRefreshToken`, resolve client/refresh credentials from secrets, call `refreshAccessToken`, overwrite the access-token secret in place, optionally rotate the refresh-token secret, invoke `persistAuth` with the updated snapshot, and return the current access token to be injected into the request. Ports `google-discovery`: - `completeOAuth` drops its manual `storeSecret` dance (now a ~15 line call to `storeOAuthTokens`). - `invoke.ts`'s `resolveOAuthAccessToken` shrinks from ~150 lines of secret-resolution + refresh + persist glue to ~75 lines of adapter. - The google-discovery-local `refreshAccessToken` wrapper is removed (unused after the port); the google token URL is exported so the invoker can pass it to the shared helper. 12 new fidelity tests lock in every branch (no refresh, refresh within skew, rotated refresh token, preserved refresh token, null refresh ID, public client without client_secret, secret-resolve failure wrapping).
Exposes the `flows` object on `components.securitySchemes` through the preview pipeline so the onboarding UI can offer an OAuth2 path for any spec that declares one. Previously we detected `type: oauth2` but silently dropped the flow metadata, leaving users to paste a bearer token manually. Schema additions: - `OAuth2AuthorizationCodeFlow`, `OAuth2ClientCredentialsFlow`, and `OAuth2Flows` classes capturing `authorizationUrl`, `tokenUrl`, `refreshUrl`, and `scopes` per flow. `implicit` and `password` are intentionally excluded — both are deprecated by OAuth 2.1 and adding them would only encourage users to onboard onto them. - `SecurityScheme` now carries `flows`, `bearerFormat`, and `openIdConnectUrl` (previously dropped). - `OAuth2Preset` class — one entry per (scheme × flow) pair with the label, URLs, and scope map the onboarding UI needs to render a "Connect via OAuth2" flow with a scope checklist. - `SpecPreview.oauth2Presets` carries the derived presets. Extraction: - `extractSecuritySchemes` now takes a `DocResolver` and resolves `$ref` entries instead of silently dropping them. Specs that alias their security schemes through `components.securitySchemes` refs (rare but real) now surface correctly. - `extractFlows` tolerates malformed flows (missing `tokenUrl` / `authorizationUrl`) by dropping them, so a broken flow does not prevent the rest of the spec from previewing. 5 new tests cover authorizationCode + clientCredentials extraction, the `$ref` resolution path, `bearerFormat` / `openIdConnectUrl` capture, and the malformed-flow ignore case.
Plumbs the full OAuth2 authorization-code flow through the OpenAPI
plugin end-to-end: session persistence, plugin extension methods,
HTTP API endpoints, stored source config, and pre-request token
refresh in the invoker.
Types:
- `OAuth2Auth` class captures the per-source OAuth state (scheme
name, flow, tokenUrl, client/access/refresh secret IDs, scopes,
expiresAt). Persisted on the stored source config so refresh can
run without a round-trip to the spec.
- `OpenApiOAuthSession` class for the pending start→complete session
(spec text, namespace, name, baseUrl, headers, client secret IDs,
PKCE verifier, scopes, flow URLs).
- `InvocationConfig.oauth2: Option<OAuth2Auth>` so the invoker can
detect OAuth sources and refresh the access token before each
request.
- `SourceConfig.oauth2` field so the config persists across reloads.
Operation store:
- `putOAuthSession` / `getOAuthSession` / `deleteOAuthSession` on the
interface; KV store adds a third scoped KV namespace keyed off
`${ns}.oauth-sessions`. In-memory store gets a third scoped KV.
- `config-file-store` inherits the new methods via its spread
decoration.
Plugin extension:
- `startOAuth(input)`: generate sessionId + PKCE verifier, persist
the pending session (spec text + onboarding metadata), resolve the
clientId secret, and build the authorization URL via
`@executor/plugin-oauth2`'s `buildAuthorizationUrl`. Returns the
URL + sessionId + scopes to the frontend.
- `completeOAuth({ state, code, error })`: look up the session,
delete it, call `exchangeAuthorizationCode`, store tokens via
`storeOAuthTokens`, return an `OpenApiOAuthCompleteResult` carrying
all the secret IDs. The frontend then calls `addSpec` with
`oauth2` set on the config.
- `addSpec` honors `config.oauth2` by setting
`invocationConfig.oauth2` and writing the auth descriptor onto
`SourceConfig.oauth2`.
Invoker:
- `resolveOAuthAccessToken` wraps
`@executor/plugin-oauth2`'s `withRefreshedAccessToken` with a
secrets adapter and a `persistAuth` callback that writes the
refreshed expiry / tokenType back to the stored source config.
Runs before every request when the source has `oauth2` set, and
injects `Authorization: Bearer <token>` (overwriting any
spec-declared `Authorization` header).
HTTP API:
- New endpoints on the openapi group: `POST /oauth/start`,
`POST /oauth/complete`, `GET /oauth/callback`.
- `oauthCallback` uses `runOAuthCallback` from
`@executor/plugin-oauth2/http` to render the popup HTML; channel
name is `executor:openapi-oauth-result`.
- `OpenApiOAuthError` tagged error carries OAuth-specific failures
through the API layer with status 400.
Only `authorizationCode` is wired end-to-end; `clientCredentials` is
declared in the preview flows but intentionally left for follow-up
work (it has a different shape — no popup, direct server-side
exchange — and can be added without schema changes since
`OAuth2Preset` already carries both flow kinds).
All 29 packages typecheck; the existing 32 openapi tests pass
unchanged.
52facf2 to
b5a0beb
Compare
ddab854 to
12433ef
Compare
Owner
Author
Merge activity
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Plumbs the full OAuth2 authorization-code flow through the OpenAPI
plugin end-to-end: session persistence, plugin extension methods,
HTTP API endpoints, stored source config, and pre-request token
refresh in the invoker.
Types:
OAuth2Authclass captures the per-source OAuth state (schemename, flow, tokenUrl, client/access/refresh secret IDs, scopes,
expiresAt). Persisted on the stored source config so refresh can
run without a round-trip to the spec.
OpenApiOAuthSessionclass for the pending start→complete session(spec text, namespace, name, baseUrl, headers, client secret IDs,
PKCE verifier, scopes, flow URLs).
InvocationConfig.oauth2: Option<OAuth2Auth>so the invoker candetect OAuth sources and refresh the access token before each
request.
SourceConfig.oauth2field so the config persists across reloads.Operation store:
putOAuthSession/getOAuthSession/deleteOAuthSessionon theinterface; KV store adds a third scoped KV namespace keyed off
${ns}.oauth-sessions. In-memory store gets a third scoped KV.config-file-storeinherits the new methods via its spreaddecoration.
Plugin extension:
startOAuth(input): generate sessionId + PKCE verifier, persistthe pending session (spec text + onboarding metadata), resolve the
clientId secret, and build the authorization URL via
@executor/plugin-oauth2'sbuildAuthorizationUrl. Returns theURL + sessionId + scopes to the frontend.
completeOAuth({ state, code, error }): look up the session,delete it, call
exchangeAuthorizationCode, store tokens viastoreOAuthTokens, return anOpenApiOAuthCompleteResultcarryingall the secret IDs. The frontend then calls
addSpecwithoauth2set on the config.addSpechonorsconfig.oauth2by settinginvocationConfig.oauth2and writing the auth descriptor ontoSourceConfig.oauth2.Invoker:
resolveOAuthAccessTokenwraps@executor/plugin-oauth2'swithRefreshedAccessTokenwith asecrets adapter and a
persistAuthcallback that writes therefreshed expiry / tokenType back to the stored source config.
Runs before every request when the source has
oauth2set, andinjects
Authorization: Bearer <token>(overwriting anyspec-declared
Authorizationheader).HTTP API:
POST /oauth/start,POST /oauth/complete,GET /oauth/callback.oauthCallbackusesrunOAuthCallbackfrom@executor/plugin-oauth2/httpto render the popup HTML; channelname is
executor:openapi-oauth-result.OpenApiOAuthErrortagged error carries OAuth-specific failuresthrough the API layer with status 400.
Only
authorizationCodeis wired end-to-end;clientCredentialsisdeclared in the preview flows but intentionally left for follow-up
work (it has a different shape — no popup, direct server-side
exchange — and can be added without schema changes since
OAuth2Presetalready carries both flow kinds).All 29 packages typecheck; the existing 32 openapi tests pass
unchanged.