Merge pull request #4854 from bsdkurt/master

Add maxAge option for CORS
This commit is contained in:
Frank Schmid 2018-03-27 14:08:37 +02:00 committed by GitHub
commit 714c7586d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 2 deletions

View File

@ -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

View File

@ -25,6 +25,16 @@ module.exports = {
'Access-Control-Allow-Credentials': `'${config.allowCredentials}'`,
};
// Enable CORS Max Age usage if set
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')) {
preflightHeaders['Access-Control-Allow-Methods'] =
preflightHeaders['Access-Control-Allow-Methods']

View File

@ -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');
});
});

View File

@ -61,6 +61,11 @@ module.exports = {
cors.origin = http.cors.origin || '*';
cors.allowCredentials = cors.allowCredentials || http.cors.allowCredentials;
// when merging, last one defined wins
if (_.has(http.cors, 'maxAge')) {
cors.maxAge = http.cors.maxAge;
}
corsPreflight[http.path] = cors;
}
@ -332,10 +337,15 @@ 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());
}
return cors;
},

View File

@ -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 cors options 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: {