serverless-step-functions
Advanced tools
Comparing version 1.0.5 to 1.1.0
@@ -27,4 +27,4 @@ 'use strict'; | ||
_.merge(template, | ||
this.getMethodIntegration(event.stateMachineName, stateMachineObj.name), | ||
this.getMethodResponses() | ||
this.getMethodIntegration(event.stateMachineName, stateMachineObj.name, event.http), | ||
this.getMethodResponses(event.http) | ||
); | ||
@@ -45,3 +45,3 @@ | ||
getMethodIntegration(stateMachineName, customName) { | ||
getMethodIntegration(stateMachineName, customName, http) { | ||
const stateMachineLogicalId = this.getStateMachineLogicalId(stateMachineName, customName); | ||
@@ -97,2 +97,5 @@ const apiToStepFunctionsIamRoleLogicalId = this.getApiToStepFunctionsIamRoleLogicalId(); | ||
}, | ||
}; | ||
const integrationResponse = { | ||
IntegrationResponses: [ | ||
@@ -114,2 +117,17 @@ { | ||
if (http && http.cors) { | ||
let origin = http.cors.origin; | ||
if (http.cors.origins && http.cors.origins.length) { | ||
origin = http.cors.origins.join(','); | ||
} | ||
integrationResponse.IntegrationResponses.forEach((val, i) => { | ||
integrationResponse.IntegrationResponses[i].ResponseParameters = { | ||
'method.response.header.Access-Control-Allow-Origin': `'${origin}'`, | ||
}; | ||
}); | ||
} | ||
_.merge(integration, integrationResponse); | ||
return { | ||
@@ -122,4 +140,4 @@ Properties: { | ||
getMethodResponses() { | ||
return { | ||
getMethodResponses(http) { | ||
const methodResponse = { | ||
Properties: { | ||
@@ -140,3 +158,18 @@ MethodResponses: [ | ||
}; | ||
if (http && http.cors) { | ||
let origin = http.cors.origin; | ||
if (http.cors.origins && http.cors.origins.length) { | ||
origin = http.cors.origins.join(','); | ||
} | ||
methodResponse.Properties.MethodResponses.forEach((val, i) => { | ||
methodResponse.Properties.MethodResponses[i].ResponseParameters = { | ||
'method.response.header.Access-Control-Allow-Origin': `'${origin}'`, | ||
}; | ||
}); | ||
} | ||
return methodResponse; | ||
}, | ||
}; |
@@ -74,2 +74,21 @@ 'use strict'; | ||
}); | ||
it('should set Access-Control-Allow-Origin header when cors is true', | ||
() => { | ||
expect(serverlessStepFunctions.getMethodIntegration('stateMachine', 'custom', { | ||
cors: { | ||
origins: ['*', 'http://example.com'], | ||
}, | ||
}).Properties.Integration.IntegrationResponses[0] | ||
.ResponseParameters['method.response.header.Access-Control-Allow-Origin']) | ||
.to.equal('\'*,http://example.com\''); | ||
expect(serverlessStepFunctions.getMethodIntegration('stateMachine', 'custom', { | ||
cors: { | ||
origin: '*', | ||
}, | ||
}).Properties.Integration.IntegrationResponses[0] | ||
.ResponseParameters['method.response.header.Access-Control-Allow-Origin']) | ||
.to.equal('\'*\''); | ||
}); | ||
}); | ||
@@ -82,3 +101,22 @@ | ||
}); | ||
it('should set Access-Control-Allow-Origin header when cors is true', | ||
() => { | ||
expect(serverlessStepFunctions.getMethodResponses({ | ||
cors: { | ||
origins: ['*', 'http://example.com'], | ||
}, | ||
}).Properties.MethodResponses[0] | ||
.ResponseParameters['method.response.header.Access-Control-Allow-Origin']) | ||
.to.equal('\'*,http://example.com\''); | ||
expect(serverlessStepFunctions.getMethodResponses({ | ||
cors: { | ||
origin: '*', | ||
}, | ||
}).Properties.MethodResponses[0] | ||
.ResponseParameters['method.response.header.Access-Control-Allow-Origin']) | ||
.to.equal('\'*\''); | ||
}); | ||
}); | ||
}); |
'use strict'; | ||
const NOT_FOUND = -1; | ||
const _ = require('lodash'); | ||
@@ -8,2 +8,3 @@ | ||
const events = []; | ||
const corsPreflight = {}; | ||
@@ -18,2 +19,16 @@ _.forEach(this.getAllStateMachines(), (stateMachineName) => { | ||
if (http.cors) { | ||
http.cors = this.getCors(http); | ||
const cors = corsPreflight[http.path] || {}; | ||
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.origin = http.cors.origin || '*'; | ||
cors.allowCredentials = cors.allowCredentials || http.cors.allowCredentials; | ||
corsPreflight[http.path] = cors; | ||
} | ||
events.push({ | ||
@@ -29,2 +44,3 @@ stateMachineName, | ||
events, | ||
corsPreflight, | ||
}; | ||
@@ -91,2 +107,60 @@ }, | ||
}, | ||
getCors(http) { | ||
const headers = [ | ||
'Content-Type', | ||
'X-Amz-Date', | ||
'Authorization', | ||
'X-Api-Key', | ||
'X-Amz-Security-Token', | ||
'X-Amz-User-Agent', | ||
]; | ||
let cors = { | ||
origins: ['*'], | ||
origin: '*', | ||
methods: ['OPTIONS'], | ||
headers, | ||
allowCredentials: false, | ||
}; | ||
if (typeof http.cors === 'object') { | ||
cors = http.cors; | ||
cors.methods = cors.methods || []; | ||
cors.allowCredentials = Boolean(cors.allowCredentials); | ||
if (cors.origins && cors.origin) { | ||
const errorMessage = [ | ||
'You can only use "origin" or "origins",', | ||
' but not both at the same time to configure CORS.', | ||
' Please check the docs for more info.', | ||
].join(''); | ||
throw new this.serverless.classes.Error(errorMessage); | ||
} | ||
if (cors.headers) { | ||
if (!Array.isArray(cors.headers)) { | ||
const errorMessage = [ | ||
'CORS header values must be provided as an array.', | ||
' Please check the docs for more info.', | ||
].join(''); | ||
throw new this.serverless.classes.Error(errorMessage); | ||
} | ||
} else { | ||
cors.headers = headers; | ||
} | ||
if (cors.methods.indexOf('OPTIONS') === NOT_FOUND) { | ||
cors.methods.push('OPTIONS'); | ||
} | ||
if (cors.methods.indexOf(http.method.toUpperCase()) === NOT_FOUND) { | ||
cors.methods.push(http.method.toUpperCase()); | ||
} | ||
} else { | ||
cors.methods.push(http.method.toUpperCase()); | ||
} | ||
return cors; | ||
}, | ||
}; |
@@ -221,2 +221,197 @@ 'use strict'; | ||
}); | ||
it('should throw an error if "origin" and "origins" CORS config is used', () => { | ||
serverlessStepFunctions.serverless.service.stepFunctions = { | ||
stateMachines: { | ||
first: { | ||
events: [ | ||
{ | ||
http: { | ||
method: 'POST', | ||
path: '/foo/bar', | ||
cors: { | ||
origin: '*', | ||
origins: ['*'], | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
expect(() => serverlessStepFunctions.httpValidate()) | ||
.to.throw(Error, 'can only use'); | ||
}); | ||
it('should process cors defaults', () => { | ||
serverlessStepFunctions.serverless.service.stepFunctions = { | ||
stateMachines: { | ||
first: { | ||
events: [ | ||
{ | ||
http: { | ||
method: 'POST', | ||
path: '/foo/bar', | ||
cors: true, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
const validated = serverlessStepFunctions.httpValidate(); | ||
expect(validated.events).to.be.an('Array').with.length(1); | ||
expect(validated.events[0].http.cors).to.deep.equal({ | ||
headers: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', | ||
'X-Amz-Security-Token', 'X-Amz-User-Agent'], | ||
methods: ['OPTIONS', 'POST'], | ||
origin: '*', | ||
origins: ['*'], | ||
allowCredentials: false, | ||
}); | ||
}); | ||
it('should throw if cors headers are not an array', () => { | ||
serverlessStepFunctions.serverless.service.stepFunctions = { | ||
stateMachines: { | ||
first: { | ||
events: [ | ||
{ | ||
http: { | ||
method: 'POST', | ||
path: '/foo/bar', | ||
cors: { | ||
headers: true, | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error); | ||
}); | ||
it('should process cors options', () => { | ||
serverlessStepFunctions.serverless.service.stepFunctions = { | ||
stateMachines: { | ||
first: { | ||
events: [ | ||
{ | ||
http: { | ||
method: 'POST', | ||
path: '/foo/bar', | ||
cors: { | ||
headers: ['X-Foo-Bar'], | ||
origins: ['acme.com'], | ||
methods: ['POST', 'OPTIONS'], | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
const validated = serverlessStepFunctions.httpValidate(); | ||
expect(validated.events).to.be.an('Array').with.length(1); | ||
expect(validated.events[0].http.cors).to.deep.equal({ | ||
headers: ['X-Foo-Bar'], | ||
methods: ['POST', 'OPTIONS'], | ||
origins: ['acme.com'], | ||
allowCredentials: false, | ||
}); | ||
}); | ||
it('should merge all preflight origins, method, headers and allowCredentials for a path', () => { | ||
serverlessStepFunctions.serverless.service.stepFunctions = { | ||
stateMachines: { | ||
first: { | ||
events: [ | ||
{ | ||
http: { | ||
method: 'GET', | ||
path: 'users', | ||
cors: { | ||
origins: [ | ||
'http://example.com', | ||
], | ||
allowCredentials: true, | ||
}, | ||
}, | ||
}, { | ||
http: { | ||
method: 'POST', | ||
path: 'users', | ||
cors: { | ||
origins: [ | ||
'http://example2.com', | ||
], | ||
}, | ||
}, | ||
}, { | ||
http: { | ||
method: 'PUT', | ||
path: 'users/{id}', | ||
cors: { | ||
headers: [ | ||
'TestHeader', | ||
], | ||
}, | ||
}, | ||
}, { | ||
http: { | ||
method: 'DELETE', | ||
path: 'users/{id}', | ||
cors: { | ||
headers: [ | ||
'TestHeader2', | ||
], | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
const validated = serverlessStepFunctions.httpValidate(); | ||
expect(validated.corsPreflight['users/{id}'].methods) | ||
.to.deep.equal(['OPTIONS', 'DELETE', 'PUT']); | ||
expect(validated.corsPreflight.users.origins) | ||
.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('serverlessStepFunctions', () => { | ||
serverlessStepFunctions.serverless.service.stepFunctions = { | ||
stateMachines: { | ||
first: { | ||
events: [ | ||
{ | ||
http: { | ||
method: 'POST', | ||
path: '/foo/bar', | ||
cors: { | ||
methods: ['POST'], | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
}; | ||
const validated = serverlessStepFunctions.httpValidate(); | ||
expect(validated.events).to.be.an('Array').with.length(1); | ||
expect(validated.events[0].http.cors.methods).to.deep.equal(['POST', 'OPTIONS']); | ||
}); | ||
}); |
@@ -10,2 +10,3 @@ 'use strict'; | ||
const httpMethods = require('./deploy/events/apiGateway/methods'); | ||
const httpCors = require('./deploy/events/apiGateway/cors'); | ||
const httpIamRole = require('./deploy/events/apiGateway/iamRole'); | ||
@@ -40,2 +41,3 @@ const httpDeployment = require('./deploy/events/apiGateway/deployment'); | ||
httpMethods, | ||
httpCors, | ||
httpIamRole, | ||
@@ -105,2 +107,3 @@ httpDeployment, | ||
.then(this.compileMethods) | ||
.then(this.compileCors) | ||
.then(this.compileHttpIamRole) | ||
@@ -107,0 +110,0 @@ .then(this.compileDeployment); |
@@ -98,2 +98,4 @@ 'use strict'; | ||
.stub(serverlessStepFunctions, 'compileMethods').returns(BbPromise.resolve()); | ||
const compileCorsStub = sinon | ||
.stub(serverlessStepFunctions, 'compileCors').returns(BbPromise.resolve()); | ||
const compileHttpIamRoleStub = sinon | ||
@@ -109,2 +111,3 @@ .stub(serverlessStepFunctions, 'compileHttpIamRole').returns(BbPromise.resolve()); | ||
expect(compileMethodsStub.notCalled).to.be.equal(true); | ||
expect(compileCorsStub.notCalled).to.be.equal(true); | ||
expect(compileHttpIamRoleStub.notCalled).to.be.equal(true); | ||
@@ -116,2 +119,3 @@ expect(compileDeploymentStub.notCalled).to.be.equal(true); | ||
serverlessStepFunctions.compileMethods.restore(); | ||
serverlessStepFunctions.compileCors.restore(); | ||
serverlessStepFunctions.compileHttpIamRole.restore(); | ||
@@ -132,2 +136,4 @@ serverlessStepFunctions.compileDeployment.restore(); | ||
.stub(serverlessStepFunctions, 'compileMethods').returns(BbPromise.resolve()); | ||
const compileCorsStub = sinon | ||
.stub(serverlessStepFunctions, 'compileCors').returns(BbPromise.resolve()); | ||
const compileHttpIamRoleStub = sinon | ||
@@ -143,3 +149,4 @@ .stub(serverlessStepFunctions, 'compileHttpIamRole').returns(BbPromise.resolve()); | ||
expect(compileMethodsStub.calledAfter(compileResourcesStub)).to.be.equal(true); | ||
expect(compileHttpIamRoleStub.calledAfter(compileMethodsStub)).to.be.equal(true); | ||
expect(compileCorsStub.calledAfter(compileMethodsStub)).to.be.equal(true); | ||
expect(compileHttpIamRoleStub.calledAfter(compileCorsStub)).to.be.equal(true); | ||
expect(compileDeploymentStub.calledAfter(compileHttpIamRoleStub)).to.be.equal(true); | ||
@@ -146,0 +153,0 @@ |
{ | ||
"name": "serverless-step-functions", | ||
"version": "1.0.5", | ||
"version": "1.1.0", | ||
"description": "The module is AWS Step Functions plugin for Serverless Framework", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -118,2 +118,43 @@ [![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com) [![Build Status](https://travis-ci.org/horike37/serverless-step-functions.svg?branch=master)](https://travis-ci.org/horike37/serverless-step-functions) [![npm version](https://badge.fury.io/js/serverless-step-functions.svg)](https://badge.fury.io/js/serverless-step-functions) [![Coverage Status](https://coveralls.io/repos/github/horike37/serverless-step-functions/badge.svg?branch=master)](https://coveralls.io/github/horike37/serverless-step-functions?branch=master) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) | ||
#### Enabling CORS | ||
To set CORS configurations for your HTTP endpoints, simply modify your event configurations as follows: | ||
```yml | ||
stepFunctions: | ||
stateMachines: | ||
hello: | ||
events: | ||
- http: | ||
path: posts/create | ||
method: POST | ||
cors: true | ||
definition: | ||
``` | ||
Setting cors to true assumes a default configuration which is equivalent to: | ||
```yml | ||
stepFunctions: | ||
stateMachines: | ||
hello: | ||
events: | ||
- http: | ||
path: posts/create | ||
method: POST | ||
cors: | ||
origin: '*' | ||
headers: | ||
- Content-Type | ||
- X-Amz-Date | ||
- Authorization | ||
- X-Api-Key | ||
- X-Amz-Security-Token | ||
- X-Amz-User-Agent | ||
allowCredentials: false | ||
definition: | ||
``` | ||
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. | ||
#### Send request to an API | ||
@@ -120,0 +161,0 @@ You can input an value as json in request body, the value is passed as the input value of your statemachine |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
148575
41
3551
444