mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
AWS API Gateway request body validation (#5956)
AWS API Gateway request body validation
This commit is contained in:
commit
b042ef02e9
@ -24,6 +24,7 @@ layout: Doc
|
||||
- [Setting API keys for your Rest API](#setting-api-keys-for-your-rest-api)
|
||||
- [Configuring endpoint types](#configuring-endpoint-types)
|
||||
- [Request Parameters](#request-parameters)
|
||||
- [Request Schema Validation](#request-schema-validation)
|
||||
- [Setting source of API key for metering requests](#setting-source-of-api-key-for-metering-requests)
|
||||
- [Lambda Integration](#lambda-integration)
|
||||
- [Example "LAMBDA" event (before customization)](#example-lambda-event-before-customization)
|
||||
@ -649,6 +650,50 @@ functions:
|
||||
id: true
|
||||
```
|
||||
|
||||
### Request Schema Validators
|
||||
|
||||
To use request schema validation with API gateway, add the [JSON Schema](https://json-schema.org/)
|
||||
for your content type. Since JSON Schema is represented in JSON, it's easier to include it from a
|
||||
file.
|
||||
|
||||
```yml
|
||||
functions:
|
||||
create:
|
||||
handler: posts.create
|
||||
events:
|
||||
- http:
|
||||
path: posts/create
|
||||
method: post
|
||||
request:
|
||||
schema:
|
||||
application/json: ${file(create_request.json)}
|
||||
```
|
||||
|
||||
A sample schema contained in `create_request.json` might look something like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"definitions": {},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "The Root Schema",
|
||||
"required": [
|
||||
"username"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string",
|
||||
"title": "The Foo Schema",
|
||||
"default": "",
|
||||
"pattern": "^[a-zA-Z0-9]+$"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**NOTE:** schema validators are only applied to content types you specify. Other content types are
|
||||
not blocked.
|
||||
|
||||
### Setting source of API key for metering requests
|
||||
|
||||
API Gateway provide a feature for metering your API's requests and you can choice [the source of key](https://docs.aws.amazon.com/apigateway/api-reference/resource/rest-api/#apiKeySource) which is used for metering. If you want to acquire that key from the request's X-API-Key header, set option like this:
|
||||
|
||||
@ -242,6 +242,13 @@ module.exports = {
|
||||
getMethodLogicalId(resourceId, methodName) {
|
||||
return `ApiGatewayMethod${resourceId}${this.normalizeMethodName(methodName)}`;
|
||||
},
|
||||
getValidatorLogicalId(resourceId, methodName) {
|
||||
return `${this.getMethodLogicalId(resourceId, methodName)}Validator`;
|
||||
},
|
||||
getModelLogicalId(resourceId, methodName, contentType) {
|
||||
return `${this.getMethodLogicalId(resourceId, methodName)}${_.startCase(
|
||||
contentType).replace(' ', '')}Model`;
|
||||
},
|
||||
getApiKeyLogicalId(apiKeyNumber, apiKeyName) {
|
||||
if (apiKeyName) {
|
||||
return `ApiGatewayApiKey${this.normalizeName(apiKeyName)}${apiKeyNumber}`;
|
||||
|
||||
@ -390,6 +390,22 @@ describe('#naming()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getValidatorLogicalId()', () => {
|
||||
it('', () => {
|
||||
expect(sdk.naming.getValidatorLogicalId(
|
||||
'ResourceId', 'get'
|
||||
)).to.equal('ApiGatewayMethodResourceIdGetValidator');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getModelLogicalId()', () => {
|
||||
it('', () => {
|
||||
expect(sdk.naming.getModelLogicalId(
|
||||
'ResourceId', 'get', 'application/json'
|
||||
)).to.equal('ApiGatewayMethodResourceIdGetApplicationJsonModel');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getApiKeyLogicalId(keyIndex)', () => {
|
||||
it('should produce the given index with ApiGatewayApiKey as a prefix', () => {
|
||||
expect(sdk.naming.getApiKeyLogicalId(1)).to.equal('ApiGatewayApiKey1');
|
||||
|
||||
@ -30,6 +30,8 @@ module.exports = {
|
||||
|
||||
const methodLogicalId = this.provider.naming
|
||||
.getMethodLogicalId(resourceName, event.http.method);
|
||||
const validatorLogicalId = this.provider.naming
|
||||
.getValidatorLogicalId(resourceName, event.http.method);
|
||||
const lambdaLogicalId = this.provider.naming
|
||||
.getLambdaLogicalId(event.functionName);
|
||||
|
||||
@ -67,6 +69,42 @@ module.exports = {
|
||||
|
||||
this.apiGatewayMethodLogicalIds.push(methodLogicalId);
|
||||
|
||||
if (event.http.request && event.http.request.schema) {
|
||||
for (const requestSchema of _.entries(event.http.request.schema)) {
|
||||
const contentType = requestSchema[0];
|
||||
const schema = requestSchema[1];
|
||||
|
||||
const modelLogicalId = this.provider.naming
|
||||
.getModelLogicalId(resourceName, event.http.method, contentType);
|
||||
|
||||
template.Properties.RequestValidatorId = { Ref: validatorLogicalId };
|
||||
template.Properties.RequestModels = { [contentType]: { Ref: modelLogicalId } };
|
||||
|
||||
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
|
||||
[modelLogicalId]: {
|
||||
Type: 'AWS::ApiGateway::Model',
|
||||
Properties: {
|
||||
RestApiId: {
|
||||
Ref: this.provider.naming.getRestApiLogicalId(),
|
||||
},
|
||||
ContentType: contentType,
|
||||
Schema: schema,
|
||||
},
|
||||
},
|
||||
[validatorLogicalId]: {
|
||||
Type: 'AWS::ApiGateway::RequestValidator',
|
||||
Properties: {
|
||||
RestApiId: {
|
||||
Ref: this.provider.naming.getRestApiLogicalId(),
|
||||
},
|
||||
ValidateRequestBody: true,
|
||||
ValidateRequestParameters: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, {
|
||||
[methodLogicalId]: template,
|
||||
});
|
||||
|
||||
@ -57,6 +57,54 @@ describe('#compileMethods()', () => {
|
||||
};
|
||||
});
|
||||
|
||||
it('should have request validators/models defined when they are set', () => {
|
||||
awsCompileApigEvents.validated.events = [
|
||||
{
|
||||
functionName: 'First',
|
||||
http: {
|
||||
path: 'users/create',
|
||||
method: 'post',
|
||||
integration: 'AWS',
|
||||
request: { schema: { 'application/json': { foo: 'bar' } } },
|
||||
},
|
||||
},
|
||||
];
|
||||
return awsCompileApigEvents.compileMethods().then(() => {
|
||||
expect(
|
||||
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.ApiGatewayMethodUsersCreatePostValidator
|
||||
).to.deep.equal({
|
||||
Type: 'AWS::ApiGateway::RequestValidator',
|
||||
Properties: {
|
||||
RestApiId: { Ref: 'ApiGatewayRestApi' },
|
||||
ValidateRequestBody: true,
|
||||
ValidateRequestParameters: true,
|
||||
},
|
||||
});
|
||||
expect(
|
||||
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.ApiGatewayMethodUsersCreatePostApplicationJsonModel
|
||||
).to.deep.equal({
|
||||
Type: 'AWS::ApiGateway::Model',
|
||||
Properties: {
|
||||
RestApiId: { Ref: 'ApiGatewayRestApi' },
|
||||
ContentType: 'application/json',
|
||||
Schema: { foo: 'bar' },
|
||||
},
|
||||
});
|
||||
expect(
|
||||
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.ApiGatewayMethodUsersCreatePost.Properties.RequestModels
|
||||
).to.deep.equal({
|
||||
'application/json': { Ref: 'ApiGatewayMethodUsersCreatePostApplicationJsonModel' },
|
||||
});
|
||||
expect(
|
||||
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate
|
||||
.Resources.ApiGatewayMethodUsersCreatePost.Properties.RequestValidatorId
|
||||
).to.deep.equal({ Ref: 'ApiGatewayMethodUsersCreatePostValidator' });
|
||||
});
|
||||
});
|
||||
|
||||
it('should have request parameters defined when they are set', () => {
|
||||
awsCompileApigEvents.validated.events = [
|
||||
{
|
||||
|
||||
@ -98,7 +98,9 @@ module.exports = {
|
||||
if (http.request) {
|
||||
const keys = Object.keys(http.request);
|
||||
const allowedKeys =
|
||||
http.integration === 'AWS_PROXY' ? ['parameters'] : ['parameters', 'uri'];
|
||||
http.integration === 'AWS_PROXY'
|
||||
? ['parameters', 'schema']
|
||||
: ['parameters', 'uri', 'schema'];
|
||||
|
||||
if (!_.isEmpty(_.difference(keys, allowedKeys))) {
|
||||
const requestWarningMessage = [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user