mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 23:55:20 +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 {
|
export interface OpenAPICustomPrefillProperties {
|
||||||
'x-gitbook-prefill'?: string;
|
'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';
|
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;
|
return customCodeSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSecurityHeaders(args: {
|
export function getSecurityHeaders(args: {
|
||||||
securityRequirement: OpenAPIV3.OperationObject['security'];
|
securityRequirement: OpenAPIV3.OperationObject['security'];
|
||||||
securities: OpenAPIOperationData['securities'];
|
securities: OpenAPIOperationData['securities'];
|
||||||
}): {
|
}): {
|
||||||
@@ -314,6 +314,7 @@ function getSecurityHeaders(args: {
|
|||||||
for (const security of selectedSecurity.schemes) {
|
for (const security of selectedSecurity.schemes) {
|
||||||
switch (security.type) {
|
switch (security.type) {
|
||||||
case 'http': {
|
case 'http': {
|
||||||
|
// We do not use x-gitbook-prefix for http schemes to avoid confusion with the standard.
|
||||||
let scheme = security.scheme;
|
let scheme = security.scheme;
|
||||||
const defaultPlaceholderValue = scheme?.toLowerCase()?.includes('basic')
|
const defaultPlaceholderValue = scheme?.toLowerCase()?.includes('basic')
|
||||||
? 'username:password'
|
? 'username:password'
|
||||||
@@ -329,6 +330,8 @@ function getSecurityHeaders(args: {
|
|||||||
scheme = 'Basic';
|
scheme = 'Basic';
|
||||||
} else if (scheme?.includes('token')) {
|
} else if (scheme?.includes('token')) {
|
||||||
scheme = 'Token';
|
scheme = 'Token';
|
||||||
|
} else {
|
||||||
|
scheme = scheme ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.Authorization = `${scheme} ${format}`;
|
headers.Authorization = `${scheme} ${format}`;
|
||||||
@@ -339,17 +342,23 @@ function getSecurityHeaders(args: {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const name = security.name ?? 'Authorization';
|
const name = security.name ?? 'Authorization';
|
||||||
headers[name] = resolvePrefillCodePlaceholderFromSecurityScheme({
|
const placeholder = resolvePrefillCodePlaceholderFromSecurityScheme({
|
||||||
security: security,
|
security: security,
|
||||||
defaultPlaceholderValue: 'YOUR_API_KEY',
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case 'oauth2': {
|
case 'oauth2': {
|
||||||
headers.Authorization = `Bearer ${resolvePrefillCodePlaceholderFromSecurityScheme({
|
const prefix = security['x-gitbook-prefix'] ?? 'Bearer';
|
||||||
security: security,
|
headers.Authorization = `${prefix} ${resolvePrefillCodePlaceholderFromSecurityScheme(
|
||||||
defaultPlaceholderValue: 'YOUR_OAUTH2_TOKEN',
|
{
|
||||||
})}`;
|
security: security,
|
||||||
|
defaultPlaceholderValue: 'YOUR_OAUTH2_TOKEN',
|
||||||
|
}
|
||||||
|
)}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
|||||||
@@ -415,6 +415,32 @@ describe('resolvePrefillCodePlaceholderFromSecurityScheme (integration style)',
|
|||||||
|
|
||||||
expect(result).toBe('');
|
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', () => {
|
describe('resolveURLWithPrefillCodePlaceholdersFromServer', () => {
|
||||||
|
|||||||
@@ -177,6 +177,10 @@ export function resolvePrefillCodePlaceholderFromSecurityScheme(args: {
|
|||||||
const prefillExprParts = extractPrefillExpressionPartsFromSecurityScheme(security);
|
const prefillExprParts = extractPrefillExpressionPartsFromSecurityScheme(security);
|
||||||
|
|
||||||
if (prefillExprParts.length === 0) {
|
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 ?? '';
|
return defaultPlaceholderValue ?? '';
|
||||||
}
|
}
|
||||||
const prefillExpr = templatePartsToExpression(prefillExprParts);
|
const prefillExpr = templatePartsToExpression(prefillExprParts);
|
||||||
|
|||||||
Reference in New Issue
Block a user