mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 05:48:57 +00:00
Fix OpenAPISecurities and code sample not using operation security requirements (#3671)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@gitbook/react-openapi": minor
|
||||
---
|
||||
|
||||
Fix OpenAPISecurities and code sample not using operation security requirements
|
||||
@@ -11,7 +11,7 @@ import { generateMediaTypeExamples, generateSchemaExample } from './generateSche
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
import type { OpenAPIOperationData } from './types';
|
||||
import { getDefaultServerURL } from './util/server';
|
||||
import { checkIsReference } from './utils';
|
||||
import { checkIsReference, extractOperationSecurityInfo } from './utils';
|
||||
|
||||
const CUSTOM_CODE_SAMPLES_KEYS = ['x-custom-examples', 'x-code-samples', 'x-codeSamples'] as const;
|
||||
|
||||
@@ -106,7 +106,10 @@ function generateCodeSamples(props: {
|
||||
(searchParams.size ? `?${searchParams.toString()}` : '');
|
||||
|
||||
const genericHeaders = {
|
||||
...getSecurityHeaders(data.securities),
|
||||
...getSecurityHeaders({
|
||||
securityRequirement: data.operation.security,
|
||||
securities: data.securities,
|
||||
}),
|
||||
...headersObject,
|
||||
};
|
||||
|
||||
@@ -278,51 +281,66 @@ function getCustomCodeSamples(props: {
|
||||
return customCodeSamples;
|
||||
}
|
||||
|
||||
function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
|
||||
function getSecurityHeaders(args: {
|
||||
securityRequirement: OpenAPIV3.OperationObject['security'];
|
||||
securities: OpenAPIOperationData['securities'];
|
||||
}): {
|
||||
[key: string]: string;
|
||||
} {
|
||||
const security = securities[0];
|
||||
const { securityRequirement, securities } = args;
|
||||
const operationSecurityInfo = extractOperationSecurityInfo({ securityRequirement, securities });
|
||||
|
||||
if (!security) {
|
||||
if (operationSecurityInfo.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (security[1].type) {
|
||||
case 'http': {
|
||||
let scheme = security[1].scheme;
|
||||
let format = security[1].bearerFormat ?? 'YOUR_SECRET_TOKEN';
|
||||
const selectedSecurity = operationSecurityInfo.at(0);
|
||||
|
||||
if (scheme?.includes('bearer')) {
|
||||
scheme = 'Bearer';
|
||||
} else if (scheme?.includes('basic')) {
|
||||
scheme = 'Basic';
|
||||
format = 'username:password';
|
||||
} else if (scheme?.includes('token')) {
|
||||
scheme = 'Token';
|
||||
if (!selectedSecurity) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const headers: { [key: string]: string } = {};
|
||||
|
||||
for (const security of selectedSecurity.schemes) {
|
||||
switch (security.type) {
|
||||
case 'http': {
|
||||
let scheme = security.scheme;
|
||||
let format = security.bearerFormat ?? 'YOUR_SECRET_TOKEN';
|
||||
|
||||
if (scheme?.includes('bearer')) {
|
||||
scheme = 'Bearer';
|
||||
} else if (scheme?.includes('basic')) {
|
||||
scheme = 'Basic';
|
||||
format = 'username:password';
|
||||
} else if (scheme?.includes('token')) {
|
||||
scheme = 'Token';
|
||||
}
|
||||
|
||||
headers.Authorization = `${scheme} ${format}`;
|
||||
break;
|
||||
}
|
||||
case 'apiKey': {
|
||||
if (security.in !== 'header') {
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `${scheme} ${format}`,
|
||||
};
|
||||
}
|
||||
case 'apiKey': {
|
||||
if (security[1].in !== 'header') return {};
|
||||
const name = security.name ?? 'Authorization';
|
||||
headers[name] = 'YOUR_API_KEY';
|
||||
|
||||
const name = security[1].name ?? 'Authorization';
|
||||
|
||||
return {
|
||||
[name]: 'YOUR_API_KEY',
|
||||
};
|
||||
}
|
||||
case 'oauth2': {
|
||||
return {
|
||||
Authorization: 'Bearer YOUR_OAUTH2_TOKEN',
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return {};
|
||||
break;
|
||||
}
|
||||
case 'oauth2': {
|
||||
headers.Authorization = 'Bearer YOUR_OAUTH2_TOKEN';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function validateHttpMethod(method: string): method is OpenAPIV3.HttpMethods {
|
||||
|
||||
@@ -6,21 +6,24 @@ import { OpenAPISchemaName } from './OpenAPISchemaName';
|
||||
import type { OpenAPIClientContext } from './context';
|
||||
import { t } from './translate';
|
||||
import type { OpenAPIOperationData, OpenAPISecurityWithRequired } from './types';
|
||||
import { createStateKey, resolveDescription } from './utils';
|
||||
import { createStateKey, extractOperationSecurityInfo, resolveDescription } from './utils';
|
||||
|
||||
/**
|
||||
* Present securities authorization that can be used for this operation.
|
||||
*/
|
||||
export function OpenAPISecurities(props: {
|
||||
securityRequirement: OpenAPIV3.OperationObject['security'];
|
||||
securities: OpenAPIOperationData['securities'];
|
||||
context: OpenAPIClientContext;
|
||||
}) {
|
||||
const { securities, context } = props;
|
||||
const { securityRequirement, securities, context } = props;
|
||||
|
||||
if (securities.length === 0) {
|
||||
if (!securities || securities.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tabsData = extractOperationSecurityInfo({ securityRequirement, securities });
|
||||
|
||||
return (
|
||||
<InteractiveSection
|
||||
header={t(context.translation, 'authorizations')}
|
||||
@@ -30,27 +33,31 @@ export function OpenAPISecurities(props: {
|
||||
toggleIcon={context.icons.chevronRight}
|
||||
selectIcon={context.icons.chevronDown}
|
||||
className="openapi-securities"
|
||||
tabs={securities.map(([key, security]) => {
|
||||
const description = resolveDescription(security);
|
||||
return {
|
||||
key: key,
|
||||
label: key,
|
||||
body: (
|
||||
<div className="openapi-schema">
|
||||
<div className="openapi-schema-presentation">
|
||||
{getLabelForType(security, context)}
|
||||
|
||||
{description ? (
|
||||
<Markdown
|
||||
source={description}
|
||||
className="openapi-securities-description"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
})}
|
||||
tabs={tabsData.map(({ key, label, schemes }) => ({
|
||||
key,
|
||||
label,
|
||||
body: (
|
||||
<div className="openapi-schema">
|
||||
{schemes.map((security, index) => {
|
||||
const description = resolveDescription(security);
|
||||
return (
|
||||
<div
|
||||
key={`${key}-${index}`}
|
||||
className="openapi-schema-presentation"
|
||||
>
|
||||
{getLabelForType(security, context)}
|
||||
{description ? (
|
||||
<Markdown
|
||||
source={description}
|
||||
className="openapi-securities-description"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,12 @@ export function OpenAPISpec(props: {
|
||||
return (
|
||||
<>
|
||||
{securities.length > 0 ? (
|
||||
<OpenAPISecurities key="securities" securities={securities} context={context} />
|
||||
<OpenAPISecurities
|
||||
key="securities"
|
||||
securityRequirement={operation.security}
|
||||
securities={securities}
|
||||
context={context}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{parameterGroups.map((group) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser'
|
||||
import type { OpenAPIUniversalContext } from './context';
|
||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||
import { tString } from './translate';
|
||||
import type { OpenAPIOperationData, OpenAPISecurityWithRequired } from './types';
|
||||
|
||||
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
||||
return typeof input === 'object' && !!input && '$ref' in input;
|
||||
@@ -253,3 +254,41 @@ export function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
export type OperationSecurityInfo = {
|
||||
key: string;
|
||||
label: string;
|
||||
schemes: OpenAPISecurityWithRequired[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract security information for an operation based on its security requirements and the spec security schemes.
|
||||
*/
|
||||
export function extractOperationSecurityInfo(args: {
|
||||
securityRequirement: OpenAPIV3.OperationObject['security'];
|
||||
securities: OpenAPIOperationData['securities'];
|
||||
}): OperationSecurityInfo[] {
|
||||
const { securityRequirement, securities } = args;
|
||||
const securitiesMap = new Map(securities);
|
||||
|
||||
// When no security requirement include every schemes
|
||||
if (!securityRequirement || securityRequirement.length === 0) {
|
||||
return securities.map(([key, security]) => ({
|
||||
key,
|
||||
label: key,
|
||||
schemes: [security],
|
||||
}));
|
||||
}
|
||||
|
||||
return securityRequirement.map((requirement, idx) => {
|
||||
const schemeKeys = Object.keys(requirement);
|
||||
|
||||
return {
|
||||
key: `security-${idx}`,
|
||||
label: schemeKeys.join(' & '),
|
||||
schemes: schemeKeys
|
||||
.map((schemeKey) => securitiesMap.get(schemeKey))
|
||||
.filter((s) => s !== undefined),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user