From 7f18dda085ddc2c38fbabb3618e0b158503088b9 Mon Sep 17 00:00:00 2001 From: Kyle Minor Date: Fri, 5 Jan 2018 14:24:38 -0500 Subject: [PATCH 1/7] Add maxAge parameter to preflightHeaders response Access-Control-Max-Age --- .../aws/package/compile/events/apiGateway/lib/cors.js | 5 +++++ .../aws/package/compile/events/apiGateway/lib/validate.js | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) 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 cb8a5fd91..48f93378a 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js @@ -24,6 +24,11 @@ module.exports = { 'Access-Control-Allow-Methods': `'${config.methods.join(',')}'`, 'Access-Control-Allow-Credentials': `'${config.allowCredentials}'`, }; + + // Enable CORS Max Age usage if set + if (typeof config.maxAge !== 'undefined') { + preflightHeaders['Access-Control-Max-Age'] = `'${config.maxAge}'`; + } if (_.includes(config.methods, 'ANY')) { preflightHeaders['Access-Control-Allow-Methods'] = 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 03dcbcfb2..6927e4d22 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -59,6 +59,11 @@ module.exports = { cors.methods = _.union(http.cors.methods, cors.methods); cors.origins = _.union(http.cors.origins, cors.origins); cors.origin = http.cors.origin || '*'; + + if(typeof http.cors.maxAge !== 'undefined'){ + cors.maxAge = http.cors.maxAge; + } + cors.allowCredentials = cors.allowCredentials || http.cors.allowCredentials; corsPreflight[http.path] = cors; @@ -326,7 +331,6 @@ module.exports = { } else { cors.methods.push(http.method.toUpperCase()); } - return cors; }, From 933b49f923513fb56bee342039471846fd6f20ef Mon Sep 17 00:00:00 2001 From: Kyle Minor Date: Tue, 9 Jan 2018 15:19:52 -0500 Subject: [PATCH 2/7] Update validate.js --- .../aws/package/compile/events/apiGateway/lib/validate.js | 1 + 1 file changed, 1 insertion(+) 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 6927e4d22..7479f182d 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -331,6 +331,7 @@ module.exports = { } else { cors.methods.push(http.method.toUpperCase()); } + return cors; }, From 25e405c456eccc3103972112910b1201e6aecbea Mon Sep 17 00:00:00 2001 From: Kyle Minor Date: Tue, 9 Jan 2018 15:47:23 -0500 Subject: [PATCH 3/7] Update files to resolve CI requirements --- lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js | 1 - .../aws/package/compile/events/apiGateway/lib/validate.js | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) 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 be99eea38..69177fe5a 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js @@ -24,7 +24,6 @@ module.exports = { 'Access-Control-Allow-Methods': `'${config.methods.join(',')}'`, 'Access-Control-Allow-Credentials': `'${config.allowCredentials}'`, }; - // Enable CORS Max Age usage if set if (typeof config.maxAge !== 'undefined') { preflightHeaders['Access-Control-Max-Age'] = `'${config.maxAge}'`; 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 7479f182d..d30706c6a 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -59,8 +59,7 @@ module.exports = { cors.methods = _.union(http.cors.methods, cors.methods); cors.origins = _.union(http.cors.origins, cors.origins); cors.origin = http.cors.origin || '*'; - - if(typeof http.cors.maxAge !== 'undefined'){ + if (typeof http.cors.maxAge !== 'undefined') { cors.maxAge = http.cors.maxAge; } @@ -331,7 +330,6 @@ module.exports = { } else { cors.methods.push(http.method.toUpperCase()); } - return cors; }, From 8140f44af05a3385cdfb2d02ec06e0b43e8cbebb Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Mon, 26 Mar 2018 20:49:56 -0400 Subject: [PATCH 4/7] Address review feedback, add tests. --- .../compile/events/apiGateway/lib/cors.js | 10 +++- .../events/apiGateway/lib/cors.test.js | 54 +++++++++++++++++++ .../compile/events/apiGateway/lib/validate.js | 5 +- 3 files changed, 63 insertions(+), 6 deletions(-) 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 69177fe5a..8b4dc4747 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js @@ -24,9 +24,15 @@ module.exports = { 'Access-Control-Allow-Methods': `'${config.methods.join(',')}'`, 'Access-Control-Allow-Credentials': `'${config.allowCredentials}'`, }; + // Enable CORS Max Age usage if set - if (typeof config.maxAge !== 'undefined') { - preflightHeaders['Access-Control-Max-Age'] = `'${config.maxAge}'`; + if (_.has(config, 'maxAge')) { + if (_.isInteger(config.maxAge) && config.maxAge > 0) { + preflightHeaders['Access-Control-Max-Age'] = `'${config.maxAge}'`; + } else { + const errorMessage = 'maxAge should be an integer over 0'; + throw new this.serverless.classes.Error(errorMessage); + } } if (_.includes(config.methods, 'ANY')) { 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 db2d06c87..64f65624b 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 @@ -67,18 +67,21 @@ describe('#compileCors()', () => { headers: ['*'], methods: ['OPTIONS', 'PUT'], allowCredentials: false, + maxAge: 86400, }, 'users/create': { origins: ['*', 'http://example.com'], headers: ['*'], methods: ['OPTIONS', 'POST'], allowCredentials: true, + maxAge: 86400, }, 'users/delete': { origins: ['*'], headers: ['CustomHeaderA', 'CustomHeaderB'], methods: ['OPTIONS', 'DELETE'], allowCredentials: false, + maxAge: 86400, }, 'users/any': { origins: ['http://example.com'], @@ -117,6 +120,13 @@ describe('#compileCors()', () => { .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] ).to.equal('\'true\''); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreateOptions + .Properties.Integration.IntegrationResponses[0] + .ResponseParameters['method.response.header.Access-Control-Max-Age'] + ).to.equal('\'86400\''); + // users/update expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate @@ -139,6 +149,13 @@ describe('#compileCors()', () => { .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] ).to.equal('\'false\''); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersUpdateOptions + .Properties.Integration.IntegrationResponses[0] + .ResponseParameters['method.response.header.Access-Control-Max-Age'] + ).to.equal('\'86400\''); + // users/delete expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate @@ -168,6 +185,13 @@ describe('#compileCors()', () => { .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] ).to.equal('\'false\''); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersDeleteOptions + .Properties.Integration.IntegrationResponses[0] + .ResponseParameters['method.response.header.Access-Control-Max-Age'] + ).to.equal('\'86400\''); + // users/any expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate @@ -198,4 +222,34 @@ describe('#compileCors()', () => { ).to.equal('\'false\''); }); }); + + it('should throw error if maxAge is not an integer greater than 0', () => { + awsCompileApigEvents.validated.corsPreflight = { + 'users/update': { + origin: 'http://example.com', + headers: ['*'], + methods: ['OPTIONS', 'PUT'], + allowCredentials: false, + maxAge: -1, + }, + }; + + expect(() => awsCompileApigEvents.compileCors()) + .to.throw(Error, 'maxAge should be an integer over 0'); + }); + + it('should throw error if maxAge is not an integer', () => { + awsCompileApigEvents.validated.corsPreflight = { + 'users/update': { + origin: 'http://example.com', + headers: ['*'], + methods: ['OPTIONS', 'PUT'], + allowCredentials: false, + maxAge: 'five', + }, + }; + + expect(() => awsCompileApigEvents.compileCors()) + .to.throw(Error, 'maxAge should be an integer over 0'); + }); }); 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 35ff1f24f..cb09de414 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -59,11 +59,8 @@ module.exports = { cors.methods = _.union(http.cors.methods, cors.methods); cors.origins = _.union(http.cors.origins, cors.origins); cors.origin = http.cors.origin || '*'; - if (typeof http.cors.maxAge !== 'undefined') { - cors.maxAge = http.cors.maxAge; - } - cors.allowCredentials = cors.allowCredentials || http.cors.allowCredentials; + cors.maxAge = cors.maxAge || http.cors.maxAge; corsPreflight[http.path] = cors; } From ffa67cf2ddcc8969f31ca29c7dbe0c8bd4133625 Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Mon, 26 Mar 2018 22:19:51 -0400 Subject: [PATCH 5/7] Expand http cors tests for maxAge. Last maxAge defined for a path wins in a merge. --- .../compile/events/apiGateway/lib/validate.js | 12 +++++++- .../events/apiGateway/lib/validate.test.js | 29 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) 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 cb09de414..6d1b82947 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -60,7 +60,11 @@ module.exports = { cors.origins = _.union(http.cors.origins, cors.origins); cors.origin = http.cors.origin || '*'; cors.allowCredentials = cors.allowCredentials || http.cors.allowCredentials; - cors.maxAge = cors.maxAge || http.cors.maxAge; + + // when merging, last one defined wins + if (_.has(http.cors, 'maxAge')) { + cors.maxAge = http.cors.maxAge; + } corsPreflight[http.path] = cors; } @@ -333,6 +337,12 @@ module.exports = { if (cors.methods.indexOf(http.method.toUpperCase()) === NOT_FOUND) { cors.methods.push(http.method.toUpperCase()); } + if (_.has(cors, 'maxAge')) { + if (!_.isInteger(cors.maxAge) || cors.maxAge < 1) { + const errorMessage = 'maxAge should be an integer over 0'; + throw new this.serverless.classes.Error(errorMessage); + } + } } else { cors.methods.push(http.method.toUpperCase()); } 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 4e481c9d8..b8c659e15 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 @@ -643,6 +643,7 @@ describe('#validate()', () => { headers: ['X-Foo-Bar'], origins: ['acme.com'], methods: ['POST', 'OPTIONS'], + maxAge: 86400, }, }, }, @@ -657,10 +658,11 @@ describe('#validate()', () => { methods: ['POST', 'OPTIONS'], origins: ['acme.com'], allowCredentials: false, + maxAge: 86400, }); }); - it('should merge all preflight origins, method, headers and allowCredentials for a path', () => { + it('should merge all preflight origins, method, headers, maxAge and allowCredentials for a path', () => { awsCompileApigEvents.serverless.service.functions = { first: { events: [ @@ -673,6 +675,7 @@ describe('#validate()', () => { 'http://example.com', ], allowCredentials: true, + maxAge: 10000, }, }, }, { @@ -683,6 +686,7 @@ describe('#validate()', () => { origins: [ 'http://example2.com', ], + maxAge: 86400, }, }, }, { @@ -717,12 +721,35 @@ describe('#validate()', () => { .to.deep.equal(['http://example2.com', 'http://example.com']); expect(validated.corsPreflight['users/{id}'].headers) .to.deep.equal(['TestHeader2', 'TestHeader']); + expect(validated.corsPreflight.users.maxAge) + .to.equal(86400); expect(validated.corsPreflight.users.allowCredentials) .to.equal(true); expect(validated.corsPreflight['users/{id}'].allowCredentials) .to.equal(false); }); + it('should throw an error if the maxAge is not a positive integer', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'POST', + path: '/foo/bar', + cors: { + origin: '*', + maxAge: -1, + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileApigEvents.validate()).to.throw(Error); + }); + it('should add default statusCode to custom statusCodes', () => { awsCompileApigEvents.serverless.service.functions = { first: { From 4a367939d92a8617c6ddd6bb6968f7f3975b462b Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Mon, 26 Mar 2018 23:34:42 -0400 Subject: [PATCH 6/7] Fix lint error for line too long. --- .../aws/package/compile/events/apiGateway/lib/validate.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b8c659e15..6cbbfaac5 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 @@ -662,7 +662,7 @@ describe('#validate()', () => { }); }); - it('should merge all preflight origins, method, headers, maxAge and allowCredentials for a path', () => { + it('should merge all preflight cors options for a path', () => { awsCompileApigEvents.serverless.service.functions = { first: { events: [ From 666716f185161b6cedaf8824eb1db78503326a1a Mon Sep 17 00:00:00 2001 From: Kurt Miller Date: Tue, 27 Mar 2018 06:27:00 -0400 Subject: [PATCH 7/7] Add docs for cors maxAge property with example. --- docs/providers/aws/events/apigateway.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index c5a6f0dac..c6b8f8041 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -224,6 +224,21 @@ functions: Configuring the `cors` property sets [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin), [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers), [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods),[Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) headers in the CORS preflight response. +To enable the `Access-Control-Max-Age` preflight response header, set the `maxAge` property in the `cors` object: + +```yml +functions: + hello: + handler: handler.hello + events: + - http: + path: hello + method: get + cors: + origin: '*' + maxAge: 86400 +``` + If you want to use CORS with the lambda-proxy integration, remember to include the `Access-Control-Allow-*` headers in your headers object, like this: ```javascript