mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 17:43:24 +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 { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import type { OpenAPIOperationData } from './types';
|
import type { OpenAPIOperationData } from './types';
|
||||||
import { getDefaultServerURL } from './util/server';
|
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;
|
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()}` : '');
|
(searchParams.size ? `?${searchParams.toString()}` : '');
|
||||||
|
|
||||||
const genericHeaders = {
|
const genericHeaders = {
|
||||||
...getSecurityHeaders(data.securities),
|
...getSecurityHeaders({
|
||||||
|
securityRequirement: data.operation.security,
|
||||||
|
securities: data.securities,
|
||||||
|
}),
|
||||||
...headersObject,
|
...headersObject,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -278,51 +281,66 @@ function getCustomCodeSamples(props: {
|
|||||||
return customCodeSamples;
|
return customCodeSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSecurityHeaders(securities: OpenAPIOperationData['securities']): {
|
function getSecurityHeaders(args: {
|
||||||
|
securityRequirement: OpenAPIV3.OperationObject['security'];
|
||||||
|
securities: OpenAPIOperationData['securities'];
|
||||||
|
}): {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
} {
|
} {
|
||||||
const security = securities[0];
|
const { securityRequirement, securities } = args;
|
||||||
|
const operationSecurityInfo = extractOperationSecurityInfo({ securityRequirement, securities });
|
||||||
|
|
||||||
if (!security) {
|
if (operationSecurityInfo.length === 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (security[1].type) {
|
const selectedSecurity = operationSecurityInfo.at(0);
|
||||||
case 'http': {
|
|
||||||
let scheme = security[1].scheme;
|
|
||||||
let format = security[1].bearerFormat ?? 'YOUR_SECRET_TOKEN';
|
|
||||||
|
|
||||||
if (scheme?.includes('bearer')) {
|
if (!selectedSecurity) {
|
||||||
scheme = 'Bearer';
|
return {};
|
||||||
} else if (scheme?.includes('basic')) {
|
}
|
||||||
scheme = 'Basic';
|
|
||||||
format = 'username:password';
|
const headers: { [key: string]: string } = {};
|
||||||
} else if (scheme?.includes('token')) {
|
|
||||||
scheme = 'Token';
|
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 {
|
const name = security.name ?? 'Authorization';
|
||||||
Authorization: `${scheme} ${format}`,
|
headers[name] = 'YOUR_API_KEY';
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'apiKey': {
|
|
||||||
if (security[1].in !== 'header') return {};
|
|
||||||
|
|
||||||
const name = security[1].name ?? 'Authorization';
|
break;
|
||||||
|
}
|
||||||
return {
|
case 'oauth2': {
|
||||||
[name]: 'YOUR_API_KEY',
|
headers.Authorization = 'Bearer YOUR_OAUTH2_TOKEN';
|
||||||
};
|
break;
|
||||||
}
|
}
|
||||||
case 'oauth2': {
|
default: {
|
||||||
return {
|
break;
|
||||||
Authorization: 'Bearer YOUR_OAUTH2_TOKEN',
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateHttpMethod(method: string): method is OpenAPIV3.HttpMethods {
|
function validateHttpMethod(method: string): method is OpenAPIV3.HttpMethods {
|
||||||
|
|||||||
@@ -6,21 +6,24 @@ import { OpenAPISchemaName } from './OpenAPISchemaName';
|
|||||||
import type { OpenAPIClientContext } from './context';
|
import type { OpenAPIClientContext } from './context';
|
||||||
import { t } from './translate';
|
import { t } from './translate';
|
||||||
import type { OpenAPIOperationData, OpenAPISecurityWithRequired } from './types';
|
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.
|
* Present securities authorization that can be used for this operation.
|
||||||
*/
|
*/
|
||||||
export function OpenAPISecurities(props: {
|
export function OpenAPISecurities(props: {
|
||||||
|
securityRequirement: OpenAPIV3.OperationObject['security'];
|
||||||
securities: OpenAPIOperationData['securities'];
|
securities: OpenAPIOperationData['securities'];
|
||||||
context: OpenAPIClientContext;
|
context: OpenAPIClientContext;
|
||||||
}) {
|
}) {
|
||||||
const { securities, context } = props;
|
const { securityRequirement, securities, context } = props;
|
||||||
|
|
||||||
if (securities.length === 0) {
|
if (!securities || securities.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabsData = extractOperationSecurityInfo({ securityRequirement, securities });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection
|
||||||
header={t(context.translation, 'authorizations')}
|
header={t(context.translation, 'authorizations')}
|
||||||
@@ -30,27 +33,31 @@ export function OpenAPISecurities(props: {
|
|||||||
toggleIcon={context.icons.chevronRight}
|
toggleIcon={context.icons.chevronRight}
|
||||||
selectIcon={context.icons.chevronDown}
|
selectIcon={context.icons.chevronDown}
|
||||||
className="openapi-securities"
|
className="openapi-securities"
|
||||||
tabs={securities.map(([key, security]) => {
|
tabs={tabsData.map(({ key, label, schemes }) => ({
|
||||||
const description = resolveDescription(security);
|
key,
|
||||||
return {
|
label,
|
||||||
key: key,
|
body: (
|
||||||
label: key,
|
<div className="openapi-schema">
|
||||||
body: (
|
{schemes.map((security, index) => {
|
||||||
<div className="openapi-schema">
|
const description = resolveDescription(security);
|
||||||
<div className="openapi-schema-presentation">
|
return (
|
||||||
{getLabelForType(security, context)}
|
<div
|
||||||
|
key={`${key}-${index}`}
|
||||||
{description ? (
|
className="openapi-schema-presentation"
|
||||||
<Markdown
|
>
|
||||||
source={description}
|
{getLabelForType(security, context)}
|
||||||
className="openapi-securities-description"
|
{description ? (
|
||||||
/>
|
<Markdown
|
||||||
) : null}
|
source={description}
|
||||||
</div>
|
className="openapi-securities-description"
|
||||||
</div>
|
/>
|
||||||
),
|
) : null}
|
||||||
};
|
</div>
|
||||||
})}
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ export function OpenAPISpec(props: {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{securities.length > 0 ? (
|
{securities.length > 0 ? (
|
||||||
<OpenAPISecurities key="securities" securities={securities} context={context} />
|
<OpenAPISecurities
|
||||||
|
key="securities"
|
||||||
|
securityRequirement={operation.security}
|
||||||
|
securities={securities}
|
||||||
|
context={context}
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{parameterGroups.map((group) => {
|
{parameterGroups.map((group) => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { AnyObject, OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser'
|
|||||||
import type { OpenAPIUniversalContext } from './context';
|
import type { OpenAPIUniversalContext } from './context';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import { tString } from './translate';
|
import { tString } from './translate';
|
||||||
|
import type { OpenAPIOperationData, OpenAPISecurityWithRequired } from './types';
|
||||||
|
|
||||||
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
||||||
return typeof input === 'object' && !!input && '$ref' in input;
|
return typeof input === 'object' && !!input && '$ref' in input;
|
||||||
@@ -253,3 +254,41 @@ export function getSchemaTitle(schema: OpenAPIV3.SchemaObject): string {
|
|||||||
|
|
||||||
return type;
|
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