Repository: serverless/serverless (JavaScript)
Date: 2019-02-04 10:27:55+00:00
Task type: Bug Fix

Issue:
Access-Control-Allow-Origin set to undefined when using single origin
<!--
1. If you have a question and not a bug report please ask first at http://forum.serverless.com
2. Please check if an issue already exists. This bug may have already been documented
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Bug Report

## Description

* What went wrong?
When trying to configure a single origin `Access-Control-Allow-Origin` is set to undefined causing API rejects:
```
{
  "Access-Control-Allow-Origin": "'undefined'",
  "Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
  "Access-Control-Allow-Methods": "'OPTIONS,GET'",
  "Access-Control-Allow-Credentials": "'false'"
}
```
* What did you expect should have happened?
For `Access-Control-Allow-Origin` to be set to the configured value.
* What was the config you used?
```
functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: get
          cors:
            origin: '*'
            headers:
              - Content-Type
              - X-Amz-Date
              - Authorization
              - X-Api-Key
              - X-Amz-Security-Token
              - X-Amz-User-Agent
            allowCredentials: false
```
* What stacktrace or error message from your provider did you see?
None. Error doesn't become apparent until I try to use the API.
 
Similar or dependent issues:
* None found

## Additional Data

* ***Serverless Framework Version you're using***:
master (1.36.3)
* ***Operating System***:
Linux (Arch)
* ***Stack Trace***:
N/A
* ***Provider Error messages***:
N/A

This appears to happen because of the following statement in lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js:
```
      if (origins) {
        origin = origins[0];
      }
```
`origins` is an array so if the array is empty `origin` gets overridden with `undefined`.  I will follow up with a PR.


Issue discussion:

Solution patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
index 5e90313a4ec..147f91f9b6e 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
@@ -18,11 +18,11 @@ module.exports = {
         origins = origin.split(',').map(a => a.trim());
       }
 
-      if (origins) {
+      if (!_.isEmpty(origins)) {
         origin = origins[0];
       }
 
-      if (!origin && !origins) {
+      if (!origin) {
         const errorMessage = 'must specify either origin or origins';
         throw new this.serverless.classes.Error(errorMessage);
       }
@@ -110,7 +110,8 @@ module.exports = {
         ResponseParameters: responseParameters,
         ResponseTemplates: {
           'application/json':
-            Array.isArray(origins) ? this.generateCorsResponseTemplate(origins) : '',
+            Array.isArray(origins) && origins.length ?
+              this.generateCorsResponseTemplate(origins) : '',
         },
       },
     ];
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js
index f70b893184c..a480f3978e9 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js
@@ -67,6 +67,10 @@ module.exports = {
               cors.maxAge = http.cors.maxAge;
             }
 
+            if (_.has(http.cors, 'cacheControl')) {
+              cors.cacheControl = http.cors.cacheControl;
+            }
+
             corsPreflight[http.path] = cors;
           }
 


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js
index 2c04595b17e..a51eac76d46 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js
@@ -64,6 +64,7 @@ describe('#compileCors()', () => {
     awsCompileApigEvents.validated.corsPreflight = {
       'users/update': {
         origin: 'http://example.com',
+        origins: [],
         headers: ['*'],
         methods: ['OPTIONS', 'PUT'],
         allowCredentials: false,
diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js
index 6cbbfaac553..54a77792660 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js
@@ -644,6 +644,7 @@ describe('#validate()', () => {
                 origins: ['acme.com'],
                 methods: ['POST', 'OPTIONS'],
                 maxAge: 86400,
+                cacheControl: 'max-age=600, s-maxage=600, proxy-revalidate',
               },
             },
           },
@@ -659,6 +660,7 @@ describe('#validate()', () => {
       origins: ['acme.com'],
       allowCredentials: false,
       maxAge: 86400,
+      cacheControl: 'max-age=600, s-maxage=600, proxy-revalidate',
     });
   });
 
@@ -676,6 +678,7 @@ describe('#validate()', () => {
                 ],
                 allowCredentials: true,
                 maxAge: 10000,
+                cacheControl: 'max-age=600, s-maxage=600, proxy-revalidate',
               },
             },
           }, {
@@ -723,6 +726,8 @@ describe('#validate()', () => {
       .to.deep.equal(['TestHeader2', 'TestHeader']);
     expect(validated.corsPreflight.users.maxAge)
       .to.equal(86400);
+    expect(validated.corsPreflight.users.cacheControl)
+      .to.equal('max-age=600, s-maxage=600, proxy-revalidate');
     expect(validated.corsPreflight.users.allowCredentials)
       .to.equal(true);
     expect(validated.corsPreflight['users/{id}'].allowCredentials)