get parameters from aws lambda post request - amazon-web-services

I'm trying to receive a POST from a twilio widget, but my aws lambda (nodejs) function fails.
{"message": "Could not parse request body into json: Unrecognized token \'RecordingSource\': was expecting (\'true\', \'false\' or \'null\')\n at [Source: (byte[])\"RecordingSource=RecordVerb&RecordingSid=REd9475d9sdfw616e81995366d5f02291506b0&RecordingUrl=https%3A%2F%2Fapi.twilio.com%2F2010-04-01%2FAccounts%2FAC87e46c891a699385%2FRecordings%2FREd9466d5f02291506b0&RecordingStatus=completed&RecordingChannels=1&ErrorCode=0&CallSid=CA4a7f45753ef87894245dc95d445d8672&RecordingStartTime=Sat%2C%2021%20Mar%202020%2014%3A50%3A32%20%2B0000&AccountSid=AC8799385&RecordingDuration=2\"; line: 1, column: 17]"}
My AWS lambda function is very simple.
exports.handler = async (event) => {
console.log('-------------------------');
console.log(event);
console.log('-------------------------');

Your twilio widget is sending in application/x-www-form-urlencoded but your server is attempting to handle application/json. To send json instead is a client side configuration, and your widget will have to support such a configuration.
As #Alan suggests,
you could set the Recording Status Callback to a Twilio Function URL, which can do what you need it to do. Twilio Functions uses Node/JavaScript.

Related

API Gateway Mapping Template for SNS not recognising TopicArn parameter

I have a POST method which integrates with SNS. It initially accepted a TopicArn query string parameter and a Message, however to avoid client applications having knowledge of the ARN, and to move the message content into the request body, I needed to make use of an API Gateway mapping template.
I added the below Velocity (VTL) code under an application/json mapping template (fake account ID):
TopicArn=$util.urlEncode('arn:aws:sns:eu-west-2:1234567890123:MyTopic')
&Subject=$util.urlEncode('Contact form message')
&Message=$util.urlEncode($input.body)
I ran a test using the console with a body of:
{
"name": "test",
"email": "test#test.com",
"message": "test message"
}
but this returns the error:
Invalid parameter: TopicArn or TargetArn Reason: no value for required parameter
Endpoint request body after transformations: TopicArn=arn%3Aaws%3Asns%3Aeu-west-2%1234567890123%3AMyTopic
&Subject=Contact+form+message
&Message=%7B%0A++++%22name%22%3A+%22test%22%2C%0A++++%22email%22%3A+%22test%40test.com%22%2C%0A++++%22message%22%3A+%22test+message%22%0A%7D
Sending request to https://sns.eu-west-2.amazonaws.com/?Action=Publish
Received response. Status: 400
I'm not particularly familiar with VTL, but I literally copied some example syntax from AWS docs yet the TopicArn parameter doesn't appear to be passed to SNS.
Can anyone see what I've done wrong here?

How to let API Gateway Proxy Error Responses?

I've got an API Gateway in front of a Lambda.
Successful responses from the Lambda (HTTP 2xx) have the response body forwarded.
However, for error responses (HTTP 5xx and others), the API Gateway transforms the response body using response templates.
Is there a way to avoid this? To have the original error response body from the Lambda?
In my Lambda I have this:
return callback(generalError, {
statusCode: 500,
headers:{
"content-type": "application/json"
},
body: JSON.stringify({
error: 'INTERNAL_ERROR',
description: error.message,
})
});
However, as output from the Gateway I get this:
{ "error": "Internal server error" }
Which doesn't match. The Lambdas response. It does match the response template in API Gateway:
{"message":$context.error.messageString}
However, is there a way to just proxy the original Lambda response instead of having this transformation in place?
I've found the reason why it doesn't work.
If you set a callback to a 500 error, including an object with an error field, somehow this will become the full response, regardless of the other output or real error.
Avoiding using an error field does the trick! I renamed it to serviceError and now I'm getting the responses I expected.

Making AWS API Gatway's response the lambda function response

Im trying to create a simple API gateway in which, with a POST method to a certain endpoint, a lambda function is executed.
Setting that up was easy enough, but I'm having some trouble with the request/response handling. Im sending the following request to the API Gateway (Im using python 3.7).
payload = {
"data": "something",
"data2": "sometsadas"
}
response = requests.post('https://endpoint.com/test', params = payload)
That endpoint activates a lambda function when accesed. That function just returns the same event it received.
import json
def lambda_handler(event, context):
# TODO implement
return event
How can I make it so the return value of my lambda function is actually the response from the request? (Or at least a way in which the return value can be found somewhere inside the response)
Seems it was a problem with how the information is sent, json format is required. Solved it by doing the following in the code.
payload{'data': 'someData'}
config_response = requests.post(endpointURL, data = json.dumps(config_payload))

Custom response Lambda Authorizer for 401

Calling the Lambda callback function from a Lambda Authorizer with the string Unauthorized in the error parameter returns a 401 response with the body:
{ "message": "Unauthorized" }
Trying to use any other string in the response results in the response:
{ "message": null }
If instead you return a Deny Policy Document in the result parameter of the callback, you'll get a 403 with the response something like:
{ "message": "Unable to access resource with an explicit deny" }
After looking around it seems you need to configure a Gateway Response to return a custom response from a Lambda Authorizer, which I have working for the 403 response, but can't figure out how to do this for a 401.
For the 403 I created a Gateway Response with the template:
{\"message\":\"$context.authorizer.stringKey\"}
Then on the result object I set the following
ResultObject.context.stringKey = 'My custom response'
This works and is documented here.
However, for the 401, because I am not returning a policy document I don't know how to use a custom response. I created the same Gateway Response as I did for the 403, but if I hit the callback with any string (other than 'Unauthorized') in the error param I get the null message. I can't return in the result param because this needs to be a response structure containing the Policy Document.
Any ideas on how I can return a custom response with a 401?
Sorry to not answer your direct question, but I do think people (like me) might encounter this thread when looking on how to implement the first part of your question (return a 401 response from the authorizer lambda). You can follow AWS example here.
TL;DR:
For async functions, throw an error whose message exactly match the string "Unauthorized":
exports.handler = async function (event) {
...
throw Error("Unauthorized");
}
For sync. functions, call the callback function with its first parameter (the error response) exactly match the string "Unauthorized":
exports.handler = function(event, context, callback) {
..
callback("Unauthorized"); // Compared to a successful response `callback(null, ...)`
}
In both cases the response from the API gateway endpoint protected by your authorizer lambda would be:
401
{
"message": "Unauthorized"
}
You need to raise an exception, so when using node:
context.fail("Unauthorized");
For C# see http://yogivalani.com/aws-custom-lambda-authorizer-returns-401-unauthorized/

How to get the HTTP method in AWS Lambda?

In an AWS Lambda code, how can I get the HTTP method (e.g. GET, POST...) of an HTTP request coming from the AWS Gateway API?
I understand from the documentation that context.httpMethod is the solution for that.
However, I cannot manage to make it work.
For instance, when I try to add the following 3 lines:
if (context.httpMethod) {
console.log('HTTP method:', context.httpMethod)
}
into the AWS sample code of the "microservice-http-endpoint" blueprint as follows:
exports.handler = function(event, context) {
if (context.httpMethod) {
console.log('HTTP method:', context.httpMethod)
}
console.log('Received event:', JSON.stringify(event, null, 2));
// For clarity, I have removed the remaining part of the sample
// provided by AWS, which works well, for instance when triggered
// with Postman through the API Gateway as an intermediary.
};
I never have anything in the log because httpMethod is always empty.
The context.httpMethod approach works only in templates. So, if you want to have access to the HTTP method in your Lambda function, you need to find the method in the API Gateway (e.g. GET), go to the Integration Request section, click on Mapping Templates, and add a new mapping template for application/json. Then, select the application/json and select Mapping Template and in the edit box enter something like:
{
"http_method": "$context.httpMethod"
}
Then, when your Lambda function is called, you should see a new attribute in the event passed in called http_method which contains the HTTP method used to invoke the function.
API Gateway now has a built-in mapping template that passes along stuff like http method, route, and a lot more. I can't embed because I don't have enough points, but you get the idea.
Here is a screenshot of how you add it in the API Gateway console:
To get there navigate to AWS Console > API Gateway > (select a resource, IE - GET /home) > Integration Request > Mapping Templates > Then click on application/json and select Method Request Passthrough from dropdown shown in the screenshot above
I had this problem when I created a template microservice-http-endpoint-python project from functions.
Since it creates an HTTP API Gateway, and only REST APIs have Mapping template I was not able to put this work. Only changing the code of Lambda.
Basically, the code does the same, but I am not using the event['httpMethod']
Please check this:
import boto3
import json
print('Loading function')
dynamo = boto3.client('dynamodb')
def respond(err, res=None):
return {
'statusCode': '400' if err else '200',
'body': err.message if err else json.dumps(res),
'headers': {
'Content-Type': 'application/json',
},
}
def lambda_handler(event, context):
'''Demonstrates a simple HTTP endpoint using API Gateway. You have full
access to the request and response payload, including headers and
status code.
To scan a DynamoDB table, make a GET request with the TableName as a
query string parameter. To put, update, or delete an item, make a POST,
PUT, or DELETE request respectively, passing in the payload to the
DynamoDB API as a JSON body.
'''
print("Received event: " + json.dumps(event, indent=2))
operations = {
'DELETE': lambda dynamo, x: dynamo.delete_item(**x),
'GET': lambda dynamo, x: dynamo.scan(**x),
'POST': lambda dynamo, x: dynamo.put_item(**x),
'PUT': lambda dynamo, x: dynamo.update_item(**x),
}
operation = event['requestContext']['http']['method']
if operation in operations:
payload = event['queryStringParameters'] if operation == 'GET' else json.loads(event['body'])
return respond(None, operations[operation](dynamo, payload))
else:
return respond(ValueError('Unsupported method "{}"'.format(operation)))
I changed the code from:
operation = event['httpMethod']
to
operation = event['requestContext']['http']['method']
How do I get this solution?
I simply returned the entire event, checked the JSON and put it to work with the correct format.
If event appears an empty object, make sure you enabled proxy integration for the method. Proxy integration for an HTTP method adds request information into event.
See Use Lambda Proxy integration on API Gateway page.
If you are using API gateway, http method will be automatically passed to the event parameter when the lambda is triggered.
export const handler: Handler<APIGatewayProxyEvent> = async (
event: APIGatewayEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
const httpMethod = event.httpMethod;
...
}