mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 05:48:57 +00:00
Add x-gitbook-prefix and x-gitbook-token-placeholder for OpenAPI security scheme (#3820)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@gitbook/openapi-parser': patch
|
||||
'@gitbook/react-openapi': patch
|
||||
---
|
||||
|
||||
Add x-gitbook-prefix and x-gitbook-token-placeholder for OpenAPI security scheme
|
||||
@@ -75,6 +75,14 @@ export interface OpenAPICustomOperationProperties {
|
||||
*/
|
||||
export interface OpenAPICustomPrefillProperties {
|
||||
'x-gitbook-prefill'?: string;
|
||||
/**
|
||||
* Token placeholder used inside sample credentials (e.g., "Bearer ${token}").
|
||||
*/
|
||||
'x-gitbook-token-placeholder'?: string;
|
||||
/**
|
||||
* Prefix to override the default one for the security scheme (e.g., "Bearer", "Basic", "Token").
|
||||
*/
|
||||
'x-gitbook-prefix'?: string;
|
||||
}
|
||||
|
||||
export type OpenAPIStability = 'experimental' | 'alpha' | 'beta';
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { getSecurityHeaders } from './OpenAPICodeSample';
|
||||
import type { OpenAPIOperationData } from './types';
|
||||
|
||||
describe('getSecurityHeaders', () => {
|
||||
it('should handle custom HTTP scheme with x-gitbook-prefix', () => {
|
||||
const securities: OpenAPIOperationData['securities'] = [
|
||||
[
|
||||
'customScheme',
|
||||
{
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
name: 'Authorization',
|
||||
'x-gitbook-prefix': 'CustomScheme',
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
const result = getSecurityHeaders({
|
||||
securityRequirement: [{ customScheme: [] }],
|
||||
securities,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
Authorization: 'CustomScheme YOUR_API_KEY',
|
||||
});
|
||||
});
|
||||
|
||||
it('should use x-gitbook-prefix with x-gitbook-token-placeholder together', () => {
|
||||
const securities: OpenAPIOperationData['securities'] = [
|
||||
[
|
||||
'customAuth',
|
||||
{
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
name: 'Authorization',
|
||||
'x-gitbook-prefix': 'Token',
|
||||
'x-gitbook-token-placeholder': 'MY_CUSTOM_TOKEN',
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
const result = getSecurityHeaders({
|
||||
securityRequirement: [{ customAuth: [] }],
|
||||
securities,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
Authorization: 'Token MY_CUSTOM_TOKEN',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not use x-gitbook-prefix for http scheme', () => {
|
||||
const securities: OpenAPIOperationData['securities'] = [
|
||||
[
|
||||
'customAuth',
|
||||
{
|
||||
type: 'http',
|
||||
in: 'header',
|
||||
name: 'Authorization',
|
||||
scheme: 'bearer',
|
||||
'x-gitbook-prefix': 'Token',
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
const result = getSecurityHeaders({
|
||||
securityRequirement: [{ customAuth: [] }],
|
||||
securities,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
Authorization: 'Bearer YOUR_SECRET_TOKEN',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -290,7 +290,7 @@ function getCustomCodeSamples(props: {
|
||||
return customCodeSamples;
|
||||
}
|
||||
|
||||
function getSecurityHeaders(args: {
|
||||
export function getSecurityHeaders(args: {
|
||||
securityRequirement: OpenAPIV3.OperationObject['security'];
|
||||
securities: OpenAPIOperationData['securities'];
|
||||
}): {
|
||||
@@ -314,6 +314,7 @@ function getSecurityHeaders(args: {
|
||||
for (const security of selectedSecurity.schemes) {
|
||||
switch (security.type) {
|
||||
case 'http': {
|
||||
// We do not use x-gitbook-prefix for http schemes to avoid confusion with the standard.
|
||||
let scheme = security.scheme;
|
||||
const defaultPlaceholderValue = scheme?.toLowerCase()?.includes('basic')
|
||||
? 'username:password'
|
||||
@@ -329,6 +330,8 @@ function getSecurityHeaders(args: {
|
||||
scheme = 'Basic';
|
||||
} else if (scheme?.includes('token')) {
|
||||
scheme = 'Token';
|
||||
} else {
|
||||
scheme = scheme ?? '';
|
||||
}
|
||||
|
||||
headers.Authorization = `${scheme} ${format}`;
|
||||
@@ -339,17 +342,23 @@ function getSecurityHeaders(args: {
|
||||
break;
|
||||
}
|
||||
const name = security.name ?? 'Authorization';
|
||||
headers[name] = resolvePrefillCodePlaceholderFromSecurityScheme({
|
||||
const placeholder = resolvePrefillCodePlaceholderFromSecurityScheme({
|
||||
security: security,
|
||||
defaultPlaceholderValue: 'YOUR_API_KEY',
|
||||
});
|
||||
// Use x-gitbook-prefix if provided for apiKey schemes
|
||||
const prefix = security['x-gitbook-prefix'];
|
||||
headers[name] = prefix ? `${prefix} ${placeholder}` : placeholder;
|
||||
break;
|
||||
}
|
||||
case 'oauth2': {
|
||||
headers.Authorization = `Bearer ${resolvePrefillCodePlaceholderFromSecurityScheme({
|
||||
security: security,
|
||||
defaultPlaceholderValue: 'YOUR_OAUTH2_TOKEN',
|
||||
})}`;
|
||||
const prefix = security['x-gitbook-prefix'] ?? 'Bearer';
|
||||
headers.Authorization = `${prefix} ${resolvePrefillCodePlaceholderFromSecurityScheme(
|
||||
{
|
||||
security: security,
|
||||
defaultPlaceholderValue: 'YOUR_OAUTH2_TOKEN',
|
||||
}
|
||||
)}`;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -415,6 +415,32 @@ describe('resolvePrefillCodePlaceholderFromSecurityScheme (integration style)',
|
||||
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should prioritize x-gitbook-prefill over x-gitbook-token-placeholder when both are present', () => {
|
||||
const result = resolvePrefillCodePlaceholderFromSecurityScheme({
|
||||
security: {
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
'x-gitbook-prefill': '{{ visitor.claims.apiToken }}',
|
||||
'x-gitbook-token-placeholder': 'API_TOKEN_KEY',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe('$$__X-GITBOOK-PREFILL[(visitor.claims.apiToken)]__$$');
|
||||
});
|
||||
|
||||
it('should return x-gitbook-token-placeholder for apiKey scheme', () => {
|
||||
const result = resolvePrefillCodePlaceholderFromSecurityScheme({
|
||||
security: {
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
name: 'X-API-KEY',
|
||||
'x-gitbook-token-placeholder': 'YOUR_API_KEY_HERE',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toBe('YOUR_API_KEY_HERE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveURLWithPrefillCodePlaceholdersFromServer', () => {
|
||||
|
||||
@@ -177,6 +177,10 @@ export function resolvePrefillCodePlaceholderFromSecurityScheme(args: {
|
||||
const prefillExprParts = extractPrefillExpressionPartsFromSecurityScheme(security);
|
||||
|
||||
if (prefillExprParts.length === 0) {
|
||||
// If no x-gitbook-prefill, check for x-gitbook-token-placeholder
|
||||
if (security['x-gitbook-token-placeholder']) {
|
||||
return security['x-gitbook-token-placeholder'];
|
||||
}
|
||||
return defaultPlaceholderValue ?? '';
|
||||
}
|
||||
const prefillExpr = templatePartsToExpression(prefillExprParts);
|
||||
|
||||
Reference in New Issue
Block a user