Showing the changes to plugins/openapi (4 of 15 files). Show all
Unified · Split
| @@ -38,7 +38,7 @@ import { · 37 unmodified lines | |||
| 38 | type SourceToolExplorerSearch, | 38 | type SourceToolExplorerSearch, |
| 39 | useSourcePluginNavigation, | 39 | useSourcePluginNavigation, |
| 40 | useSourcePluginSearch, | 40 | useSourcePluginSearch, |
| 41 | } from "@executor/react/source-plugins"; | 41 | } from "@executor/react/plugins"; |
| 42 | import { | 42 | import { |
| 43 | openApiHttpApiExtension, | 43 | openApiHttpApiExtension, |
| 44 | } from "@executor/plugin-openapi-http"; | 44 | } from "@executor/plugin-openapi-http"; |
| @@ -169,6 +169,31 @@ const inputFromConfig = ( · 124 unmodified lines | |||
| 169 | auth: config.auth, | 169 | auth: config.auth, |
| 170 | }); | 170 | }); |
| 171 | 171 | ||
| 172 | const secretValue = (input: OpenApiConnectInput["auth"]): string => | ||
| 173 | input.kind === "bearer" | ||
| 174 | ? JSON.stringify(input.tokenSecretRef) | ||
| 175 | : ""; | ||
| 176 | |||
| 177 | const authFromSecretValue = ( | ||
| 178 | authKind: OpenApiConnectInput["auth"]["kind"], | ||
| 179 | value: string, | ||
| 180 | ): OpenApiConnectInput["auth"] => { | ||
| 181 | if (authKind === "none") { | ||
| 182 | return { | ||
| 183 | kind: "none", | ||
| 184 | }; | ||
| 185 | } | ||
| 186 | |||
| 187 | if (!value) { | ||
| 188 | throw new Error("Select a secret for bearer auth."); | ||
| 189 | } | ||
| 190 | |||
| 191 | return { | ||
| 192 | kind: "bearer", | ||
| 193 | tokenSecretRef: JSON.parse(value) as OpenApiConnectInput["auth"] & { tokenSecretRef: never }["tokenSecretRef"], | ||
| 194 | }; | ||
| 195 | }; | ||
| 196 | |||
| 172 | const useAvailableSecrets = ( | 197 | const useAvailableSecrets = ( |
| 173 | openApiHttpClient: ReturnType<typeof getOpenApiHttpClient>, | 198 | openApiHttpClient: ReturnType<typeof getOpenApiHttpClient>, |
| 174 | ) => { | 199 | ) => { |
| @@ -205,9 +230,7 @@ function OpenApiSourceForm(props: { · 30 unmodified lines | |||
| 205 | props.initialValue.auth.kind, | 230 | props.initialValue.auth.kind, |
| 206 | ); | 231 | ); |
| 207 | const [tokenSecretRef, setTokenSecretRef] = useState( | 232 | const [tokenSecretRef, setTokenSecretRef] = useState( |
| 208 | props.initialValue.auth.kind === "bearer" | 233 | secretValue(props.initialValue.auth), |
| 209 | ? props.initialValue.auth.tokenSecretRef | ||
| 210 | : "", | ||
| 211 | ); | 234 | ); |
| 212 | const [error, setError] = useState<string | null>(null); | 235 | const [error, setError] = useState<string | null>(null); |
| 213 | const [preview, setPreview] = useState<OpenApiPreviewResponse | null>(null); | 236 | const [preview, setPreview] = useState<OpenApiPreviewResponse | null>(null); |
| @@ -309,19 +332,12 @@ function OpenApiSourceForm(props: { · 95 unmodified lines | |||
| 309 | } | 332 | } |
| 310 | 333 | ||
| 311 | try { | 334 | try { |
| 335 | const auth = authFromSecretValue(authKind, tokenSecretRef); | ||
| 312 | await submitMutation.mutateAsync({ | 336 | await submitMutation.mutateAsync({ |
| 313 | name: trimmedName, | 337 | name: trimmedName, |
| 314 | specUrl: trimmedSpecUrl, | 338 | specUrl: trimmedSpecUrl, |
| 315 | baseUrl: trimmedBaseUrl || null, | 339 | baseUrl: trimmedBaseUrl || null, |
| 316 | auth: | 340 | auth, |
| 317 | authKind === "bearer" | ||
| 318 | ? { | ||
| 319 | kind: "bearer", | ||
| 320 | tokenSecretRef: tokenSecretRef.trim(), | ||
| 321 | } | ||
| 322 | : { | ||
| 323 | kind: "none", | ||
| 324 | }, | ||
| 325 | }); | 341 | }); |
| 326 | } catch (cause) { | 342 | } catch (cause) { |
| 327 | setError(cause instanceof Error ? cause.message : "Failed saving source."); | 343 | setError(cause instanceof Error ? cause.message : "Failed saving source."); |
| @@ -398,7 +414,13 @@ function OpenApiSourceForm(props: { · 70 unmodified lines | |||
| 398 | > | 414 | > |
| 399 | <option value="">Select a secret</option> | 415 | <option value="">Select a secret</option> |
| 400 | {availableSecrets.map((secret) => ( | 416 | {availableSecrets.map((secret) => ( |
| 401 | <option key={secret.id} value={secret.id}> | 417 | <option |
| 418 | key={`${secret.providerId}:${secret.id}`} | ||
| 419 | value={JSON.stringify({ | ||
| 420 | providerId: secret.providerId, | ||
| 421 | handle: secret.id, | ||
| 422 | })} | ||
| 423 | > | ||
| 402 | {secret.name || secret.id} | 424 | {secret.name || secret.id} |
| 403 | </option> | 425 | </option> |
| 404 | ))} | 426 | ))} |
| @@ -13,6 +13,9 @@ import type { · 12 unmodified lines | |||
| 13 | ExecutorSourceConnector, | 13 | ExecutorSourceConnector, |
| 14 | SourcePluginRuntime, | 14 | SourcePluginRuntime, |
| 15 | } from "@executor/platform-sdk/plugins"; | 15 | } from "@executor/platform-sdk/plugins"; |
| 16 | import { | ||
| 17 | SecretMaterialResolverService, | ||
| 18 | } from "@executor/platform-sdk/runtime"; | ||
| 16 | import { | 19 | import { |
| 17 | OpenApiConnectionAuthSchema, | 20 | OpenApiConnectionAuthSchema, |
| 18 | deriveOpenApiNamespace, | 21 | deriveOpenApiNamespace, |
| @@ -64,12 +67,6 @@ export type OpenApiSourceStorage = { · 45 unmodified lines | |||
| 64 | }) => Effect.Effect<void, Error, never>; | 67 | }) => Effect.Effect<void, Error, never>; |
| 65 | }; | 68 | }; |
| 66 | 69 | ||
| 67 | export type OpenApiSecrets = { | ||
| 68 | resolve: (input: { | ||
| 69 | ref: string; | ||
| 70 | }) => Effect.Effect<string, Error, never>; | ||
| 71 | }; | ||
| 72 | |||
| 73 | export type OpenApiSdk = { | 70 | export type OpenApiSdk = { |
| 74 | previewDocument: ( | 71 | previewDocument: ( |
| 75 | input: OpenApiPreviewRequest, | 72 | input: OpenApiPreviewRequest, |
| @@ -93,7 +90,6 @@ export type OpenApiSdk = { · 17 unmodified lines | |||
| 93 | 90 | ||
| 94 | export type OpenApiSdkPluginOptions = { | 91 | export type OpenApiSdkPluginOptions = { |
| 95 | storage: OpenApiSourceStorage; | 92 | storage: OpenApiSourceStorage; |
| 96 | secrets?: OpenApiSecrets; | ||
| 97 | }; | 93 | }; |
| 98 | 94 | ||
| 99 | const OpenApiExecutorAddInputSchema = Schema.Struct({ | 95 | const OpenApiExecutorAddInputSchema = Schema.Struct({ |
| @@ -414,22 +410,18 @@ const decodeResponseBody = async (response: Response): Promise<unknown> => { · 314 unmodified lines | |||
| 414 | }; | 410 | }; |
| 415 | 411 | ||
| 416 | const resolveBearerToken = ( | 412 | const resolveBearerToken = ( |
| 417 | options: OpenApiSdkPluginOptions, | ||
| 418 | stored: OpenApiStoredSourceData, | 413 | stored: OpenApiStoredSourceData, |
| 419 | ): Effect.Effect<string | null, Error, never> => { | 414 | ): Effect.Effect<string | null, Error, any> => { |
| 420 | if (stored.auth.kind === "none") { | 415 | const { auth } = stored; |
| 416 | if (auth.kind === "none") { | ||
| 421 | return Effect.succeed(null); | 417 | return Effect.succeed(null); |
| 422 | } | 418 | } |
| 423 | 419 | ||
| 424 | if (!options.secrets) { | 420 | return Effect.flatMap(SecretMaterialResolverService, (resolveSecretMaterial) => |
| 425 | return Effect.fail( | 421 | resolveSecretMaterial({ |
| 426 | new Error("OpenAPI bearer auth is configured, but no secret resolver is available."), | 422 | ref: auth.tokenSecretRef, |
| 427 | ); | 423 | }).pipe(Effect.map((token) => token.trim())) |
| 428 | } | 424 | ); |
| 429 | |||
| 430 | return options.secrets.resolve({ | ||
| 431 | ref: stored.auth.tokenSecretRef, | ||
| 432 | }).pipe(Effect.map((token) => token.trim())); | ||
| 433 | }; | 425 | }; |
| 434 | 426 | ||
| 435 | const openApiDocumentHeaders = (input: { | 427 | const openApiDocumentHeaders = (input: { |
| @@ -550,7 +542,7 @@ const createOpenApiSourceRuntime = ( · 114 unmodified lines | |||
| 550 | }); | 542 | }); |
| 551 | } | 543 | } |
| 552 | 544 | ||
| 553 | const bearerToken = yield* resolveBearerToken(options, stored); | 545 | const bearerToken = yield* resolveBearerToken(stored); |
| 554 | const fetched = yield* fetchOpenApiDocument({ | 546 | const fetched = yield* fetchOpenApiDocument({ |
| 555 | stored, | 547 | stored, |
| 556 | bearerToken, | 548 | bearerToken, |
| @@ -686,7 +678,7 @@ const createOpenApiSourceRuntime = ( · 129 unmodified lines | |||
| 686 | } | 678 | } |
| 687 | } | 679 | } |
| 688 | 680 | ||
| 689 | const bearerToken = yield* resolveBearerToken(options, stored); | 681 | const bearerToken = yield* resolveBearerToken(stored); |
| 690 | if (bearerToken && bearerToken.length > 0) { | 682 | if (bearerToken && bearerToken.length > 0) { |
| 691 | headers.authorization = `Bearer ${bearerToken}`; | 683 | headers.authorization = `Bearer ${bearerToken}`; |
| 692 | } | 684 | } |
| @@ -3,8 +3,9 @@ import * as Schema from "effect/Schema"; · 2 unmodified lines | |||
| 3 | import YAML from "yaml"; | 3 | import YAML from "yaml"; |
| 4 | 4 | ||
| 5 | import { | 5 | import { |
| 6 | SecretRefSchema, | ||
| 6 | StringMapSchema, | 7 | StringMapSchema, |
| 7 | } from "@executor/source-core"; | 8 | } from "@executor/platform-sdk/schema"; |
| 8 | 9 | ||
| 9 | export const OpenApiConnectionAuthSchema = Schema.Union( | 10 | export const OpenApiConnectionAuthSchema = Schema.Union( |
| 10 | Schema.Struct({ | 11 | Schema.Struct({ |
| @@ -12,7 +13,7 @@ export const OpenApiConnectionAuthSchema = Schema.Union( · 1 unmodified line | |||
| 12 | }), | 13 | }), |
| 13 | Schema.Struct({ | 14 | Schema.Struct({ |
| 14 | kind: Schema.Literal("bearer"), | 15 | kind: Schema.Literal("bearer"), |
| 15 | tokenSecretRef: Schema.String, | 16 | tokenSecretRef: SecretRefSchema, |
| 16 | }), | 17 | }), |
| 17 | ); | 18 | ); |
| 18 | 19 | ||
| @@ -12,6 +12,7 @@ · 11 unmodified lines | |||
| 12 | "typecheck": "bunx tsc --noEmit -p tsconfig.json" | 12 | "typecheck": "bunx tsc --noEmit -p tsconfig.json" |
| 13 | }, | 13 | }, |
| 14 | "dependencies": { | 14 | "dependencies": { |
| 15 | "@executor/platform-sdk": "workspace:*", | ||
| 15 | "@executor/source-core": "workspace:*", | 16 | "@executor/source-core": "workspace:*", |
| 16 | "effect": "catalog:", | 17 | "effect": "catalog:", |
| 17 | "yaml": "^2.8.1" | 18 | "yaml": "^2.8.1" |