Repository: serverless/serverless (JavaScript)
Date: 2019-04-24 19:26:35+00:00
Task type: Feature

Issue:
Support wild-cards in cors multiple origins
# This is a Feature Proposal

## Description
Allow passing wildcards in origins (example https://*.mydomain.com) so that the behaviour is similar to what you can achieve with an actual server.

* What is the use case that should be solved. The more detail you describe this in the easier it is to understand for us.
Many setups use multiple sub-domains, in which case you want to  simply allow all sub-domains from a specific domain. This is a common paradigm in htaccess files, s3 configurations, etc. The CORS spec does not allow for wildcards, but building on top of this pr  will make https://github.com/serverless/serverless/pull/5740 it fairly easy to match the requesting domain to the wildcard value, and use the requesting domain in the template.

* If there is additional config how would it look

Similar or dependent issues:
* https://github.com/serverless/serverless/pull/5740


Issue discussion:
@tdmartino Could you fix hyperlinks to PR? 
https://github.com/serverless/serverless/pull/5740https://guides.github.com/features/mastering-markdown/ seems broken.
@exoego fixed links
@exoego I am not 100% sure of the process, it says we should open an issue before submitting a pr, this is the issue. If nobody comments on it, it means you guys are not interested? When can/should I start working on this? Thank you!
@tdmartino 
I would like you to understand that core develeopers may not dedicate themselves 100% to watch every activities on repository for reasons,  so it may take days or even months to get resnponse from them.

It is all right 👍  to open a PR without waiting response on an issue, to the best of my relief.
Based on my experience, I had opened many PRs to servereless framework and other OSSs without waiting responses after opening issues. 
Most of all maintainers took PR seriously but happily, found a time for review, disscussed with contributers, and then merged PRs (sometimes rejected ofcourse).

It would be very helpful if you find a solution to this issue and open a PR 😄 

Solution patch:
diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md
index 9adab44355e..7ee56678461 100644
--- a/docs/providers/aws/events/apigateway.md
+++ b/docs/providers/aws/events/apigateway.md
@@ -257,6 +257,15 @@ functions:
             allowCredentials: false
 ```
 
+Wildcards are accepted. The following example will match all sub-domains of example.com over http:
+
+```yml
+    cors:
+      origins:
+        - http://*.example.com
+        - http://example2.com
+```
+
 Please note that since you can't send multiple values for [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin), this configuration uses a response template to check if the request origin matches one of your provided `origins` and overrides the header with the following code:
 
 ```
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 147f91f9b6e..e6286f8f27d 100644
--- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
+++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js
@@ -117,12 +117,21 @@ module.exports = {
     ];
   },
 
+  regexifyWildcards(orig) {
+    return orig.map((str) => str.replace(/\./g, '[.]')
+      .replace('*', '.*'));
+  },
+
   generateCorsResponseTemplate(origins) {
+    // glob pattern needs to be parsed into a Java regex
+    // escape literal dots, replace wildcard * for .*
+    const regexOrigins = this.regexifyWildcards(origins);
+
     return (
       '#set($origin = $input.params("Origin"))\n' +
       '#if($origin == "") #set($origin = $input.params("origin")) #end\n' +
-      `#if(${origins
-        .map((o, i, a) => `$origin == "${o}"${i < a.length - 1 ? ' || ' : ''}`)
+      `#if(${regexOrigins
+        .map((o, i, a) => `$origin.matches("${o}")${i < a.length - 1 ? ' || ' : ''}`)
         .join('')}` +
       ') #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end'
     );


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 a51eac76d46..f7a7401ac21 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
@@ -72,7 +72,7 @@ describe('#compileCors()', () => {
         cacheControl: 'max-age=600, s-maxage=600',
       },
       'users/create': {
-        origins: ['http://localhost:3000', 'http://example.com'],
+        origins: ['http://localhost:3000', 'https://*.example.com'],
         headers: ['*'],
         methods: ['OPTIONS', 'POST'],
         allowCredentials: true,
@@ -108,7 +108,7 @@ describe('#compileCors()', () => {
           .Resources.ApiGatewayMethodUsersCreateOptions
           .Properties.Integration.IntegrationResponses[0]
           .ResponseTemplates['application/json']
-      ).to.equal('#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin == "http://localhost:3000" || $origin == "http://example.com") #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end');
+      ).to.equal('#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin.matches("http://localhost:3000") || $origin.matches("https://.*[.]example[.]com")) #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end');
 
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
@@ -230,7 +230,7 @@ describe('#compileCors()', () => {
           .Resources.ApiGatewayMethodUsersAnyOptions
           .Properties.Integration.IntegrationResponses[0]
           .ResponseTemplates['application/json']
-      ).to.equal('#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin == "http://localhost:3000" || $origin == "http://example.com") #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end');
+      ).to.equal('#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin.matches("http://localhost:3000") || $origin.matches("http://example[.]com")) #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end');
 
       expect(
         awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate