Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Access-Control-Allow-Credentials for CORS settings fixes #2182 #2736

Merged
merged 3 commits into from
Jan 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
81 changes: 27 additions & 54 deletions docs/providers/aws/events/apigateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,31 @@ functions:
cors: true
```

If you want to use CORS with the lambda-proxy integration, remember to include `Access-Control-Allow-Origin` in your returned headers object, like this:
Setting `cors` to `true` assumes a default configuration which is equivalent to:

```yml
functions:
hello:
handler: handler.hello
events:
- http:
path: hello
method: get
cors:
origins:
- '*'
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
allowCredentials: false
```

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.

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
// handler.js
Expand All @@ -111,7 +135,8 @@ module.exports.hello = function(event, context, callback) {
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin" : "*" // Required for CORS support to work
"Access-Control-Allow-Origin" : "*", // Required for CORS support to work
"Access-Control-Allow-Credentials" : true // Required for cookies, authorization headers with HTTPS
},
body: JSON.stringify({ "message": "Hello World!" })
};
Expand Down Expand Up @@ -545,58 +570,6 @@ functions:
Content-Type: "'application/json+hal'"
```

## Enabling CORS with the Lambda Integration Method

```yml
functions:
hello:
handler: handler.hello
events:
- http:
path: user/create
method: get
integration: lambda
cors: true
```

You can equally set your own attributes:

```yml
functions:
hello:
handler: handler.hello
events:
- http:
path: user/create
method: get
integration: lambda
cors:
origins:
- '*'
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
```

This example is the default setting and is exactly the same as the previous example. The `Access-Control-Allow-Methods` header is set automatically, based on the endpoints specified in your service configuration with CORS enabled.

**Note:** If you are using the default lambda proxy integration, remember to include `Access-Control-Allow-Origin` in your returned headers object otherwise CORS will fail.

```
module.exports.hello = (event, context, callback) => {
return callback(null, {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*'
},
body: 'Hello World!'
});
}
```

## Setting an HTTP Proxy on API Gateway

To set up an HTTP proxy, you'll need two CloudFormation templates, one for the endpoint (known as resource in CF), and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = {
'Access-Control-Allow-Origin': `'${config.origins.join(',')}'`,
'Access-Control-Allow-Headers': `'${config.headers.join(',')}'`,
'Access-Control-Allow-Methods': `'${config.methods.join(',')}'`,
'Access-Control-Allow-Credentials': `'${config.allowCredentials}'`,
};

_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
Expand Down
24 changes: 24 additions & 0 deletions lib/plugins/aws/deploy/compile/events/apiGateway/lib/cors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,19 @@ describe('#compileCors()', () => {
origins: ['*'],
headers: ['*'],
methods: ['OPTIONS', 'PUT'],
allowCredentials: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we remove this setup so that we test that it defaults to false?

Then we have one test w/o any allowCredentials setup, one which tests if it's set to true and one which tests if it's set to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code in validate.js applies the default so there is no default logic to test here. compileCors operates on the validated input (see cors.js). I am just following the convention established in the existing code.

},
'users/create': {
origins: ['*'],
headers: ['*'],
methods: ['OPTIONS', 'POST'],
allowCredentials: true,
},
'users/delete': {
origins: ['*'],
headers: ['CustomHeaderA', 'CustomHeaderB'],
methods: ['OPTIONS', 'DELETE'],
allowCredentials: false,
},
};
return awsCompileApigEvents.compileCors().then(() => {
Expand All @@ -89,6 +92,13 @@ describe('#compileCors()', () => {
.ResponseParameters['method.response.header.Access-Control-Allow-Methods']
).to.equal('\'OPTIONS,POST\'');

expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources.ApiGatewayMethodUsersCreateOptions
.Properties.Integration.IntegrationResponses[0]
.ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
).to.equal('\'true\'');

expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources.ApiGatewayMethodUsersUpdateOptions
Expand All @@ -103,6 +113,13 @@ describe('#compileCors()', () => {
.ResponseParameters['method.response.header.Access-Control-Allow-Methods']
).to.equal('\'OPTIONS,PUT\'');

expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources.ApiGatewayMethodUsersUpdateOptions
.Properties.Integration.IntegrationResponses[0]
.ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
).to.equal('\'false\'');

expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources.ApiGatewayMethodUsersDeleteOptions
Expand All @@ -123,6 +140,13 @@ describe('#compileCors()', () => {
.Properties.Integration.IntegrationResponses[0]
.ResponseParameters['method.response.header.Access-Control-Allow-Methods']
).to.equal('\'OPTIONS,DELETE\'');

expect(
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
.Resources.ApiGatewayMethodUsersDeleteOptions
.Properties.Integration.IntegrationResponses[0]
.ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
).to.equal('\'false\'');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module.exports = {
cors.headers = _.union(http.cors.headers, cors.headers);
cors.methods = _.union(http.cors.methods, cors.methods);
cors.origins = _.union(http.cors.origins, cors.origins);
cors.allowCredentials = cors.allowCredentials || http.cors.allowCredentials;

corsPreflight[http.path] = cors;
}
Expand Down Expand Up @@ -261,11 +262,14 @@ module.exports = {
origins: ['*'],
methods: ['OPTIONS'],
headers,
allowCredentials: false,
};

if (typeof http.cors === 'object') {
cors = http.cors;
cors.methods = cors.methods || [];
cors.allowCredentials = Boolean(cors.allowCredentials);

if (cors.headers) {
if (!Array.isArray(cors.headers)) {
const errorMessage = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ describe('#validate()', () => {
headers: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'],
methods: ['OPTIONS', 'POST'],
origins: ['*'],
allowCredentials: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we do the same like proposed above (for the other test) and remove this configuration so that we test for the default case (which should set allowCredentials to false).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct validate should default to false.

});
});

Expand Down Expand Up @@ -550,10 +551,11 @@ describe('#validate()', () => {
headers: ['X-Foo-Bar'],
methods: ['POST', 'OPTIONS'],
origins: ['acme.com'],
allowCredentials: false,
});
});

it('should merge all preflight origins, method, and headers for a path', () => {
it('should merge all preflight origins, method, headers and allowCredentials for a path', () => {
awsCompileApigEvents.serverless.service.functions = {
first: {
events: [
Expand All @@ -565,6 +567,7 @@ describe('#validate()', () => {
origins: [
'http://example.com',
],
allowCredentials: true,
},
},
}, {
Expand Down Expand Up @@ -609,6 +612,10 @@ 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.allowCredentials)
.to.equal(true);
expect(validated.corsPreflight['users/{id}'].allowCredentials)
.to.equal(false);
});

it('should add default statusCode to custom statusCodes', () => {
Expand Down