diff --git a/__tests__/__datasets__/security-root-level.json b/__tests__/__datasets__/security-root-level.json new file mode 100644 index 00000000..1dab26b2 --- /dev/null +++ b/__tests__/__datasets__/security-root-level.json @@ -0,0 +1,60 @@ +{ + "openapi": "3.0.3", + "info": { + "version": "1.0.0", + "title": "Example API definition with a root-defined `security` setup." + }, + "servers": [ + { + "url": "https://httpbin.org" + } + ], + "security": [ + { + "apiKey_query": [] + } + ], + "paths": { + "/anything/apiKey": { + "get": { + "summary": "Query parameter", + "description": "`apiKey` auth will be supplied within an `apiKey` query parameter.", + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "summary": "Cookie", + "description": "`apiKey` auth will be supplied within an `api_key` cookie.", + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "apiKey_cookie": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "apiKey_cookie": { + "type": "apiKey", + "in": "cookie", + "name": "api_key", + "description": "An API key that will be supplied in a named cookie. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object" + }, + "apiKey_query": { + "type": "apiKey", + "in": "query", + "name": "apiKey", + "description": "An API key that will be supplied in a named query parameter. https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object" + } + } + } +} diff --git a/__tests__/lib/reducer.test.ts b/__tests__/lib/reducer.test.ts index a5a8cc85..1973bccd 100644 --- a/__tests__/lib/reducer.test.ts +++ b/__tests__/lib/reducer.test.ts @@ -7,6 +7,7 @@ import { expect, describe, it } from 'vitest'; import reducer from '../../src/lib/reducer'; import complexNesting from '../__datasets__/complex-nesting.json'; import petstoreRefQuirks from '../__datasets__/petstore-ref-quirks.json'; +import securityRootLevel from '../__datasets__/security-root-level.json'; import tagQuirks from '../__datasets__/tag-quirks.json'; describe('reducer', () => { @@ -160,6 +161,17 @@ describe('reducer', () => { }); }); + it('should retain securitySchemes for root-level security definitions', () => { + const reduced = reducer(securityRootLevel as any, { paths: { '/anything/apiKey': '*' } }); + + expect(reduced.components).toStrictEqual({ + securitySchemes: { + apiKey_cookie: expect.any(Object), + apiKey_query: expect.any(Object), + }, + }); + }); + it("should not leave any components if there aren't any in use", () => { const reduced = reducer(uspto as any, { paths: { '/{dataset}/{version}/records': '*' } }); diff --git a/src/lib/reducer.ts b/src/lib/reducer.ts index fe654fc9..1177e04d 100644 --- a/src/lib/reducer.ts +++ b/src/lib/reducer.ts @@ -88,6 +88,15 @@ export default function reducer(definition: OASDocument, opts: ReducerOptions = // Stringify and parse so we get a full non-reference clone of the API definition to work with. const reduced = JSON.parse(JSON.stringify(definition)) as OASDocument; + // Retain any root-level security definitions. + if ('security' in reduced) { + Object.values(reduced.security).forEach(sec => { + Object.keys(sec).forEach(scheme => { + $refs.add(`#/components/securitySchemes/${scheme}`); + }); + }); + } + if ('paths' in reduced) { Object.keys(reduced.paths).forEach(path => { const pathLC = path.toLowerCase();