Repository: nestjs/swagger
Pull request #1230 (2021-02-24 17:05:59)

feat: add support for multiple authentication types
## PR Checklist
Please check if your PR fulfills the following requirements:

- [x] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md
- [x] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)


## PR Type
What kind of change does this PR introduce?

<!-- Please check the one that applies to this PR using "x". -->
```
[ ] Bugfix
[x] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Other... Please describe:
```

## What is the current behavior?
The current api only allows for combining security requirements using logical OR. OpenApi specification also supports combining security requirements using logical AND.
https://swagger.io/docs/specification/authentication/

```ts
@ApiSecurity('key1')
@ApiSecurity('key2')
@Controller('cats')
class CatsController {}
```

```json
{
  "security": [
    { "key1": [] },
    { "key2": [] }
  ]
}
```

Issue Number: #130

## What is the new behavior?

```ts
@ApiSecurity({ key1: [], key2: []})
@Controller('cats')
class CatsController {}
```

```json
{
  "security": [
    { "key1": [] , "key2": [] }
  ]
}
```

## Does this PR introduce a breaking change?
```
[ ] Yes
[x] No
```

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->


Patch:
diff --git a/lib/decorators/api-security.decorator.ts b/lib/decorators/api-security.decorator.ts
index f93725b1..46c82b9b 100644
--- a/lib/decorators/api-security.decorator.ts
+++ b/lib/decorators/api-security.decorator.ts
@@ -1,12 +1,19 @@
+import { isString } from 'lodash';
 import { DECORATORS } from '../constants';
 import { SecurityRequirementObject } from '../interfaces/open-api-spec.interface';
 import { extendMetadata } from '../utils/extend-metadata.util';
 
 export function ApiSecurity(
-  name: string,
+  name: string | SecurityRequirementObject,
   requirements: string[] = []
 ): ClassDecorator & MethodDecorator {
-  let metadata: SecurityRequirementObject[] = [{ [name]: requirements }];
+  let metadata: SecurityRequirementObject[];
+
+  if (isString(name)) {
+    metadata = [{ [name]: requirements }];
+  } else {
+    metadata = [name];
+  }
 
   return (
     target: object,
diff --git a/lib/document-builder.ts b/lib/document-builder.ts
index 6417a12f..4e578931 100644
--- a/lib/document-builder.ts
+++ b/lib/document-builder.ts
@@ -1,9 +1,10 @@
 import { Logger } from '@nestjs/common';
-import { isUndefined, negate, pickBy } from 'lodash';
+import { isString, isUndefined, negate, pickBy } from 'lodash';
 import { buildDocumentBase } from './fixtures/document.base';
 import { OpenAPIObject } from './interfaces';
 import {
   ExternalDocumentationObject,
+  SecurityRequirementObject,
   SecuritySchemeObject,
   ServerVariableObject,
   TagObject
@@ -91,11 +92,19 @@ export class DocumentBuilder {
   }
 
   public addSecurityRequirements(
-    name: string,
+    name: string | SecurityRequirementObject,
     requirements: string[] = []
   ): this {
+    let securityRequirement: SecurityRequirementObject;
+
+    if (isString(name)) {
+      securityRequirement = { [name]: requirements };
+    } else {
+      securityRequirement = name;
+    }
+
     this.document.security = (this.document.security || []).concat({
-      [name]: requirements
+      ...securityRequirement
     });
     return this;
   }


Test patch:
diff --git a/e2e/api-spec.json b/e2e/api-spec.json
index f0f6550c..c1806215 100644
--- a/e2e/api-spec.json
+++ b/e2e/api-spec.json
@@ -33,6 +33,16 @@
         "in": "header",
         "name": "api_key"
       },
+      "key1": {
+        "type": "apiKey",
+        "in": "header",
+        "name": "key1"
+      },
+      "key2": {
+        "type": "apiKey",
+        "in": "header",
+        "name": "key2"
+      },
       "cookie": {
         "type": "apiKey",
         "in": "cookie",
@@ -229,6 +239,10 @@
   "security": [
     {
       "bearer": []
+    },
+    {
+      "basic": [],
+      "cookie": []
     }
   ],
   "paths": {
@@ -281,6 +295,10 @@
           "create cats"
         ],
         "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
           {
             "bearer": []
           },
@@ -383,6 +401,10 @@
           "cats"
         ],
         "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
           {
             "bearer": []
           },
@@ -432,6 +454,10 @@
           "cats"
         ],
         "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
           {
             "bearer": []
           },
@@ -536,6 +562,10 @@
           "cats"
         ],
         "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
           {
             "bearer": []
           },
@@ -569,6 +599,10 @@
           "cats"
         ],
         "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
           {
             "bearer": []
           },
@@ -613,6 +647,10 @@
           "cats"
         ],
         "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
           {
             "bearer": []
           },
@@ -667,6 +705,10 @@
           "cats"
         ],
         "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
           {
             "bearer": []
           },
@@ -700,6 +742,10 @@
           "cats"
         ],
         "security": [
+          {
+            "key2": [],
+            "key1": []
+          },
           {
             "bearer": []
           },
diff --git a/e2e/fastify.e2e-spec.ts b/e2e/fastify.e2e-spec.ts
index 5b04022e..95bc5319 100644
--- a/e2e/fastify.e2e-spec.ts
+++ b/e2e/fastify.e2e-spec.ts
@@ -27,8 +27,11 @@ describe('Fastify Swagger', () => {
       .addBearerAuth()
       .addOAuth2()
       .addApiKey()
+      .addApiKey({ type: 'apiKey' }, 'key1')
+      .addApiKey({ type: 'apiKey' }, 'key2')
       .addCookieAuth()
-      .addSecurityRequirements('bearer');
+      .addSecurityRequirements('bearer')
+      .addSecurityRequirements({ basic: [], cookie: [] });
   });
 
   it('should produce a valid OpenAPI 3.0 schema', async () => {
diff --git a/e2e/src/cats/cats.controller.ts b/e2e/src/cats/cats.controller.ts
index 923abbff..7a1aed40 100644
--- a/e2e/src/cats/cats.controller.ts
+++ b/e2e/src/cats/cats.controller.ts
@@ -17,6 +17,7 @@ import { PaginationQuery } from './dto/pagination-query.dto';
 
 @ApiSecurity('basic')
 @ApiBearerAuth()
+@ApiSecurity({ key2: [], key1: [] })
 @ApiTags('cats')
 @ApiHeader({
   name: 'header',
diff --git a/e2e/validate-schema.e2e-spec.ts b/e2e/validate-schema.e2e-spec.ts
index 1df73bc3..f7f5d148 100644
--- a/e2e/validate-schema.e2e-spec.ts
+++ b/e2e/validate-schema.e2e-spec.ts
@@ -24,8 +24,11 @@ describe('Validate OpenAPI schema', () => {
       .addBearerAuth()
       .addOAuth2()
       .addApiKey()
+      .addApiKey({ type: 'apiKey' }, 'key1')
+      .addApiKey({ type: 'apiKey' }, 'key2')
       .addCookieAuth()
       .addSecurityRequirements('bearer')
+      .addSecurityRequirements({ basic: [], cookie: [] })
       .build();
 
     document = SwaggerModule.createDocument(app, options);