Skip to content

Commit 5006201

Browse files
committed
add cors feature
1 parent 9822150 commit 5006201

File tree

4 files changed

+167
-7
lines changed

4 files changed

+167
-7
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
const BbPromise = require('bluebird');
5+
6+
module.exports = {
7+
8+
compileCors() {
9+
_.forEach(this.pluginhttpValidated.corsPreflight, (config, path) => {
10+
const resourceName = this.getResourceName(path);
11+
const resourceRef = this.getResourceId(path);
12+
const corsMethodLogicalId = this.provider.naming
13+
.getMethodLogicalId(resourceName, 'options');
14+
15+
let origin = config.origin;
16+
if (config.origins && config.origins.length) {
17+
origin = config.origins.join(',');
18+
}
19+
20+
const preflightHeaders = {
21+
'Access-Control-Allow-Origin': `'${origin}'`,
22+
'Access-Control-Allow-Headers': `'${config.headers.join(',')}'`,
23+
'Access-Control-Allow-Methods': `'${config.methods.join(',')}'`,
24+
'Access-Control-Allow-Credentials': `'${config.allowCredentials}'`,
25+
};
26+
27+
if (_.includes(config.methods, 'ANY')) {
28+
preflightHeaders['Access-Control-Allow-Methods'] =
29+
preflightHeaders['Access-Control-Allow-Methods']
30+
.replace('ANY', 'DELETE,GET,HEAD,PATCH,POST,PUT');
31+
}
32+
33+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
34+
[corsMethodLogicalId]: {
35+
Type: 'AWS::ApiGateway::Method',
36+
Properties: {
37+
AuthorizationType: 'NONE',
38+
HttpMethod: 'OPTIONS',
39+
MethodResponses: this.generateCorsMethodResponses(preflightHeaders),
40+
RequestParameters: {},
41+
Integration: {
42+
Type: 'MOCK',
43+
RequestTemplates: {
44+
'application/json': '{statusCode:200}',
45+
},
46+
IntegrationResponses: this.generateCorsIntegrationResponses(preflightHeaders),
47+
},
48+
ResourceId: resourceRef,
49+
RestApiId: { Ref: this.apiGatewayRestApiLogicalId },
50+
},
51+
},
52+
});
53+
});
54+
55+
return BbPromise.resolve();
56+
},
57+
58+
generateCorsMethodResponses(preflightHeaders) {
59+
const methodResponseHeaders = {};
60+
61+
_.forEach(preflightHeaders, (value, header) => {
62+
methodResponseHeaders[`method.response.header.${header}`] = true;
63+
});
64+
65+
return [
66+
{
67+
StatusCode: '200',
68+
ResponseParameters: methodResponseHeaders,
69+
ResponseModels: {},
70+
},
71+
];
72+
},
73+
74+
generateCorsIntegrationResponses(preflightHeaders) {
75+
const responseParameters = _.mapKeys(preflightHeaders,
76+
(value, header) => `method.response.header.${header}`);
77+
78+
return [
79+
{
80+
StatusCode: '200',
81+
ResponseParameters: responseParameters,
82+
ResponseTemplates: {
83+
'application/json': '',
84+
},
85+
},
86+
];
87+
},
88+
89+
};

lib/deploy/events/apiGateway/methods.js

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ module.exports = {
2424
};
2525

2626
_.merge(template,
27-
this.getMethodIntegration(event.stateMachineName, stateMachineObj.name),
28-
this.getMethodResponses()
27+
this.getMethodIntegration(event.stateMachineName, stateMachineObj.name, event.http),
28+
this.getMethodResponses(event.http)
2929
);
3030

3131
const methodLogicalId = this.provider.naming
@@ -41,7 +41,7 @@ module.exports = {
4141
return BbPromise.resolve();
4242
},
4343

