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 @@
1UPDATE "plugin_storage"
2SET
3 "data" = jsonb_set("data"::jsonb, '{config,oauth2,authorizationUrl}', 'null'::jsonb, true)::json,
4 "updated_at" = now()
5WHERE
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
127 "when": 1779087600000,127 "when": 1779087600000,
128 "tag": "0017_plugin_storage_sources",128 "tag": "0017_plugin_storage_sources",
129 "breakpoints": true129 "breakpoints": true
130 },
131 {
132 "idx": 18,
133 "version": "7",
134 "when": 1779199200000,
135 "tag": "0018_repair_openapi_oauth_authorization_url",
136 "breakpoints": true
130 }137 }
131 ]138 ]
132}139}
packages/plugins/http-source/src/sdk/types.ts+5−2
@@ -1,4 +1,4 @@
1import { Schema } from "effect";1import { Effect, Schema } from "effect";
2 2
3export const HttpCredentialInput = Schema.Union([3export const HttpCredentialInput = Schema.Union([
4 Schema.String,4 Schema.String,
@@ -37,7 +37,10 @@ export const OAuth2SourceConfig = Schema.Struct({ · 32 unmodified lines
37 securitySchemeName: Schema.String,37 securitySchemeName: Schema.String,
38 flow: OAuth2Flow,38 flow: OAuth2Flow,
39 tokenUrl: Schema.String,39 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 ),
41 issuerUrl: Schema.optional(Schema.NullOr(Schema.String)),44 issuerUrl: Schema.optional(Schema.NullOr(Schema.String)),
42 clientIdSlot: Schema.String,45 clientIdSlot: Schema.String,
43 clientSecretSlot: Schema.NullOr(Schema.String),46 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
108 openApiPlugin({ httpClientLayer: clientLayer }),108 openApiPlugin({ httpClientLayer: clientLayer }),
109 memorySecretsPlugin(),109 memorySecretsPlugin(),
110 ] as const;110 ] as const;
111 const config = makeTestConfig({ plugins });
112 111
113 const now = new Date();112 const now = new Date();
114 const orgScope = Scope.make({113 const orgScope = Scope.make({
@@ -121,6 +120,7 @@ describe("OpenAPI client_credentials OAuth", () => { · 6 unmodified lines
121 name: "alice",120 name: "alice",
122 createdAt: now,121 createdAt: now,
123 });122 });
123 const config = makeTestConfig({ plugins, scopes: [userScope, orgScope] });
124 124
125 const adminExec = yield* createExecutor({125 const adminExec = yield* createExecutor({
126 ...config,126 ...config,
@@ -214,6 +214,62 @@ describe("OpenAPI client_credentials OAuth", () => { · 87 unmodified lines
214 namespace: "petstore",214 namespace: "petstore",
215 oauth2,215 oauth2,
216 });216 });
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
217 yield* userExec.sources.setBinding(273 yield* userExec.sources.setBinding(
218 SetSourceCredentialBindingInput.make({274 SetSourceCredentialBindingInput.make({
219 source: { id: "petstore", scope: userScope.id },275 source: { id: "petstore", scope: userScope.id },