Skip to content

Commit e505b10

Browse files
committed
cors support
1 parent 5006201 commit e505b10

File tree

6 files changed

+468
-3
lines changed

6 files changed

+468
-3
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,47 @@ stepFunctions:
115115
definition:
116116
```
117117

118+
#### Enabling CORS
119+
120+
To set CORS configurations for your HTTP endpoints, simply modify your event configurations as follows:
121+
122+
```yml
123+
stepFunctions:
124+
stateMachines:
125+
hello:
126+
events:
127+
- http:
128+
path: posts/create
129+
method: POST
130+
cors: true
131+
definition:
132+
```
133+
134+
Setting cors to true assumes a default configuration which is equivalent to:
135+
136+
```yml
137+
stepFunctions:
138+
stateMachines:
139+
hello:
140+
events:
141+
- http:
142+
path: posts/create
143+
method: POST
144+
cors:
145+
origin: '*'
146+
headers:
147+
- Content-Type
148+
- X-Amz-Date
149+
- Authorization
150+
- X-Api-Key
151+
- X-Amz-Security-Token
152+
- X-Amz-User-Agent
153+
allowCredentials: false
154+
definition:
155+
```
156+
157+
Configuring the cors property sets Access-Control-Allow-Origin, Access-Control-Allow-Headers, Access-Control-Allow-Methods,Access-Control-Allow-Credentials headers in the CORS preflight response.
158+
118159
#### Send request to an API
119160
You can input an value as json in request body, the value is passed as the input value of your statemachine
120161

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
'use strict';
2+
3+
const expect = require('chai').expect;
4+
const Serverless = require('serverless/lib/Serverless');
5+
const AwsProvider = require('serverless/lib/plugins/aws/provider/awsProvider');
6+
const ServerlessStepFunctions = require('./../../../index');
7+
8+
describe('#methods()', () => {
9+
let serverless;
10+
let serverlessStepFunctions;
11+
12+
beforeEach(() => {
13+
serverless = new Serverless();
14+
serverless.setProvider('aws', new AwsProvider(serverless));
15+
serverless.service.provider.compiledCloudFormationTemplate = {
16+
Resources: {},
17+
};
18+
19+
const options = {
20+
stage: 'dev',
21+
region: 'us-east-1',
22+
};
23+
serverlessStepFunctions = new ServerlessStepFunctions(serverless, options);
24+
serverlessStepFunctions.serverless.service.stepFunctions = {
25+
stateMachines: {
26+
first: {},
27+
},
28+
};
29+
serverlessStepFunctions.apiGatewayResourceLogicalIds = {
30+
'users/create': 'ApiGatewayResourceUsersCreate',
31+
'users/list': 'ApiGatewayResourceUsersList',
32+
'users/update': 'ApiGatewayResourceUsersUpdate',
33+
'users/delete': 'ApiGatewayResourceUsersDelete',
34+
'users/any': 'ApiGatewayResourceUsersAny',
35+
};
36+
serverlessStepFunctions.apiGatewayResourceNames = {
37+
'users/create': 'UsersCreate',
38+
'users/list': 'UsersList',
39+
'users/update': 'UsersUpdate',
40+
'users/delete': 'UsersDelete',
41+
'users/any': 'UsersAny',
42+
};
43+
serverlessStepFunctions.pluginhttpValidated = {};
44+
});
45+
46+
it('should create preflight method for CORS enabled resource', () => {
47+
serverlessStepFunctions.pluginhttpValidated.corsPreflight = {
48+
'users/update': {
49+
origin: 'http://example.com',
50+
headers: ['*'],
51+
methods: ['OPTIONS', 'PUT'],
52+
allowCredentials: false,
53+
},
54+
'users/create': {
55+
origins: ['*', 'http://example.com'],
56+
headers: ['*'],
57+
methods: ['OPTIONS', 'POST'],
58+
allowCredentials: true,
59+
},
60+
'users/delete': {
61+
origins: ['*'],
62+
headers: ['CustomHeaderA', 'CustomHeaderB'],
63+
methods: ['OPTIONS', 'DELETE'],
64+
allowCredentials: false,
65+
},
66+
'users/any': {
67+
origins: ['http://example.com'],
68+
headers: ['*'],
69+
methods: ['OPTIONS', 'ANY'],
70+
allowCredentials: false,
71+
},
72+
};
73+
return serverlessStepFunctions.compileCors().then(() => {
74+
// users/create
75+
expect(
76+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
77+
.Resources.ApiGatewayMethodUsersCreateOptions
78+
.Properties.Integration.IntegrationResponses[0]
79+
.ResponseParameters['method.response.header.Access-Control-Allow-Origin']
80+
).to.equal('\'*,http://example.com\'');
81+
82+
expect(
83+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
84+
.Resources.ApiGatewayMethodUsersCreateOptions
85+
.Properties.Integration.IntegrationResponses[0]
86+
.ResponseParameters['method.response.header.Access-Control-Allow-Headers']
87+
).to.equal('\'*\'');
88+
89+
expect(
90+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
91+
.Resources.ApiGatewayMethodUsersCreateOptions
92+
.Properties.Integration.IntegrationResponses[0]
93+
.ResponseParameters['method.response.header.Access-Control-Allow-Methods']
94+
).to.equal('\'OPTIONS,POST\'');
95+
96+
expect(
97+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
98+
.Resources.ApiGatewayMethodUsersCreateOptions
99+
.Properties.Integration.IntegrationResponses[0]
100+
.ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
101+
).to.equal('\'true\'');
102+
103+
// users/update
104+
expect(
105+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
106+
.Resources.ApiGatewayMethodUsersUpdateOptions
107+
.Properties.Integration.IntegrationResponses[0]
108+
.ResponseParameters['method.response.header.Access-Control-Allow-Origin']
109+
).to.equal('\'http://example.com\'');
110+
111+
expect(
112+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
113+
.Resources.ApiGatewayMethodUsersUpdateOptions
114+
.Properties.Integration.IntegrationResponses[0]
115+
.ResponseParameters['method.response.header.Access-Control-Allow-Methods']
116+
).to.equal('\'OPTIONS,PUT\'');
117+
118+
expect(
119+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
120+
.Resources.ApiGatewayMethodUsersUpdateOptions
121+
.Properties.Integration.IntegrationResponses[0]
122+
.ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
123+
).to.equal('\'false\'');
124+
125+
// users/delete
126+
expect(
127+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
128+
.Resources.ApiGatewayMethodUsersDeleteOptions
129+
.Properties.Integration.IntegrationResponses[0]
130+
.ResponseParameters['method.response.header.Access-Control-Allow-Origin']
131+
).to.equal('\'*\'');
132+
133+
expect(
134+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
135+
.Resources.ApiGatewayMethodUsersDeleteOptions
136+
.Properties.Integration.IntegrationResponses[0]
137+
.ResponseParameters['method.response.header.Access-Control-Allow-Headers']
138+
).to.equal('\'CustomHeaderA,CustomHeaderB\'');
139+
140+
expect(
141+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
142+
.Resources.ApiGatewayMethodUsersDeleteOptions
143+
.Properties.Integration.IntegrationResponses[0]
144+
.ResponseParameters['method.response.header.Access-Control-Allow-Methods']
145+
).to.equal('\'OPTIONS,DELETE\'');
146+
147+
expect(
148+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
149+
.Resources.ApiGatewayMethodUsersDeleteOptions
150+
.Properties.Integration.IntegrationResponses[0]
151+
.ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
152+
).to.equal('\'false\'');
153+
154+
// users/any
155+
expect(
156+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
157+
.Resources.ApiGatewayMethodUsersAnyOptions
158+
.Properties.Integration.IntegrationResponses[0]
159+
.ResponseParameters['method.response.header.Access-Control-Allow-Origin']
160+
).to.equal('\'http://example.com\'');
161+
162+
expect(
163+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
164+
.Resources.ApiGatewayMethodUsersAnyOptions
165+
.Properties.Integration.IntegrationResponses[0]
166+
.ResponseParameters['method.response.header.Access-Control-Allow-Headers']
167+
).to.equal('\'*\'');
168+
169+
expect(
170+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
171+
.Resources.ApiGatewayMethodUsersAnyOptions
172+
.Properties.Integration.IntegrationResponses[0]
173+
.ResponseParameters['method.response.header.Access-Control-Allow-Methods']
174+
).to.equal('\'OPTIONS,DELETE,GET,HEAD,PATCH,POST,PUT\'');
175+
176+
expect(
177+
serverlessStepFunctions.serverless.service.provider.compiledCloudFormationTemplate
178+
.Resources.ApiGatewayMethodUsersAnyOptions
179+
.Properties.Integration.IntegrationResponses[0]
180+
.ResponseParameters['method.response.header.Access-Control-Allow-Credentials']
181+
).to.equal('\'false\'');
182+
});
183+
});
184+
});

lib/deploy/events/apiGateway/methods.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ module.exports = {
111111
],
112112
};
113113

114-
if (http.cors) {
114+
if (http && http.cors) {
115115
let origin = http.cors.origin;
116116
if (http.cors.origins && http.cors.origins.length) {
117117
origin = http.cors.origins.join(',');
@@ -151,7 +151,7 @@ module.exports = {
151151
},
152152
};
153153

154-
if (http.cors) {
154+
if (http && http.cors) {
155155
let origin = http.cors.origin;
156156
if (http.cors.origins && http.cors.origins.length) {
157157
origin = http.cors.origins.join(',');

lib/deploy/events/apiGateway/methods.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,50 @@ describe('#methods()', () => {
7171
.Integration.RequestTemplates['application/json']['Fn::Join'][1][2].Ref)
7272
.to.be.equal('Custom');
7373
});
74+
75+
it('should set Access-Control-Allow-Origin header when cors is true',
76+
() => {
77+
expect(serverlessStepFunctions.getMethodIntegration('stateMachine', 'custom', {
78+
cors: {
79+
origins: ['*', 'http://example.com'],
80+
},
81+
}).Properties.Integration.IntegrationResponses[0]
82+
.ResponseParameters['method.response.header.Access-Control-Allow-Origin'])
83+
.to.equal('\'*,http://example.com\'');
84+
85+
expect(serverlessStepFunctions.getMethodIntegration('stateMachine', 'custom', {
86+
cors: {
87+
origin: '*',
88+
},
89+
}).Properties.Integration.IntegrationResponses[0]
90+
.ResponseParameters['method.response.header.Access-Control-Allow-Origin'])
91+
.to.equal('\'*\'');
92+
});
7493
});
7594

7695
describe('#getMethodResponses()', () => {
7796
it('should return a corresponding methodResponses resource', () => {
7897
expect(serverlessStepFunctions.getMethodResponses().Properties)
7998
.to.have.property('MethodResponses');
8099
});
100+
101+
it('should set Access-Control-Allow-Origin header when cors is true',
102+
() => {
103+
expect(serverlessStepFunctions.getMethodResponses({
104+
cors: {
105+
origins: ['*', 'http://example.com'],
106+
},
107+
}).Properties.MethodResponses[0]
108+
.ResponseParameters['method.response.header.Access-Control-Allow-Origin'])
109+
.to.equal('\'*,http://example.com\'');
110+
111+
expect(serverlessStepFunctions.getMethodResponses({
112+
cors: {
113+
origin: '*',
114+
},
115+
}).Properties.MethodResponses[0]
116+
.ResponseParameters['method.response.header.Access-Control-Allow-Origin'])
117+
.to.equal('\'*\'');
118+
});
81119
});
82120
});

0 commit comments

Comments
 (0)