diff --git a/examples/options.js b/examples/options.js index 24a5f8ac..202c0644 100644 --- a/examples/options.js +++ b/examples/options.js @@ -23,6 +23,9 @@ const swaggerOption = { type: 'apiKey', name: 'apiKey', in: 'header' + }, + basicAuth: { + type: 'basic' } }, security: [{ @@ -281,6 +284,24 @@ const schemaSecurity = { } } +const schemaOpenapiSecurityMultipleAlterntives = { + schema: { + security: [ + { apiKey: [] }, + { bearerAuth: [] } + ] + } +} + +const schemaSwaggerSecurityMultipleAlterntives = { + schema: { + security: [ + { apiKey: [] }, + { basicAuth: [] } + ] + } +} + const schemaConsumes = { schema: { consumes: ['application/x-www-form-urlencoded'], @@ -376,6 +397,8 @@ module.exports = { schemaHeaders, schemaHeadersParams, schemaSecurity, + schemaOpenapiSecurityMultipleAlterntives, + schemaSwaggerSecurityMultipleAlterntives, schemaConsumes, schemaProduces, schemaCookies, diff --git a/index.d.ts b/index.d.ts index 8dc927ce..99831384 100644 --- a/index.d.ts +++ b/index.d.ts @@ -40,7 +40,7 @@ declare module 'fastify' { consumes?: readonly string[]; produces?: readonly string[]; externalDocs?: OpenAPIV2.ExternalDocumentationObject | OpenAPIV3.ExternalDocumentationObject; - security?: ReadonlyArray<{ [securityLabel: string]: readonly string[] }>; + security?: ReadonlyArray<{ [securityLabel: string]: (readonly string[] | undefined) }>; /** * OpenAPI operation unique identifier */ @@ -52,7 +52,7 @@ declare module 'fastify' { [statusCode: string]: OpenAPIV3.ResponseObject['links']; } } - + interface FastifyContextConfig { swaggerTransform?: SwaggerTransform | false; } diff --git a/test/spec/openapi/route.js b/test/spec/openapi/route.js index 92b3bf5a..1bd63750 100644 --- a/test/spec/openapi/route.js +++ b/test/spec/openapi/route.js @@ -18,6 +18,7 @@ const { schemaProduces, schemaQuerystring, schemaSecurity, + schemaOpenapiSecurityMultipleAlterntives, schemaOperationId } = require('../../../examples/options') @@ -35,6 +36,7 @@ test('openapi should return a valid swagger object', async (t) => { fastify.get('/headers', schemaHeaders, () => {}) fastify.get('/headers/:id', schemaHeadersParams, () => {}) fastify.get('/security', schemaSecurity, () => {}) + fastify.get('/securityMultipleAlternatives', schemaOpenapiSecurityMultipleAlterntives, () => {}) await fastify.ready() @@ -59,6 +61,7 @@ test('openapi should return a valid swagger yaml', async (t) => { fastify.get('/headers', schemaHeaders, () => {}) fastify.get('/headers/:id', schemaHeadersParams, () => {}) fastify.get('/security', schemaSecurity, () => {}) + fastify.get('/securityMultipleAlternatives', schemaOpenapiSecurityMultipleAlterntives, () => {}) await fastify.ready() @@ -691,6 +694,212 @@ test('security headers ignored when declared in security and securityScheme', as t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) }) +test('security headers ignored when declared in multiple alternative security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + openapi: { + components: { + securitySchemes: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'header' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'header' + } + } + }, + security: [{ apiKey: [] }, { securityKey: [] }] + } + }) + + fastify.get('/address1/:id', { + schema: { + headers: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + headers: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + headers: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const openapiObject = fastify.swagger() + t.equal(typeof openapiObject, 'object') + + const api = await Swagger.validate(openapiObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + +test('security headers ignored when declared in multiple required security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + openapi: { + components: { + securitySchemes: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'header' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'header' + } + } + }, + security: [{ apiKey: [], securityKey: [] }] + } + }) + + fastify.get('/address1/:id', { + schema: { + headers: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + headers: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + headers: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const openapiObject = fastify.swagger() + t.equal(typeof openapiObject, 'object') + + const api = await Swagger.validate(openapiObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + test('security querystrings ignored when declared in security and securityScheme', async (t) => { t.plan(6) const fastify = Fastify() @@ -706,9 +915,185 @@ test('security querystrings ignored when declared in security and securityScheme } } }, - security: [{ - apiKey: [] - }] + security: [{ + apiKey: [] + }] + } + }) + + fastify.get('/address1/:id', { + schema: { + querystring: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + querystring: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const openapiObject = fastify.swagger() + t.equal(typeof openapiObject, 'object') + + const api = await Swagger.validate(openapiObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + +test('security querystrings ignored when declared in multiple alternative security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + openapi: { + components: { + securitySchemes: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'query' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'query' + } + } + }, + security: [{ apiKey: [] }, { securityKey: [] }] + } + }) + + fastify.get('/address1/:id', { + schema: { + querystring: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + querystring: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + querystring: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const openapiObject = fastify.swagger() + t.equal(typeof openapiObject, 'object') + + const api = await Swagger.validate(openapiObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + +test('security querystrings ignored when declared in multiple required security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + openapi: { + components: { + securitySchemes: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'query' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'query' + } + } + }, + security: [{ apiKey: [], securityKey: [] }] } }) @@ -721,6 +1106,10 @@ test('security querystrings ignored when declared in security and securityScheme type: 'string', description: 'api token' }, + securityKey: { + type: 'string', + description: 'security api token' + }, id: { type: 'string', description: 'common field' @@ -731,6 +1120,28 @@ test('security querystrings ignored when declared in security and securityScheme }, () => {}) fastify.get('/address2/:id', { + schema: { + querystring: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { schema: { querystring: { type: 'object', @@ -756,9 +1167,13 @@ test('security querystrings ignored when declared in security and securityScheme const api = await Swagger.validate(openapiObject) t.pass('valid swagger object') t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) - t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) }) test('security cookies ignored when declared in security and securityScheme', async (t) => { @@ -831,6 +1246,212 @@ test('security cookies ignored when declared in security and securityScheme', as t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) }) +test('security cookies ignored when declared in multiple alternative security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + openapi: { + components: { + securitySchemes: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'cookie' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'cookie' + } + } + }, + security: [{ apiKey: [] }, { securityKey: [] }] + } + }) + + fastify.get('/address1/:id', { + schema: { + cookies: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + cookies: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + cookies: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const openapiObject = fastify.swagger() + t.equal(typeof openapiObject, 'object') + + const api = await Swagger.validate(openapiObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + +test('security cookies ignored when declared in multiple required security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + openapi: { + components: { + securitySchemes: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'cookie' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'cookie' + } + } + }, + security: [{ apiKey: [], securityKey: [] }] + } + }) + + fastify.get('/address1/:id', { + schema: { + cookies: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + cookies: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + cookies: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + id: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const openapiObject = fastify.swagger() + t.equal(typeof openapiObject, 'object') + + const api = await Swagger.validate(openapiObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + test('path params on relative url', async (t) => { t.plan(2) const fastify = Fastify() diff --git a/test/spec/swagger/route.js b/test/spec/swagger/route.js index 1adbe0b7..f8f24b51 100644 --- a/test/spec/swagger/route.js +++ b/test/spec/swagger/route.js @@ -14,7 +14,8 @@ const { schemaHeadersParams, schemaParams, schemaQuerystring, - schemaSecurity + schemaSecurity, + schemaSwaggerSecurityMultipleAlterntives } = require('../../../examples/options') test('swagger should return valid swagger object', async (t) => { @@ -31,6 +32,7 @@ test('swagger should return valid swagger object', async (t) => { fastify.get('/headers', schemaHeaders, () => {}) fastify.get('/headers/:id', schemaHeadersParams, () => {}) fastify.get('/security', schemaSecurity, () => {}) + fastify.get('/securityMultipleAlternatives', schemaSwaggerSecurityMultipleAlterntives, () => {}) await fastify.ready() @@ -59,6 +61,7 @@ test('swagger should return a valid swagger yaml', async (t) => { fastify.get('/headers', schemaHeaders, () => {}) fastify.get('/headers/:id', schemaHeadersParams, () => {}) fastify.get('/security', schemaSecurity, () => {}) + fastify.get('/securityMultipleAlternatives', schemaSwaggerSecurityMultipleAlterntives, () => {}) await fastify.ready() @@ -476,6 +479,248 @@ test('security headers ignored when declared in security and securityScheme', as t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) }) +test('security headers ignored when declared in multiple alternative security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + swagger: { + securityDefinitions: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'header' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'header' + } + }, + security: [ + { apiKey: [] }, { securityKey: [] } + ] + } + }) + + fastify.get('/address1/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const swaggerObject = fastify.swagger() + t.equal(typeof swaggerObject, 'object') + + const api = await Swagger.validate(swaggerObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + +test('security headers ignored when declared in multiple required security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + swagger: { + securityDefinitions: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'header' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'header' + } + }, + security: [ + { apiKey: [], securityKey: [] } + ] + } + }) + + fastify.get('/address1/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + headers: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const swaggerObject = fastify.swagger() + t.equal(typeof swaggerObject, 'object') + + const api = await Swagger.validate(swaggerObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + test('security querystrings ignored when declared in security and securityScheme', async (t) => { t.plan(6) const fastify = Fastify() @@ -556,6 +801,248 @@ test('security querystrings ignored when declared in security and securityScheme t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) }) +test('security querystrings ignored when declared in multiple alternative security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + swagger: { + securityDefinitions: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'query' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'query' + } + }, + security: [ + { apiKey: [] }, { securityKey: [] } + ] + } + }) + + fastify.get('/address1/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const swaggerObject = fastify.swagger() + t.equal(typeof swaggerObject, 'object') + + const api = await Swagger.validate(swaggerObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + +test('security querystrings ignored when declared in multiple required security objects', async (t) => { + t.plan(10) + const fastify = Fastify() + + await fastify.register(fastifySwagger, { + swagger: { + securityDefinitions: { + apiKey: { + type: 'apiKey', + name: 'apiKey', + in: 'query' + }, + securityKey: { + type: 'apiKey', + name: 'securityKey', + in: 'query' + } + }, + security: [ + { apiKey: [], securityKey: [] } + ] + } + }) + + fastify.get('/address1/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + apiKey: { + type: 'string', + description: 'api token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address2/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + securityKey: { + type: 'string', + description: 'security api token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + fastify.get('/address3/:id', { + schema: { + params: { + type: 'object', + properties: { + id: { type: 'string' } + } + }, + querystring: { + type: 'object', + properties: { + authKey: { + type: 'string', + description: 'auth token' + }, + somethingElse: { + type: 'string', + description: 'common field' + } + } + } + } + }, () => {}) + + await fastify.ready() + + const swaggerObject = fastify.swagger() + t.equal(typeof swaggerObject, 'object') + + const api = await Swagger.validate(swaggerObject) + t.pass('valid swagger object') + t.ok(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'apiKey'))) + t.notOk(api.paths['/address1/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) + t.notOk(api.paths['/address2/{id}'].get.parameters.find(({ name }) => (name === 'securityKey'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'id'))) + t.ok(api.paths['/address3/{id}'].get.parameters.find(({ name }) => (name === 'authKey'))) +}) + test('verify generated path param definition with route prefixing', async (t) => { const opts = { schema: {} diff --git a/test/types/types.test.ts b/test/types/types.test.ts index 85393bf4..b08e3e87 100644 --- a/test/types/types.test.ts +++ b/test/types/types.test.ts @@ -125,6 +125,53 @@ app.get('/public/readonly-schema-route', { } } as const, (req, reply) => {}); +app.register(fastifySwagger, { + openapi: { + info: { + title: "Test openapi", + description: "testing multiple security schemes", + version: "1.0.0" + }, + components: { + securitySchemes: { + aapiKey: { + type: "apiKey", + name: "apiKey", + in: "header", + }, + abearerToken: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + }, + }, + security: [], + }, + mode: 'dynamic', +}); + +app.get('/multiple/security/and', { + schema: { + security: [{ apiKey: [], bearerToken: [] }], + response: { 200: {} }, + }, +}, () => {}); + +app.get('/multiple/security/or', { + schema: { + security: [{ apiKey: [] }, { bearerToken: [] }], + response: { 200: {} }, + }, +}, () => {}); + +app.get('/multiple/security/or/alternative', { + schema: { + security: [{ apiKey: [], bearerToken: undefined }, { bearerToken: [], apiKey: undefined }], + response: { 200: {} }, + }, +}, () => {}); + app .register(fastifySwagger, { swagger: {