Skip to content

Commit cb98654

Browse files
committed
fix(oas): render reserved Authorization header param when operation has no security scheme (CX-3611)
1 parent 47c78b3 commit cb98654

3 files changed

Lines changed: 75 additions & 10 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"oas": patch
3+
---
4+
5+
`getParametersAsJSONSchema()` no longer strips a custom `Authorization` header parameter when the operation has no applicable security scheme. `Accept` and `Content-Type` remain reserved (ReadMe computes them), but `Authorization` is only ignored when a security scheme already provides an auth affordance — otherwise dropping it silently removed the only way to authenticate the request (CX-3611).

packages/oas/src/operation/transformers/get-parameters-as-json-schema.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,25 @@ import {
1717
} from '../../lib/refs.js';
1818
import { isRef } from '../../types.js';
1919

20-
const RESERVED_HEADER_PARAMETERS = new Set(['accept', 'authorization', 'content-type']);
20+
// `Accept` and `Content-Type` are always reserved: ReadMe computes them from the operation's
21+
// response media types and request body, so rendering them as editable header params is redundant
22+
// and can interfere with request body serialization.
23+
const RESERVED_HEADER_PARAMETERS = new Set(['accept', 'content-type']);
2124

22-
function isReservedHeaderParameter(param: ParameterObject) {
23-
return param.in === 'header' && RESERVED_HEADER_PARAMETERS.has(param.name.toLowerCase());
25+
/**
26+
* `Authorization` is also reserved by the OpenAPI spec, but unlike `Accept`/`Content-Type` nothing
27+
* auto-computes it — it's only ever supplied when the operation has a security scheme. So we only
28+
* treat a custom `Authorization` header param as reserved when the operation actually has security
29+
* requirements; otherwise stripping it would remove the only affordance to authenticate the
30+
* request, silently breaking docs that model auth this way (CX-3611).
31+
*/
32+
function isReservedHeaderParameter(param: ParameterObject, hasSecurity: boolean) {
33+
if (param.in !== 'header') return false;
34+
35+
const name = param.name.toLowerCase();
36+
if (RESERVED_HEADER_PARAMETERS.has(name)) return true;
37+
38+
return hasSecurity && name === 'authorization';
2439
}
2540

2641
/**
@@ -176,13 +191,14 @@ export function getParametersAsJSONSchema(
176191

177192
function transformParameters(): SchemaWrapper[] {
178193
const operationParams = operation.getParameters();
194+
const hasSecurity = operation.getSecurity().length > 0;
179195

180196
const transformed = Object.keys(types)
181197
.map(type => {
182198
const required: string[] = [];
183199

184200
const parameters = operationParams.filter(param => {
185-
return param.in === type && !isReservedHeaderParameter(param);
201+
return param.in === type && !isReservedHeaderParameter(param, hasSecurity);
186202
});
187203
if (parameters.length === 0) {
188204
return null;

packages/oas/test/operation/transformers/get-parameters-as-json-schema.test.ts

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,55 @@ describe('.getParametersAsJSONSchema()', () => {
7979
});
8080

8181
it('should ignore reserved custom header parameters case-insensitively', async () => {
82+
const oas = createOasForOperation(
83+
{
84+
security: [{ bearerAuth: [] }],
85+
parameters: [
86+
{ in: 'header', name: 'Accept', required: true, schema: { type: 'string' } },
87+
{ in: 'header', name: 'accept', required: true, schema: { type: 'string' } },
88+
{ in: 'header', name: 'ACCEPT', required: true, schema: { type: 'string' } },
89+
{ in: 'header', name: 'Content-Type', required: true, schema: { type: 'string' } },
90+
{ in: 'header', name: 'content-type', required: true, schema: { type: 'string' } },
91+
{ in: 'header', name: 'CONTENT-TYPE', required: true, schema: { type: 'string' } },
92+
{ in: 'header', name: 'Authorization', required: true, schema: { type: 'string' } },
93+
{ in: 'header', name: 'authorization', required: true, schema: { type: 'string' } },
94+
{ in: 'header', name: 'AUTHORIZATION', required: true, schema: { type: 'string' } },
95+
{ in: 'header', name: 'X-Request-ID', required: true, schema: { type: 'string' } },
96+
],
97+
},
98+
{ securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer' } } },
99+
);
100+
101+
const schemas = oas.operation('/', 'get').getParametersAsJSONSchema();
102+
103+
expect(schemas).toStrictEqual([
104+
{
105+
label: 'Headers',
106+
type: 'header',
107+
schema: {
108+
$schema: 'http://json-schema.org/draft-04/schema#',
109+
type: 'object',
110+
properties: {
111+
'X-Request-ID': {
112+
type: 'string',
113+
},
114+
},
115+
required: ['X-Request-ID'],
116+
},
117+
},
118+
]);
119+
await expect(schemas?.map(s => s.schema)).toBeValidJSONSchemas();
120+
});
121+
122+
it('should render a reserved `Authorization` header param when the operation has no security scheme (CX-3611)', async () => {
123+
// `Accept` and `Content-Type` are still ignored because ReadMe computes them, but stripping
124+
// `Authorization` here would remove the only way to authenticate, so it should render.
82125
const oas = createOasForOperation({
83126
parameters: [
84127
{ in: 'header', name: 'Accept', required: true, schema: { type: 'string' } },
85-
{ in: 'header', name: 'accept', required: true, schema: { type: 'string' } },
86-
{ in: 'header', name: 'ACCEPT', required: true, schema: { type: 'string' } },
87128
{ in: 'header', name: 'Content-Type', required: true, schema: { type: 'string' } },
88-
{ in: 'header', name: 'content-type', required: true, schema: { type: 'string' } },
89-
{ in: 'header', name: 'CONTENT-TYPE', required: true, schema: { type: 'string' } },
90129
{ in: 'header', name: 'Authorization', required: true, schema: { type: 'string' } },
91130
{ in: 'header', name: 'authorization', required: true, schema: { type: 'string' } },
92-
{ in: 'header', name: 'AUTHORIZATION', required: true, schema: { type: 'string' } },
93131
{ in: 'header', name: 'X-Request-ID', required: true, schema: { type: 'string' } },
94132
],
95133
});
@@ -104,11 +142,17 @@ describe('.getParametersAsJSONSchema()', () => {
104142
$schema: 'http://json-schema.org/draft-04/schema#',
105143
type: 'object',
106144
properties: {
145+
Authorization: {
146+
type: 'string',
147+
},
148+
authorization: {
149+
type: 'string',
150+
},
107151
'X-Request-ID': {
108152
type: 'string',
109153
},
110154
},
111-
required: ['X-Request-ID'],
155+
required: ['Authorization', 'authorization', 'X-Request-ID'],
112156
},
113157
},
114158
]);

0 commit comments

Comments
 (0)