Add x-gitbook-prefix and x-gitbook-token-placeholder for OpenAPI security scheme (#3820)

This commit is contained in:
Nolann B.
2025-11-22 10:44:05 +01:00
committed by GitHub
parent 2989f79708
commit 461e15fd00
6 changed files with 135 additions and 6 deletions
+6
View File
@@ -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
+8
View File
@@ -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);