History

connections: extract sign-in state into a first-class SDK primitive

Introduces a `Connection` resource on the core SDK (`ctx.connections`) that owns live OAuth access/refresh tokens and the plugin-specific providerState needed to refresh them. Plugins register a `ConnectionProvider` alongside their secret providers; source auth configs become thin pointers (`{kind: "oauth2", connectionId}`) instead of inlining secret/token state. - core/sdk: ConnectionId brand, ConnectionRef, CreateConnectionInput, ConnectionRefreshError, ConnectionRefreshInput, executor.connections (get/list/remove/accessToken), plugin.connectionProviders hook. - core/api: /scopes/:id/connections endpoints + handler group, secrets list filter that hides connection-owned rows. - storage: new `connection` table (drizzle migrations for cloud + local). - plugins (openapi, google-discovery, mcp): migrated onto ctx.connections, widened stored-auth schemas to carry the full OAuth config so re-sign-in is reproducible without the prior Connection row existing. - react: `/connections` page with removal; signIn slot on SourcePlugin; per-plugin SignInButton mounted on the source detail top bar with dynamic "Sign in" / "Reconnect" copy driven by `connectionsAtom`. - openapi store: tolerant oauth2 decode at the row boundary so legacy rows written before the schema widening surface without OAuth attached instead of crashing the source list. - oauth2 plugin: removes the dead standalone refresh module now that refresh lives behind ctx.connections.

Rhys Sullivan committed 6 months ago

Commit bd7faba1 parent 7909ef473 files · +6087 −2239Browse files

Showing the changes to packages/plugins/openapi/src/api (2 of 73 files). Show all

Unified · Split

2 files
  1. packages/plugins/openapi/src/api/group.ts +6 −6
  2. packages/plugins/openapi/src/api/handlers.ts +1 −3
packages/plugins/openapi/src/api/group.ts+6−6
@@ -40,6 +40,9 @@ const UpdateSourcePayload = Schema.Struct({ · 39 unmodified lines
4040 name: Schema.optional(Schema.String),
4141 baseUrl: Schema.optional(Schema.String),
4242 headers: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.Unknown })),
43+ // Set after a successful re-authenticate to rewrite the source's
44+ // OAuth2Auth pointer to the freshly minted connection.
45+ oauth2: Schema.optional(OAuth2Auth),
4346 });
4447
4548 const UpdateSourceResponse = Schema.Struct({
@@ -60,10 +63,9 @@ const AddSpecResponse = Schema.Struct({ · 14 unmodified lines
6063 // ---------------------------------------------------------------------------
6164
6265 // Shared identity fields for both OAuth2 flows. `tokenScope` names which
63−// executor scope will own the minted tokens (typically the per-user scope).
64−// The token secret ids are pre-decided so the source's stored `OAuth2Auth`
65−// can reference the same ids across every user — per-user values shadow
66−// org-level fallbacks via secret fall-through on read.
66+// executor scope will own the resulting Connection (typically the per-user
67+// scope). Token secret ids are minted by the SDK from the Connection id —
68+// callers never pick them.
6769 const StartOAuthIdentityFields = {
6870 displayName: Schema.String,
6971 securitySchemeName: Schema.String,
@@ -71,7 +73,6 @@ const StartOAuthIdentityFields = { · 1 unmodified line
7173 clientIdSecretId: Schema.String,
7274 scopes: Schema.Array(Schema.String),
7375 tokenScope: Schema.optional(ScopeId),
74− accessTokenSecretId: Schema.String,
7576 } as const;
7677
7778 const StartOAuthPayload = Schema.Union(
@@ -81,7 +82,6 @@ const StartOAuthPayload = Schema.Union( · 3 unmodified lines
8182 authorizationUrl: Schema.String,
8283 redirectUrl: Schema.String,
8384 clientSecretSecretId: Schema.optional(Schema.NullOr(Schema.String)),
84− refreshTokenSecretId: Schema.optional(Schema.NullOr(Schema.String)),
8585 }),
8686 // RFC 6749 §4.4 — no user-interactive step, no session, no popup. The
8787 // plugin exchanges tokens inline and returns a completed auth. The
packages/plugins/openapi/src/api/handlers.ts+1−3
@@ -91,6 +91,7 @@ export const OpenApiHandlers = HttpApiBuilder.group(ExecutorApiWithOpenApi, "ope · 90 unmodified lines
9191 name: payload.name,
9292 baseUrl: payload.baseUrl,
9393 headers: payload.headers as Record<string, HeaderValue> | undefined,
94+ oauth2: payload.oauth2,
9495 } as OpenApiUpdateSourceInput);
9596 return { updated: true };
9697 })),
@@ -112,7 +113,6 @@ export const OpenApiHandlers = HttpApiBuilder.group(ExecutorApiWithOpenApi, "ope · 15 unmodified lines
112113 clientSecretSecretId: payload.clientSecretSecretId,
113114 scopes: [...payload.scopes],
114115 tokenScope,
115− accessTokenSecretId: payload.accessTokenSecretId,
116116 });
117117 }
118118 return yield* ext.startOAuth({
@@ -126,8 +126,6 @@ export const OpenApiHandlers = HttpApiBuilder.group(ExecutorApiWithOpenApi, "ope · 7 unmodified lines
126126 clientSecretSecretId: payload.clientSecretSecretId ?? null,
127127 scopes: [...payload.scopes],
128128 tokenScope,
129− accessTokenSecretId: payload.accessTokenSecretId,
130− refreshTokenSecretId: payload.refreshTokenSecretId ?? null,
131129 });
132130 })),
133131 )

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t