diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/convertSecurityScheme.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/convertSecurityScheme.ts index cb5ec225599..6d114bac3c6 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/convertSecurityScheme.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/converters/convertSecurityScheme.ts @@ -1,6 +1,6 @@ +import { assertNever } from "@fern-api/core-utils"; import { EnumSchema, SecurityScheme, Source } from "@fern-api/openapi-ir"; import { OpenAPIV3 } from "openapi-types"; - import { getExtension } from "../../../getExtension"; import { convertEnum } from "../../../schema/convertEnum"; import { convertSchemaWithExampleToSchema } from "../../../schema/utils/convertSchemaWithExampleToSchema"; @@ -16,52 +16,72 @@ import { export function convertSecurityScheme( securityScheme: OpenAPIV3.SecuritySchemeObject | OpenAPIV3.ReferenceObject, - source: Source -): SecurityScheme | undefined { + source: Source, + key: string +): SecurityScheme { if (isReferenceObject(securityScheme)) { throw new Error(`Converting referenced security schemes is unsupported: ${JSON.stringify(securityScheme)}`); } - return convertSecuritySchemeHelper(securityScheme, source); + return convertSecuritySchemeHelper(securityScheme, source, key); } function convertSecuritySchemeHelper( securityScheme: OpenAPIV3.SecuritySchemeObject, - source: Source -): SecurityScheme | undefined { - if (securityScheme.type === "apiKey" && securityScheme.in === "header") { - const bearerFormat = getExtension(securityScheme, OpenAPIExtension.BEARER_FORMAT); - const headerNames = getExtension( - securityScheme, - FernOpenAPIExtension.FERN_HEADER_AUTH - ); - return SecurityScheme.header({ - headerName: securityScheme.name, - prefix: bearerFormat != null ? "Bearer" : headerNames?.prefix, - headerVariableName: - headerNames?.name ?? getExtension(securityScheme, FernOpenAPIExtension.HEADER_VARIABLE_NAME), - headerEnvVar: headerNames?.env - }); - } else if (securityScheme.type === "http" && securityScheme.scheme?.toLowerCase() === "bearer") { - // ^ case insensitivity for securityScheme.scheme required in OAS - const bearerNames = getExtension(securityScheme, FernOpenAPIExtension.FERN_BEARER_TOKEN); - return SecurityScheme.bearer({ - tokenVariableName: - bearerNames?.name ?? - getExtension(securityScheme, FernOpenAPIExtension.BEARER_TOKEN_VARIABLE_NAME), - tokenEnvVar: bearerNames?.env - }); - } else if (securityScheme.type === "http" && securityScheme.scheme?.toLowerCase() === "basic") { - // ^ case insensitivity for securityScheme.scheme required in OAS - const basicSecuritySchemeNamingAndEnvvar = getBasicSecuritySchemeNameAndEnvvar(securityScheme); - const basicSecuritySchemeNaming = getBasicSecuritySchemeNames(securityScheme); - return SecurityScheme.basic({ - usernameVariableName: - basicSecuritySchemeNamingAndEnvvar?.username?.name ?? basicSecuritySchemeNaming.usernameVariable, - usernameEnvVar: basicSecuritySchemeNamingAndEnvvar?.username?.env, - passwordVariableName: - basicSecuritySchemeNamingAndEnvvar?.password?.name ?? basicSecuritySchemeNaming.passwordVariable, - passwordEnvVar: basicSecuritySchemeNamingAndEnvvar?.password?.env - }); + source: Source, + key: string +): SecurityScheme { + if (securityScheme.type === "apiKey") { + if (securityScheme.in === "header") { + const bearerFormat = getExtension(securityScheme, OpenAPIExtension.BEARER_FORMAT); + const headerNames = getExtension( + securityScheme, + FernOpenAPIExtension.FERN_HEADER_AUTH + ); + return SecurityScheme.header({ + headerName: securityScheme.name, + prefix: bearerFormat != null ? "Bearer" : headerNames?.prefix, + headerVariableName: + headerNames?.name ?? + getExtension(securityScheme, FernOpenAPIExtension.HEADER_VARIABLE_NAME), + headerEnvVar: headerNames?.env + }); + } else if (securityScheme.in === "query") { + // Handle API key in query parameter + // Optionally, you could add support for FernOpenAPIExtension for query param naming/envvar in the future + return SecurityScheme.query({ + queryParameterName: securityScheme.name + }); + } else { + throw new Error(`Unsupported API key location ${key}: ${securityScheme.in}`); + } + } else if (securityScheme.type === "http") { + if (securityScheme.scheme?.toLowerCase() === "bearer") { + // ^ case insensitivity for securityScheme.scheme required in OAS + const bearerNames = getExtension( + securityScheme, + FernOpenAPIExtension.FERN_BEARER_TOKEN + ); + return SecurityScheme.bearer({ + tokenVariableName: + bearerNames?.name ?? + getExtension(securityScheme, FernOpenAPIExtension.BEARER_TOKEN_VARIABLE_NAME), + tokenEnvVar: bearerNames?.env + }); + } else if (securityScheme.scheme?.toLowerCase() === "basic") { + // ^ case insensitivity for securityScheme.scheme required in OAS + const basicSecuritySchemeNamingAndEnvvar = getBasicSecuritySchemeNameAndEnvvar(securityScheme); + const basicSecuritySchemeNaming = getBasicSecuritySchemeNames(securityScheme); + return SecurityScheme.basic({ + usernameVariableName: + basicSecuritySchemeNamingAndEnvvar?.username?.name ?? basicSecuritySchemeNaming.usernameVariable, + usernameEnvVar: basicSecuritySchemeNamingAndEnvvar?.username?.env, + passwordVariableName: + basicSecuritySchemeNamingAndEnvvar?.password?.name ?? basicSecuritySchemeNaming.passwordVariable, + passwordEnvVar: basicSecuritySchemeNamingAndEnvvar?.password?.env + }); + } else { + throw new Error(`Unsupported HTTP scheme - ${key}: ${securityScheme.scheme}`); + } } else if (securityScheme.type === "openIdConnect") { return SecurityScheme.bearer({ tokenVariableName: undefined, @@ -71,8 +91,9 @@ function convertSecuritySchemeHelper( return SecurityScheme.oauth({ scopesEnum: getScopes(securityScheme, source) }); + } else { + assertNever(securityScheme); } - return undefined; } function getScopes(oauthSecurityScheme: OpenAPIV3.OAuth2SecurityScheme, source: Source): EnumSchema | undefined { diff --git a/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/generateIr.ts b/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/generateIr.ts index e509655f0f0..ad1aeeffcbc 100644 --- a/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/generateIr.ts +++ b/packages/cli/api-importers/openapi/openapi-ir-parser/src/openapi/v3/generateIr.ts @@ -67,11 +67,7 @@ export function generateIr({ const securitySchemes: Record = Object.fromEntries( Object.entries(openApi.components?.securitySchemes ?? {}).map(([key, securityScheme]) => { - const convertedSecurityScheme = convertSecurityScheme(securityScheme, source); - if (convertedSecurityScheme == null) { - return []; - } - return [key, convertSecurityScheme(securityScheme, source)]; + return [key, convertSecurityScheme(securityScheme, source, key)]; }) ); const authHeaders = new Set( diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 38da1ab28e1..05e6fe741cd 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,4 +1,13 @@ # yaml-language-server: $schema=../../../fern-versions-yml.schema.json +- changelogEntry: + - summary: | + - added support for API keys in query params. + - improved error message around converting security schemas + type: chore + irVersion: 59 + createdAt: "2025-08-20" + version: 0.66.20 + - changelogEntry: - summary: Handle multipart/mixed content types in endpoint responses. type: chore