44-
getMethodIntegration(stateMachineName, customName) {
44+
getMethodIntegration(stateMachineName, customName, http) {
4545
const stateMachineLogicalId = this.getStateMachineLogicalId(stateMachineName, customName);
4646
const apiToStepFunctionsIamRoleLogicalId = this.getApiToStepFunctionsIamRoleLogicalId();
4747
const integration = {
@@ -92,6 +92,9 @@ module.exports = {
9292
],
9393
},
9494
},
95+
};
96+
97+
const integrationResponse = {
9598
IntegrationResponses: [
9699
{
97100
StatusCode: 200,
@@ -108,15 +111,30 @@ module.exports = {
108111
],
109112
};
110113

114+
if (http.cors) {
115+
let origin = http.cors.origin;
116+
if (http.cors.origins && http.cors.origins.length) {
117+
origin = http.cors.origins.join(',');
118+
}
119+
120+
integrationResponse.IntegrationResponses.forEach((val, i) => {
121+
integrationResponse.IntegrationResponses[i].ResponseParameters = {
122+
'method.response.header.Access-Control-Allow-Origin': `'${origin}'`,
123+
};
124+
});
125+
}
126+
127+
_.merge(integration, integrationResponse);
128+
111129
return {
112130
Properties: {
113131
Integration: integration,
114132
},
115133
};
116134
},
117135

118-
getMethodResponses() {
119-
return {
136+
getMethodResponses(http) {
137+
const methodResponse = {
120138
Properties: {
121139
MethodResponses: [
122140
{
@@ -132,5 +150,20 @@ module.exports = {
132150
],
133151
},
134152
};
153+
154+
if (http.cors) {
155+
let origin = http.cors.origin;
156+
if (http.cors.origins && http.cors.origins.length) {
157+
origin = http.cors.origins.join(',');
158+
}
159+
160+
methodResponse.Properties.MethodResponses.forEach((val, i) => {
161+
methodResponse.Properties.MethodResponses[i].ResponseParameters = {
162+
'method.response.header.Access-Control-Allow-Origin': `'${origin}'`,
163+
};
164+
});
165+
}
166+
167+
return methodResponse;
135168
},
136169
};

lib/deploy/events/apiGateway/validate.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,50 @@ module.exports = {
112112
'X-Amz-User-Agent',
113113
];
114114

115-
const cors = {
115+
let cors = {
116116
origins: ['*'],
117117
origin: '*',
118118
methods: ['OPTIONS'],
119119
headers,
120120
allowCredentials: false,
121121
};
122122

123-
cors.methods.push(http.method.toUpperCase());
123+
if (typeof http.cors === 'object') {
124+
cors = http.cors;
125+
cors.methods = cors.methods || [];
126+
cors.allowCredentials = Boolean(cors.allowCredentials);
127+
128+
if (cors.origins && cors.origin) {
129+
const errorMessage = [
130+
'You can only use "origin" or "origins",',
131+
' but not both at the same time to configure CORS.',
132+
' Please check the docs for more info.',
133+
].join('');
134+
throw new this.serverless.classes.Error(errorMessage);
135+
}
136+
137+
if (cors.headers) {
138+
if (!Array.isArray(cors.headers)) {
139+
const errorMessage = [
140+
'CORS header values must be provided as an array.',
141+
' Please check the docs for more info.',
142+
].join('');
143+
throw new this.serverless.classes.Error(errorMessage);
144+
}
145+
} else {
146+
cors.headers = headers;
147+
}
148+
149+
if (cors.methods.indexOf('OPTIONS') === NOT_FOUND) {
150+
cors.methods.push('OPTIONS');
151+
}
152+
153+
if (cors.methods.indexOf(http.method.toUpperCase()) === NOT_FOUND) {
154+
cors.methods.push(http.method.toUpperCase());
155+
}
156+
} else {
157+
cors.methods.push(http.method.toUpperCase());
158+
}
124159

125160
return cors;
126161
},

lib/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const compileIamRole = require('./deploy/stepFunctions/compileIamRole');
77
const httpValidate = require('./deploy/events/apiGateway/validate');
88
const httpResources = require('./deploy/events/apiGateway/resources');
99
const httpMethods = require('./deploy/events/apiGateway/methods');
10+
const httpCors = require('./deploy/events/apiGateway/cors');
1011
const httpIamRole = require('./deploy/events/apiGateway/iamRole');
1112
const httpDeployment = require('./deploy/events/apiGateway/deployment');
1213
const httpRestApi = require('./deploy/events/apiGateway/restApi');
@@ -36,6 +37,7 @@ class ServerlessStepFunctions {
3637
httpValidate,
3738
httpResources,
3839
httpMethods,
40+
httpCors,
3941
httpIamRole,
4042
httpDeployment,
4143
invoke,
@@ -100,6 +102,7 @@ class ServerlessStepFunctions {
100102
.then(this.compileRestApi)
101103
.then(this.compileResources)
102104
.then(this.compileMethods)
105+
.then(this.compileCors)
103106
.then(this.compileHttpIamRole)
104107
.then(this.compileDeployment);
105108
},

0 commit comments

Comments
 (0)