Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<string>(securityScheme, OpenAPIExtension.BEARER_FORMAT);
const headerNames = getExtension<HeaderSecuritySchemeNames>(
securityScheme,
FernOpenAPIExtension.FERN_HEADER_AUTH
);
return SecurityScheme.header({
headerName: securityScheme.name,
prefix: bearerFormat != null ? "Bearer" : headerNames?.prefix,
headerVariableName:
headerNames?.name ?? getExtension<string>(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<SecuritySchemeNames>(securityScheme, FernOpenAPIExtension.FERN_BEARER_TOKEN);
return SecurityScheme.bearer({
tokenVariableName:
bearerNames?.name ??
getExtension<string>(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<string>(securityScheme, OpenAPIExtension.BEARER_FORMAT);
const headerNames = getExtension<HeaderSecuritySchemeNames>(
securityScheme,
FernOpenAPIExtension.FERN_HEADER_AUTH
);
return SecurityScheme.header({
headerName: securityScheme.name,
prefix: bearerFormat != null ? "Bearer" : headerNames?.prefix,
headerVariableName:
headerNames?.name ??
getExtension<string>(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}`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to dig in at some point about how we surface CLI errors, I feel like there's a juicier way to do this and make it nicely formatted. But this is okay for now I think

}
} else if (securityScheme.type === "http") {
if (securityScheme.scheme?.toLowerCase() === "bearer") {
// ^ case insensitivity for securityScheme.scheme required in OAS
const bearerNames = getExtension<SecuritySchemeNames>(
securityScheme,
FernOpenAPIExtension.FERN_BEARER_TOKEN
);
return SecurityScheme.bearer({
tokenVariableName:
bearerNames?.name ??
getExtension<string>(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,
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@ export function generateIr({

const securitySchemes: Record<string, SecurityScheme> = 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(
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/cli/versions.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down