mirror of
https://github.com/serverless/serverless.git
synced 2026-01-25 15:07:39 +00:00
315 lines
11 KiB
JavaScript
315 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
const BbPromise = require('bluebird');
|
|
const _ = require('lodash');
|
|
|
|
module.exports = {
|
|
compileMethods() {
|
|
const corsConfig = {};
|
|
_.forEach(this.serverless.service.functions, (functionObject, functionName) => {
|
|
functionObject.events.forEach(event => {
|
|
if (event.http) {
|
|
let method;
|
|
let path;
|
|
|
|
if (typeof event.http === 'object') {
|
|
method = event.http.method;
|
|
path = event.http.path;
|
|
} else if (typeof event.http === 'string') {
|
|
method = event.http.split(' ')[0];
|
|
path = event.http.split(' ')[1];
|
|
} else {
|
|
const errorMessage = [
|
|
`HTTP event of function ${functionName} is not an object nor a string.`,
|
|
' The correct syntax is: http: get users/list',
|
|
' OR an object with "path" and "method" properties.',
|
|
' Please check the docs for more info.',
|
|
].join('');
|
|
throw new this.serverless.classes
|
|
.Error(errorMessage);
|
|
}
|
|
|
|
// Setup cors.
|
|
let cors;
|
|
let corsEnabled = false;
|
|
if (!!event.http.cors) {
|
|
corsEnabled = true;
|
|
const headers = [
|
|
'Content-Type',
|
|
'X-Amz-Date',
|
|
'Authorization',
|
|
'X-Api-Key',
|
|
'X-Amz-Security-Token'];
|
|
|
|
cors = {
|
|
origins: ['*'],
|
|
methods: ['OPTIONS'],
|
|
headers,
|
|
};
|
|
|
|
if (typeof event.http.cors === 'object') {
|
|
cors = event.http.cors;
|
|
cors.methods = [];
|
|
if (cors.headers && !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') > -1) {
|
|
cors.methods.push('OPTIONS');
|
|
}
|
|
|
|
if (!cors.methods.indexOf(method.toUpperCase()) > -1) {
|
|
cors.methods.push(method.toUpperCase());
|
|
}
|
|
} else {
|
|
cors.methods.push(method.toUpperCase());
|
|
}
|
|
|
|
if (corsConfig[path]) {
|
|
corsConfig[path] = _.merge(corsConfig[path], cors);
|
|
} else {
|
|
corsConfig[path] = cors;
|
|
}
|
|
}
|
|
|
|
const resourceLogicalId = this.resourceLogicalIds[path];
|
|
const normalizedMethod = method[0].toUpperCase() +
|
|
method.substr(1).toLowerCase();
|
|
const extractedResourceId = resourceLogicalId.match(/ResourceApigEvent(.*)/)[1];
|
|
|
|
// universal velocity template
|
|
// provides
|
|
// `{body, method, principalId, stage, headers, query, path, identity, stageVariables} = event`
|
|
// as js objects
|
|
const DEFAULT_JSON_REQUEST_TEMPLATE = `
|
|
#define( $loop )
|
|
{
|
|
#foreach($key in $map.keySet())
|
|
"$util.escapeJavaScript($key)":
|
|
"$util.escapeJavaScript($map.get($key))"
|
|
#if( $foreach.hasNext ) , #end
|
|
#end
|
|
}
|
|
#end
|
|
{
|
|
"body": $input.json("$"),
|
|
"method": "$context.httpMethod",
|
|
"principalId": "$context.authorizer.principalId",
|
|
"stage": "$context.stage",
|
|
|
|
#set( $map = $input.params().header )
|
|
"headers": $loop,
|
|
|
|
#set( $map = $input.params().querystring )
|
|
"query": $loop,
|
|
|
|
#set( $map = $input.params().path )
|
|
"path": $loop,
|
|
|
|
#set( $map = $context.identity )
|
|
"identity": $loop,
|
|
|
|
#set( $map = $stageVariables )
|
|
"stageVariables": $loop
|
|
}
|
|
`;
|
|
|
|
const allowOrigin = '"method.response.header.Access-Control-Allow-Origin"';
|
|
const corsMethodResponseTemplate =
|
|
corsEnabled ? `${allowOrigin} : ${allowOrigin}` : '';
|
|
|
|
const corsMethodIntegrationTemplate =
|
|
corsEnabled ? `${allowOrigin}: "'${cors.origins.join(',')}'"` : '';
|
|
|
|
const methodTemplate = `
|
|
{
|
|
"Type" : "AWS::ApiGateway::Method",
|
|
"Properties" : {
|
|
"AuthorizationType" : "NONE",
|
|
"HttpMethod" : "${method.toUpperCase()}",
|
|
"MethodResponses" : [
|
|
{
|
|
"ResponseModels" : {},
|
|
"ResponseParameters" : {
|
|
${corsMethodResponseTemplate}
|
|
},
|
|
"StatusCode" : "200"
|
|
}
|
|
],
|
|
"RequestParameters" : {},
|
|
"Integration" : {
|
|
"IntegrationHttpMethod" : "POST",
|
|
"Type" : "AWS",
|
|
"Uri" : {
|
|
"Fn::Join": [ "",
|
|
[
|
|
"arn:aws:apigateway:",
|
|
{"Ref" : "AWS::Region"},
|
|
":lambda:path/2015-03-31/functions/",
|
|
{"Fn::GetAtt" : ["${functionName}", "Arn"]},
|
|
"/invocations"
|
|
]
|
|
]
|
|
},
|
|
"RequestTemplates" : {
|
|
"application/json" : ${JSON.stringify(DEFAULT_JSON_REQUEST_TEMPLATE)}
|
|
},
|
|
"IntegrationResponses" : [
|
|
{
|
|
"StatusCode" : "200",
|
|
"ResponseParameters" : {
|
|
${corsMethodIntegrationTemplate}
|
|
},
|
|
"ResponseTemplates" : {
|
|
"application/json": ""
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"ResourceId" : { "Ref": "${resourceLogicalId}" },
|
|
"RestApiId" : { "Ref": "RestApiApigEvent" }
|
|
}
|
|
}
|
|
`;
|
|
|
|
const methodTemplateJson = JSON.parse(methodTemplate);
|
|
|
|
// set authorizer config if available
|
|
if (event.http.authorizer) {
|
|
let authorizerName;
|
|
if (typeof event.http.authorizer === 'string') {
|
|
if (event.http.authorizer.indexOf(':') === -1) {
|
|
authorizerName = event.http.authorizer;
|
|
} else {
|
|
const authorizerArn = event.http.authorizer;
|
|
const splittedAuthorizerArn = authorizerArn.split(':');
|
|
const splittedLambdaName = splittedAuthorizerArn[splittedAuthorizerArn
|
|
.length - 1].split('-');
|
|
authorizerName = splittedLambdaName[splittedLambdaName.length - 1];
|
|
}
|
|
} else if (typeof event.http.authorizer === 'object') {
|
|
if (event.http.authorizer.arn) {
|
|
const authorizerArn = event.http.authorizer.arn;
|
|
const splittedAuthorizerArn = authorizerArn.split(':');
|
|
const splittedLambdaName = splittedAuthorizerArn[splittedAuthorizerArn
|
|
.length - 1].split('-');
|
|
authorizerName = splittedLambdaName[splittedLambdaName.length - 1];
|
|
} else if (event.http.authorizer.name) {
|
|
authorizerName = event.http.authorizer.name;
|
|
}
|
|
}
|
|
|
|
const AuthorizerLogicalId = `${authorizerName}Authorizer`;
|
|
|
|
methodTemplateJson.Properties.AuthorizationType = 'CUSTOM';
|
|
methodTemplateJson.Properties.AuthorizerId = {
|
|
Ref: AuthorizerLogicalId,
|
|
};
|
|
methodTemplateJson.DependsOn = AuthorizerLogicalId;
|
|
}
|
|
|
|
if (event.http.private) methodTemplateJson.Properties.ApiKeyRequired = true;
|
|
|
|
const methodObject = {
|
|
[`${normalizedMethod}MethodApigEvent${extractedResourceId}`]:
|
|
methodTemplateJson,
|
|
};
|
|
|
|
_.merge(this.serverless.service.resources.Resources,
|
|
methodObject);
|
|
|
|
// store a method logical id in memory to be used
|
|
// by Deployment resources "DependsOn" property
|
|
if (this.methodDependencies) {
|
|
this.methodDependencies
|
|
.push(`${normalizedMethod}MethodApigEvent${extractedResourceId}`);
|
|
} else {
|
|
this.methodDependencies =
|
|
[`${normalizedMethod}MethodApigEvent${extractedResourceId}`];
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// If no paths have CORS settings, then CORS isn't required.
|
|
if (!_.isEmpty(corsConfig)) {
|
|
const allowOrigin = '"method.response.header.Access-Control-Allow-Origin"';
|
|
const allowHeaders = '"method.response.header.Access-Control-Allow-Headers"';
|
|
const allowMethods = '"method.response.header.Access-Control-Allow-Methods"';
|
|
|
|
const preflightMethodResponse = `
|
|
${allowOrigin}: true,
|
|
${allowHeaders}: true,
|
|
${allowMethods}: true
|
|
`;
|
|
|
|
let configIndex = 0;
|
|
_.forOwn(corsConfig, (config, path) => {
|
|
const resourceLogicalId = this.resourceLogicalIds[path];
|
|
const preflightIntegrationResponse =
|
|
`
|
|
${allowOrigin}: "'${config.origins.join(',')}'",
|
|
${allowHeaders}: "'${config.headers.join(',')}'",
|
|
${allowMethods}: "'${config.methods.join(',')}'"
|
|
`;
|
|
|
|
const preflightTemplate = `
|
|
{
|
|
"Type" : "AWS::ApiGateway::Method",
|
|
"Properties" : {
|
|
"AuthorizationType" : "NONE",
|
|
"HttpMethod" : "OPTIONS",
|
|
"MethodResponses" : [
|
|
{
|
|
"ResponseModels" : {},
|
|
"ResponseParameters" : {
|
|
${preflightMethodResponse}
|
|
},
|
|
"StatusCode" : "200"
|
|
}
|
|
],
|
|
"RequestParameters" : {},
|
|
"Integration" : {
|
|
"Type" : "MOCK",
|
|
"RequestTemplates" : {
|
|
"application/json": "{statusCode:200}"
|
|
},
|
|
"IntegrationResponses" : [
|
|
{
|
|
"StatusCode" : "200",
|
|
"ResponseParameters" : {
|
|
${preflightIntegrationResponse}
|
|
},
|
|
"ResponseTemplates" : {
|
|
"application/json": ""
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"ResourceId" : { "Ref": "${resourceLogicalId}" },
|
|
"RestApiId" : { "Ref": "RestApiApigEvent" }
|
|
}
|
|
}
|
|
`;
|
|
|
|
const preflightObject = {
|
|
[`PreflightMethodApigEvent${configIndex++}`]:
|
|
JSON.parse(preflightTemplate),
|
|
};
|
|
|
|
_.merge(this.serverless.service.resources.Resources,
|
|
preflightObject);
|
|
});
|
|
}
|
|
|
|
return BbPromise.resolve();
|
|
},
|
|
};
|