From f1577f1569cc307c197fb3cd6bb7bdbd232b2af3 Mon Sep 17 00:00:00 2001 From: beryxz Date: Fri, 23 Aug 2024 19:41:48 +0200 Subject: [PATCH 1/4] fix: multiple alternative security objects in route schema --- index.d.ts | 4 ++-- test/types/types.test.ts | 47 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) 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/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: { From 6a7ed5405e809ab61024ae864832930d035c0b86 Mon Sep 17 00:00:00 2001 From: beryxz Date: Fri, 23 Aug 2024 22:20:34 +0200 Subject: [PATCH 2/4] test(openapi): multiple security objects --- examples/options.js | 10 + test/spec/openapi/route.js | 629 ++++++++++++++++++++++++++++++++++++- 2 files changed, 635 insertions(+), 4 deletions(-) diff --git a/examples/options.js b/examples/options.js index 24a5f8ac..e0f7d1c3 100644 --- a/examples/options.js +++ b/examples/options.js @@ -281,6 +281,15 @@ const schemaSecurity = { } } +const schemaSecurityMultipleAlterntives = { + schema: { + security: [ + { apiKey: [] }, + { bearerAuth: [] } + ] + } +} + const schemaConsumes = { schema: { consumes: ['application/x-www-form-urlencoded'], @@ -376,6 +385,7 @@ module.exports = { schemaHeaders, schemaHeadersParams, schemaSecurity, + schemaSecurityMultipleAlterntives, schemaConsumes, schemaProduces, schemaCookies, diff --git a/test/spec/openapi/route.js b/test/spec/openapi/route.js index 92b3bf5a..2267e1b0 100644 --- a/test/spec/openapi/route.js +++ b/test/spec/openapi/route.js @@ -18,6 +18,7 @@ const { schemaProduces, schemaQuerystring, schemaSecurity, + schemaSecurityMultipleAlterntives, 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', schemaSecurityMultipleAlterntives, () => {}) 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', schemaSecurityMultipleAlterntives, () => {}) 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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: 'authorization bearer' + }, + 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() From b1982bddff10ef65029b4fc48045b2369fe4fcdf Mon Sep 17 00:00:00 2001 From: beryxz Date: Fri, 23 Aug 2024 22:50:14 +0200 Subject: [PATCH 3/4] test(swagger): multiple security objects --- examples/options.js | 17 +- test/spec/openapi/route.js | 30 +-- test/spec/swagger/route.js | 368 ++++++++++++++++++++++++++++++++++++- 3 files changed, 397 insertions(+), 18 deletions(-) diff --git a/examples/options.js b/examples/options.js index e0f7d1c3..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,7 +284,7 @@ const schemaSecurity = { } } -const schemaSecurityMultipleAlterntives = { +const schemaOpenapiSecurityMultipleAlterntives = { schema: { security: [ { apiKey: [] }, @@ -290,6 +293,15 @@ const schemaSecurityMultipleAlterntives = { } } +const schemaSwaggerSecurityMultipleAlterntives = { + schema: { + security: [ + { apiKey: [] }, + { basicAuth: [] } + ] + } +} + const schemaConsumes = { schema: { consumes: ['application/x-www-form-urlencoded'], @@ -385,7 +397,8 @@ module.exports = { schemaHeaders, schemaHeadersParams, schemaSecurity, - schemaSecurityMultipleAlterntives, + schemaOpenapiSecurityMultipleAlterntives, + schemaSwaggerSecurityMultipleAlterntives, schemaConsumes, schemaProduces, schemaCookies, diff --git a/test/spec/openapi/route.js b/test/spec/openapi/route.js index 2267e1b0..523f5c09 100644 --- a/test/spec/openapi/route.js +++ b/test/spec/openapi/route.js @@ -18,7 +18,7 @@ const { schemaProduces, schemaQuerystring, schemaSecurity, - schemaSecurityMultipleAlterntives, + schemaOpenapiSecurityMultipleAlterntives, schemaOperationId } = require('../../../examples/options') @@ -36,7 +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', schemaSecurityMultipleAlterntives, () => {}) + fastify.get('/securityMultipleAlternatives', schemaOpenapiSecurityMultipleAlterntives, () => {}) await fastify.ready() @@ -61,7 +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', schemaSecurityMultipleAlterntives, () => {}) + fastify.get('/securityMultipleAlternatives', schemaOpenapiSecurityMultipleAlterntives, () => {}) await fastify.ready() @@ -729,7 +729,7 @@ test('security headers ignored when declared in multiple alternative security ob }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -751,7 +751,7 @@ test('security headers ignored when declared in multiple alternative security ob }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -832,7 +832,7 @@ test('security headers ignored when declared in multiple required security objec }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -854,7 +854,7 @@ test('security headers ignored when declared in multiple required security objec }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -1005,7 +1005,7 @@ test('security querystrings ignored when declared in multiple alternative securi }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -1027,7 +1027,7 @@ test('security querystrings ignored when declared in multiple alternative securi }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -1108,7 +1108,7 @@ test('security querystrings ignored when declared in multiple required security }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -1130,7 +1130,7 @@ test('security querystrings ignored when declared in multiple required security }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -1281,7 +1281,7 @@ test('security cookies ignored when declared in multiple alternative security ob }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -1303,7 +1303,7 @@ test('security cookies ignored when declared in multiple alternative security ob }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -1384,7 +1384,7 @@ test('security cookies ignored when declared in multiple required security objec }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', @@ -1406,7 +1406,7 @@ test('security cookies ignored when declared in multiple required security objec }, securityKey: { type: 'string', - description: 'authorization bearer' + description: 'security api token' }, id: { type: 'string', diff --git a/test/spec/swagger/route.js b/test/spec/swagger/route.js index 1adbe0b7..7f076656 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,127 @@ 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('verify generated path param definition with route prefixing', async (t) => { const opts = { schema: {} From 0571b62381ab4548207c05f3b0fb0dae5be05d5c Mon Sep 17 00:00:00 2001 From: beryxz Date: Fri, 23 Aug 2024 22:55:31 +0200 Subject: [PATCH 4/4] test: fix openapi test & add missing swagger test --- test/spec/openapi/route.js | 2 +- test/spec/swagger/route.js | 121 +++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/test/spec/openapi/route.js b/test/spec/openapi/route.js index 523f5c09..1bd63750 100644 --- a/test/spec/openapi/route.js +++ b/test/spec/openapi/route.js @@ -817,7 +817,7 @@ test('security headers ignored when declared in multiple required security objec } } }, - security: [{ apiKey: [] }, { securityKey: [] }] + security: [{ apiKey: [], securityKey: [] }] } }) diff --git a/test/spec/swagger/route.js b/test/spec/swagger/route.js index 7f076656..f8f24b51 100644 --- a/test/spec/swagger/route.js +++ b/test/spec/swagger/route.js @@ -922,6 +922,127 @@ test('security querystrings ignored when declared in multiple alternative securi 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: {}