History

Fix OpenAPI OAuth config compatibility (#848)

Rhys Sullivan committed 5 months ago

Commit 85e677d1 parent 0fd4d114 files · +78 −3Browse files

Unified · Split

4 files
  1. apps/cloud/drizzle/0018_repair_openapi_oauth_authorization_url.sql +9 −0
  2. apps/cloud/drizzle/meta/_journal.json +7 −0
  3. packages/plugins/http-source/src/sdk/types.ts +5 −2
  4. packages/plugins/openapi/src/sdk/client-credentials-oauth.test.ts +57 −1
apps/cloud/drizzle/0018_repair_openapi_oauth_authorization_url.sql+9−0
@@ -0,0 +1,9 @@
1+UPDATE "plugin_storage"
2+SET
3+ "data" = jsonb_set("data"::jsonb, '{config,oauth2,authorizationUrl}', 'null'::jsonb, true)::json,
4+ "updated_at" = now()
5+WHERE
6+ "plugin_id" = 'openapi'
7+ AND "collection" = 'source'
8+ AND "data" #> '{config,oauth2}' IS NOT NULL
9+ AND NOT ("data"::jsonb #> '{config,oauth2}' ? 'authorizationUrl');
apps/cloud/drizzle/meta/_journal.json+7−0
@@ -127,6 +127,13 @@ · 126 unmodified lines
127127 "when": 1779087600000,
128128 "tag": "0017_plugin_storage_sources",
129129 "breakpoints": true
130+ },
131+ {
132+ "idx": 18,
133+ "version": "7",
134+ "when": 1779199200000,
135+ "tag": "0018_repair_openapi_oauth_authorization_url",
136+ "breakpoints": true
130137 }
131138 ]
132139 }
packages/plugins/http-source/src/sdk/types.ts+5−2
@@ -1,4 +1,4 @@
1−import { Schema } from "effect";
1+import { Effect, Schema } from "effect";
22
33 export const HttpCredentialInput = Schema.Union([
44 Schema.String,
@@ -37,7 +37,10 @@ export const OAuth2SourceConfig = Schema.Struct({ · 32 unmodified lines
3737 securitySchemeName: Schema.String,
3838 flow: OAuth2Flow,
3939 tokenUrl: Schema.String,
40− authorizationUrl: Schema.NullOr(Schema.String),
40+ authorizationUrl: Schema.NullOr(Schema.String).pipe(
41+ Schema.optional,
42+ Schema.withDecodingDefault(Effect.succeed(null)),
43+ ),
4144 issuerUrl: Schema.optional(Schema.NullOr(Schema.String)),
4245 clientIdSlot: Schema.String,
4346 clientSecretSlot: Schema.NullOr(Schema.String),
packages/plugins/openapi/src/sdk/client-credentials-oauth.test.ts+57−1
@@ -108,7 +108,6 @@ describe("OpenAPI client_credentials OAuth", () => { · 107 unmodified lines
108108 openApiPlugin({ httpClientLayer: clientLayer }),
109109 memorySecretsPlugin(),
110110 ] as const;
111− const config = makeTestConfig({ plugins });
112111
113112 const now = new Date();
114113 const orgScope = Scope.make({
@@ -121,6 +120,7 @@ describe("OpenAPI client_credentials OAuth", () => { · 6 unmodified lines
121120 name: "alice",
122121 createdAt: now,
123122 });
123+ const config = makeTestConfig({ plugins, scopes: [userScope, orgScope] });
124124
125125 const adminExec = yield* createExecutor({
126126 ...config,
@@ -214,6 +214,62 @@ describe("OpenAPI client_credentials OAuth", () => { · 87 unmodified lines
214214 namespace: "petstore",
215215 oauth2,
216216 });
217+ const storedSourceRow = yield* Effect.promise(() =>
218+ config.db.findFirst("plugin_storage", {
219+ where: (b) =>
220+ b.and(
221+ b("scope_id", "=", String(userScope.id)),
222+ b("plugin_id", "=", "openapi"),
223+ b("collection", "=", "source"),
224+ b("key", "=", "petstore"),
225+ ),
226+ }),
227+ );
228+ const storedData = storedSourceRow?.data as
229+ | {
230+ readonly config?: Record<string, unknown>;
231+ }
232+ | undefined;
233+ const storedOAuth2 = storedData?.config?.oauth2;
234+ if (
235+ !storedSourceRow ||
236+ !storedData?.config ||
237+ typeof storedOAuth2 !== "object" ||
238+ storedOAuth2 === null ||
239+ Array.isArray(storedOAuth2)
240+ ) {
241+ return yield* new OpenApiClientCredentialsTestSetupError({
242+ message: "Expected stored OpenAPI source OAuth config",
243+ });
244+ }
245+ const { authorizationUrl: _authorizationUrl, ...oauth2WithoutAuthorizationUrl } =
246+ storedOAuth2 as Record<string, unknown>;
247+ yield* Effect.promise(() =>
248+ config.db.updateMany("plugin_storage", {
249+ where: (b) =>
250+ b.and(
251+ b("scope_id", "=", String(userScope.id)),
252+ b("plugin_id", "=", "openapi"),
253+ b("collection", "=", "source"),
254+ b("key", "=", "petstore"),
255+ ),
256+ set: {
257+ data: {
258+ ...storedData,
259+ config: {
260+ ...storedData.config,
261+ oauth2: oauth2WithoutAuthorizationUrl,
262+ },
263+ },
264+ },
265+ }),
266+ );
267+ const sourceAfterLegacyShape = yield* userExec.openapi.getSource(
268+ "petstore",
269+ String(userScope.id),
270+ );
271+ expect(sourceAfterLegacyShape?.config.oauth2?.authorizationUrl).toBeNull();
272+
217273 yield* userExec.sources.setBinding(
218274 SetSourceCredentialBindingInput.make({
219275 source: { id: "petstore", scope: userScope.id },