Skip to content

Commit 0832918

Browse files
committed
Enable HTTP endpoints authorizers
1 parent 7e8d331 commit 0832918

File tree

5 files changed

+252
-0
lines changed

5 files changed

+252
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
'use strict';
2+
3+
const BbPromise = require('bluebird');
4+
const _ = require('lodash');
5+
const awsArnRegExs = require('../../../utils/arnRegularExpressions');
6+
7+
module.exports = {
8+
compileAuthorizers() {
9+
this.pluginhttpValidated.events.forEach((event) => {
10+
if (event.http.authorizer && event.http.authorizer.arn) {
11+
const authorizer = event.http.authorizer;
12+
const authorizerProperties = {
13+
AuthorizerResultTtlInSeconds: authorizer.resultTtlInSeconds,
14+
IdentitySource: authorizer.identitySource,
15+
Name: authorizer.name,
16+
RestApiId: this.provider.getApiGatewayRestApiId(),
17+
};
18+
19+
if (typeof authorizer.identityValidationExpression === 'string') {
20+
_.assign(authorizerProperties, {
21+
IdentityValidationExpression: authorizer.identityValidationExpression,
22+
});
23+
}
24+
25+
const authorizerLogicalId = this.provider.naming.getAuthorizerLogicalId(authorizer.name);
26+
27+
if (typeof authorizer.arn === 'string'
28+
&& awsArnRegExs.cognitoIdpArnExpr.test(authorizer.arn)) {
29+
authorizerProperties.Type = 'COGNITO_USER_POOLS';
30+
authorizerProperties.ProviderARNs = [authorizer.arn];
31+
} else {
32+
authorizerProperties.AuthorizerUri =
33+
{
34+
'Fn::Join': ['',
35+
[
36+
'arn:',
37+
{ Ref: 'AWS::Partition' },
38+
':apigateway:',
39+
{ Ref: 'AWS::Region' },
40+
':lambda:path/2015-03-31/functions/',
41+
authorizer.arn,
42+
'/invocations',
43+
],
44+
],
45+
};
46+
authorizerProperties.Type = authorizer.type ? authorizer.type.toUpperCase() : 'TOKEN';
47+
}
48+
49+
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
50+
[authorizerLogicalId]: {
51+
Type: 'AWS::ApiGateway::Authorizer',
52+
Properties: authorizerProperties,
53+
},
54+
});
55+
}
56+
});
57+
return BbPromise.resolve();
58+
},
59+
};
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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+
9+
describe('#compileAuthorizers()', () => {
10+
let awsCompileApigEvents;
11+
12+
beforeEach(() => {
13+
const serverless = new Serverless();
14+
serverless.setProvider('aws', new AwsProvider(serverless));
15+
serverless.service.service = 'first-service';
16+
serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} };
17+
awsCompileApigEvents = new ServerlessStepFunctions(serverless);
18+
awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi';
19+
awsCompileApigEvents.pluginhttpValidated = {};
20+
});
21+
22+
it('should create an authorizer with minimal configuration', () => {
23+
awsCompileApigEvents.pluginhttpValidated.events = [{
24+
http: {
25+
path: 'users/create',
26+
method: 'POST',
27+
authorizer: {
28+
name: 'authorizer',
29+
arn: { 'Fn::GetAtt': ['SomeLambdaFunction', 'Arn'] },
30+
resultTtlInSeconds: 300,
31+
identitySource: 'method.request.header.Authorization',
32+
},
33+
},
34+
}];
35+
36+
return awsCompileApigEvents.compileAuthorizers().then(() => {
37+
const resource = awsCompileApigEvents.serverless.service.provider
38+
.compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
39+
40+
expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
41+
expect(resource.Properties.AuthorizerResultTtlInSeconds).to.equal(300);
42+
expect(resource.Properties.AuthorizerUri).to.deep.equal({
43+
'Fn::Join': ['',
44+
[
45+
'arn:',
46+
{ Ref: 'AWS::Partition' },
47+
':apigateway:',
48+
{ Ref: 'AWS::Region' },
49+
':lambda:path/2015-03-31/functions/',
50+
{ 'Fn::GetAtt': ['SomeLambdaFunction', 'Arn'] },
51+
'/invocations',
52+
],
53+
],
54+
});
55+
expect(resource.Properties.IdentitySource).to.equal('method.request.header.Authorization');
56+
expect(resource.Properties.IdentityValidationExpression).to.equal(undefined);
57+
expect(resource.Properties.Name).to.equal('authorizer');
58+
expect(resource.Properties.RestApiId.Ref).to.equal('ApiGatewayRestApi');
59+
expect(resource.Properties.Type).to.equal('TOKEN');
60+
});
61+
});
62+
63+
it('should create an authorizer with provided configuration', () => {
64+
awsCompileApigEvents.pluginhttpValidated.events = [{
65+
http: {
66+
path: 'users/create',
67+
method: 'POST',
68+
authorizer: {
69+
name: 'authorizer',
70+
arn: 'foo',
71+
resultTtlInSeconds: 500,
72+
identitySource: 'method.request.header.Custom',
73+
identityValidationExpression: 'regex',
74+
},
75+
},
76+
}];
77+
78+
return awsCompileApigEvents.compileAuthorizers().then(() => {
79+
const resource = awsCompileApigEvents.serverless.service.provider
80+
.compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
81+
82+
expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
83+
expect(resource.Properties.AuthorizerUri).to.deep.equal({
84+
'Fn::Join': ['',
85+
[
86+
'arn:',
87+
{ Ref: 'AWS::Partition' },
88+
':apigateway:',
89+
{ Ref: 'AWS::Region' },
90+
':lambda:path/2015-03-31/functions/',
91+
'foo',
92+
'/invocations',
93+
],
94+
],
95+
});
96+
expect(resource.Properties.AuthorizerResultTtlInSeconds).to.equal(500);
97+
expect(resource.Properties.IdentitySource).to.equal('method.request.header.Custom');
98+
expect(resource.Properties.IdentityValidationExpression).to.equal('regex');
99+
expect(resource.Properties.Name).to.equal('authorizer');
100+
expect(resource.Properties.RestApiId.Ref).to.equal('ApiGatewayRestApi');
101+
expect(resource.Properties.Type).to.equal('TOKEN');
102+
});
103+
});
104+
105+
it('should apply optional provided type value to Authorizer Type', () => {
106+
awsCompileApigEvents.pluginhttpValidated.events = [{
107+
http: {
108+
path: 'users/create',
109+
method: 'POST',
110+
authorizer: {
111+
name: 'authorizer',
112+
arn: 'foo',
113+
resultTtlInSeconds: 500,
114+
identityValidationExpression: 'regex',
115+
type: 'request',
116+
},
117+
},
118+
}];
119+
120+
return awsCompileApigEvents.compileAuthorizers().then(() => {
121+
const resource = awsCompileApigEvents.serverless.service.provider
122+
.compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
123+
124+
expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
125+
expect(resource.Properties.Type).to.equal('REQUEST');
126+
});
127+
});
128+
129+
it('should apply TOKEN as authorizer Type when not given a type value', () => {
130+
awsCompileApigEvents.pluginhttpValidated.events = [{
131+
http: {
132+
path: 'users/create',
133+
method: 'POST',
134+
authorizer: {
135+
name: 'authorizer',
136+
arn: 'foo',
137+
resultTtlInSeconds: 500,
138+
identityValidationExpression: 'regex',
139+
},
140+
},
141+
}];
142+
143+
return awsCompileApigEvents.compileAuthorizers().then(() => {
144+
const resource = awsCompileApigEvents.serverless.service.provider
145+
.compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
146+
147+
expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer');
148+
expect(resource.Properties.Type).to.equal('TOKEN');
149+
});
150+
});
151+
152+
it('should create a valid cognito user pool authorizer', () => {
153+
awsCompileApigEvents.pluginhttpValidated.events = [{
154+
http: {
155+
path: 'users/create',
156+
method: 'POST',
157+
authorizer: {
158+
name: 'authorizer',
159+
arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ',
160+
},
161+
},
162+
}];
163+
164+
return awsCompileApigEvents.compileAuthorizers().then(() => {
165+
const resource = awsCompileApigEvents.serverless.service.provider
166+
.compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer;
167+
168+
expect(resource.Properties.Name).to.equal('authorizer');
169+
170+
expect(resource.Properties.ProviderARNs[0]
171+
).to.equal('arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ');
172+
173+
expect(resource.Properties.Type).to.equal('COGNITO_USER_POOLS');
174+
});
175+
});
176+
});

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 httpAuthorizers = require('./deploy/events/apiGateway/authorizers');
1011
const httpCors = require('./deploy/events/apiGateway/cors');
1112
const httpApiKeys = require('./deploy/events/apiGateway/apiKeys');
1213
const httpUsagePlan = require('./deploy/events/apiGateway/usagePlan');
@@ -40,6 +41,7 @@ class ServerlessStepFunctions {
4041
httpValidate,
4142
httpResources,
4243
httpMethods,
44+
httpAuthorizers,
4345
httpCors,
4446
httpApiKeys,
4547
httpUsagePlan,
@@ -111,6 +113,7 @@ class ServerlessStepFunctions {
111113
.then(this.compileRestApi)
112114
.then(this.compileResources)
113115
.then(this.compileMethods)
116+
.then(this.compileAuthorizers)
114117
.then(this.compileCors)
115118
.then(this.compileHttpIamRole)
116119
.then(this.compileDeployment)

lib/index.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ describe('#index', () => {
101101
.stub(serverlessStepFunctions, 'compileResources').returns(BbPromise.resolve());
102102
const compileMethodsStub = sinon
103103
.stub(serverlessStepFunctions, 'compileMethods').returns(BbPromise.resolve());
104+
const compileAuthorizersStub = sinon
105+
.stub(serverlessStepFunctions, 'compileAuthorizers').returns(BbPromise.resolve());
104106
const compileCorsStub = sinon
105107
.stub(serverlessStepFunctions, 'compileCors').returns(BbPromise.resolve());
106108
const compileHttpIamRoleStub = sinon
@@ -120,6 +122,7 @@ describe('#index', () => {
120122
expect(compileRestApiStub.notCalled).to.be.equal(true);
121123
expect(compileResourcesStub.notCalled).to.be.equal(true);
122124
expect(compileMethodsStub.notCalled).to.be.equal(true);
125+
expect(compileAuthorizersStub.notCalled).to.be.equal(true);
123126
expect(compileCorsStub.notCalled).to.be.equal(true);
124127
expect(compileHttpIamRoleStub.notCalled).to.be.equal(true);
125128
expect(compileDeploymentStub.notCalled).to.be.equal(true);
@@ -131,6 +134,7 @@ describe('#index', () => {
131134
serverlessStepFunctions.compileRestApi.restore();
132135
serverlessStepFunctions.compileResources.restore();
133136
serverlessStepFunctions.compileMethods.restore();
137+
serverlessStepFunctions.compileAuthorizers.restore();
134138
serverlessStepFunctions.compileCors.restore();
135139
serverlessStepFunctions.compileHttpIamRole.restore();
136140
serverlessStepFunctions.compileDeployment.restore();
@@ -152,6 +156,8 @@ describe('#index', () => {
152156
.stub(serverlessStepFunctions, 'compileResources').returns(BbPromise.resolve());
153157
const compileMethodsStub = sinon
154158
.stub(serverlessStepFunctions, 'compileMethods').returns(BbPromise.resolve());
159+
const compileAuthorizersStub = sinon
160+
.stub(serverlessStepFunctions, 'compileAuthorizers').returns(BbPromise.resolve());
155161
const compileCorsStub = sinon
156162
.stub(serverlessStepFunctions, 'compileCors').returns(BbPromise.resolve());
157163
const compileHttpIamRoleStub = sinon
@@ -171,6 +177,7 @@ describe('#index', () => {
171177
expect(compileRestApiStub.calledOnce).to.be.equal(true);
172178
expect(compileResourcesStub.calledAfter(compileRestApiStub)).to.be.equal(true);
173179
expect(compileMethodsStub.calledAfter(compileResourcesStub)).to.be.equal(true);
180+
expect(compileAuthorizersStub.calledAfter(compileResourcesStub)).to.be.equal(true);
174181
expect(compileCorsStub.calledAfter(compileMethodsStub)).to.be.equal(true);
175182
expect(compileHttpIamRoleStub.calledAfter(compileCorsStub)).to.be.equal(true);
176183
expect(compileDeploymentStub.calledAfter(compileHttpIamRoleStub)).to.be.equal(true);
@@ -183,6 +190,7 @@ describe('#index', () => {
183190
serverlessStepFunctions.compileRestApi.restore();
184191
serverlessStepFunctions.compileResources.restore();
185192
serverlessStepFunctions.compileMethods.restore();
193+
serverlessStepFunctions.compileAuthorizers.restore();
186194
serverlessStepFunctions.compileHttpIamRole.restore();
187195
serverlessStepFunctions.compileDeployment.restore();
188196
serverlessStepFunctions.compileApiKeys.restore();

lib/utils/arnRegularExpressions.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
module.exports = {
4+
cognitoIdpArnExpr: /^arn:[a-zA-Z-]*:cognito-idp/,
5+
lambdaArnExpr: /arn:[a-zA-Z-]*:lambda/,
6+
};

0 commit comments

Comments
 (0)