Unable to parse JSON response from SQS in Lambda node.js function - node.js

I am trying to get some data via Webhook into lambda function using API Gateway and SQS. This Webhook contains a JSON payload, and HTTP headers that provide context. Since there is no way to get these webhook HTTP headers from API Gateway to SQS using "Action=SendMessage" in Mapping template (correct me if my assumption is wrong...I have tried http headers in method request and integration request), I have included the headers from webhook in the API Gateway mapping template as follows:
Action=SendMessage&MessageGroupId=$input.params('MessageGroupId')&
MessageBody={
"Header1":"$input.params('Header1 key')",
"body":"$input.json('$')"
}
My code in Lambda function is as follows:
'use strict';
exports.handler = async function(event, context) {
if (event.Records) {
event.Records.forEach(record => {
var rec = JSON.parse(JSON.stringify(record.body));
console.log("Record body: " + rec);
console.log("Header1: " + rec.Header1);
});
}
return {};
};
The value of 'rec' in CloudWatch log is rather quite long but here is the simplified version:
Record body: {
"Header1":" OYqmDnuUpDs8oF1RwoBnJywBY6c1I4qLklU=",
"body":"{"id":820982911946154508,"email":"jon#doe.ca","closed_at":null,"created_at":"2020-11-30T20:25:43-05:00","updated_at":"2020-11-30T20:25:43-05:00","number":234,"note":null,"token":"123456abcd","gateway":null,"test":true,"total_price":"7.00","subtotal_price":"-3.00"
}
My question - how can I read the values of Header1 and body? I have tried rec.Header1, rec[0].Header1 and rec["Header1"] etc. and I get "Undefined" in all the cases. Thanks.

The body is already stringified.
You need to simply call JSON.parse(record.body) to parse the string to valid JSON. Note that this could throw an error.

Related

validateRequest method in Node.js Twilio client library not validating request while running in AWS Lambda

I am trying to validate that an http POST request to an AWS Lamdbda function URL from a Twilio HTTP Request widget inside a Twilio Studio flow truly originated from Twilio. I am using the Node.js Twilio client library, which provides the validateRequest method to accomplish what I am after. The content-type header in the Twilio Studio flows HTTP Request widget is set to application/json
The problem is that I am passing in the "x-twilio-signature" header, the url, twilio auth token, and POST params to the validateRequest method and it always returns false. Below is the code snippet used to try and accomplish this.
const authToken = process.env.twilio_auth_token
const sid = process.env.twilio_account_sid
const client = require('twilio')
exports.handler = (event) =>
{
let twilioSignature = event.headers['x-twilio-signature']
let requestBody = event.body
let requestUrl = 'https://my-function-url.io/'
let requestIsValid = client.validateRequest(authToken, twilioSignature, requestUrl, requestBody)
if(requestIsValid){
console.log('valid request')
} else {
console.log('invalid request')
}
}
Seems like someone else had a similar issue in the past. I copied parts of the answer here:
The issue here is that query string parameters are treated differently to POST body parameters when generating the signature.
Notably part 3 of the steps used to generate the request signature says:
If your request is a POST, Twilio takes all the POST fields, sorts them by alphabetically by their name, and concatenates the parameter name and value to the end of the URL (with no delimiter).

Slack delayed message integration with Node TypeScript and Lambda

I started implementing a slash-command which kept evolving and eventually might hit the 3-second slack response limit. I am using serverless-stack with Node and TypeScript. With sst (and the vscode launchfile) it hooks and attaches the debugger into the lambda function which is pretty neat for debugging.
When hitting the api endpoint I tried various methods to send back an acknowledgement to slack, do my thing and send a delayed message back without success. I didnt have much luck finding info on this but one good source was this SO Answer - unfortunetly it didn't work. I didn't use request-promise since it's deprecated and tried to implement it with vanilla methods (maybe that's where i failed?). But also invoking a second lambda function from within (like in the first example of the post) didn't seem to be within the 3s limitation.
I am wondering if I am doing something wrong or if attachinf the debugger is just taking to long etc.
However, before attempting to send a delayed message it was fine including accessing and scaning dynamodb records, manipulating the results and then responding back to slack while debugger attached without hitting the timeout.
Attempting to use a post
export const answer: APIGatewayProxyHandlerV2 = async (
event: APIGatewayProxyEventV2, context, callback
) => {
const slack = decodeQueryStringAs<SlackRequest>(event.body);
axios.post(slack.response_url, {
text: "completed",
response_type: "ephemeral",
replace_original: "true"
});
return { statusCode: 200, body: '' };
}
The promise never resolved, i guess that once hitting return on the function the lambda function gets disposed and so the promise?
Invoking 2nd Lambda function
export const v2: APIGatewayProxyHandlerV2 = async (
event: APIGatewayProxyEventV2, context, callback
): Promise<any> => {
//tried with CB here and without
//callback(null, { statusCode: 200, body: 'processing' });
const slack = decodeQueryStringAs<SlackRequest>(event.body);
const originalMessage = slack.text;
const responseInfo = url.parse(slack.response_url)
const data = JSON.stringify({
...slack,
})
const lambda = new AWS.Lambda()
const params = {
FunctionName: 'dev-****-FC******SmE7',
InvocationType: 'Event', // Ensures asynchronous execution
Payload: data
}
return lambda.invoke(params).promise()// Returns 200 immediately after invoking the second lambda, not waiting for the result
.then(() => callback(null, { statusCode: 200, body: 'working on it' }))
};
Looking at the debugger logs it does send the 200 code and invokes the new lambda function though slack still times out.
Nothing special happens logic wise ... the current non-delayed-message implementation does much more logic wise (accessing DB and manipulating result data) and manages not to timeout.
Any suggestions or help is welcome.
Quick side note, I used request-promise in the linked SO question's answer since the JS native Promise object was not yet available on AWS Lambda's containers at the time.
There's a fundamental difference between the orchestration of the functions in the linked question and your own from what I understand but I think you have the same goal:
> Invoke an asynchronous operation from Slack which posts back to slack once it has a result
Here's the problem with your current approach: Slack sends a request to your (1st) lambda function, which returns a response to slack, and then invokes the second lambda function.
The slack event is no longer accepting responses once your first lambda returns the 200. Here lies the difference between your approach and the linked SO question.
The desired approach would sequentially look like this:
Slack sends a request to Lambda no. 1
Lambda no. 1 returns a 200 response to Slack
Lambda no. 1 invokes Lambda no. 2
Lambda no. 2 sends a POST request to a slack URL (google incoming webhooks for slack)
Slack receives the POST requests and displays it in the channel you chose for your webhook.
Code wise this would look like the following (without request-promise lol):
Lambda 1
module.exports = async (event, context) => {
// Invoking the second lambda function
const AWS = require('aws-sdk')
const lambda = new AWS.Lambda()
const params = {
FunctionName: 'YOUR_SECOND_FUNCTION_NAME',
InvocationType: 'Event', // Ensures asynchronous execution
Payload: JSON.stringify({
... your payload for lambda 2 ...
})
}
await lambda.invoke(params).promise() // Starts Lambda 2
return {
text: "working...",
response_type: "ephemeral",
replace_original: "true"
}
}
Lambda 2
module.exports = async (event, context) => {
// Use event (payload sent from Lambda 1) and do what you need to do
return axios.post('YOUR_INCOMING_WEBHOOK_URL', {
text: 'this will be sent to slack'
});
}

Parsing request body prohibits request signature verification

I'm trying to build a serverless Slackbot using Lambda function. I end up with an error while verifying the Request URL through the Slack event API. #slack/events-api is the dependency that I'm using to capture the slack events.
Here is my code.
const sls = require('serverless-http');
const { createEventAdapter } = require('#slack/events-api');
require('dotenv').config();
const { SLACK_SIGNING_SECRET } = process.env
const slackEvents = createEventAdapter( SLACK_SIGNING_SECRET || '' );
slackEvents.on('message', async event => {
console.log('received!')
});
module.exports.server = sls(slackEvents.requestListener());
This is the error that I'm getting while verifing the request url
Slack Request URL verification
Can someone help me with this?
Just ran into this exact issue, and took a look into http-handler.js in node-slack-events.
All we have to do is store the raw request body as rawBody before serverless-http parses it.
serverless-http lets you transform the request, before it is sent to the app—great opportunity for a fix:
module.exports.handler = serverless(app, {
request(request) {
request.rawBody = request.body;
},
});
I'm not sure how to solve your problem exactly, but I do know what's causing it.
The library you're using, serverless-http parses the JSON body sent by Slack. This causes an error to be thrown, because the slack-api-sdk expects to parse the raw request body itself.
Could you try removing serverless-http and just respond to the API Gateway event?

I want to change the URL from AWS CloudFront with the help of AWS lambda

I have this an URL, let's assume, "www.sample.com/hello". Now I have triggered a lambda function on viewer request where I just need to change the url to "www.sample.com/hello2". I did it using lambda edge functions but it is throwing me an error.
This is the code I wrote in lamda
const path = require('path');
exports.handler = (event, context, callback) => {
const cf = event.Records[0].cf;
const request = cf.request;
const response = cf.response;
const statusCode = response.status;
const path = request.uri;
const afterpath = path.substring(path.indexOf("/")+1);
if (afterpath == 'sample') {
request.uri = request.uri
.replace(afterpath,'samplepathitis')
}
return callback(null, request);
};
I am getting this error
503 ERROR
The request could not be satisfied.
The Lambda function associated with the CloudFront distribution is invalid
or doesn't have the required permissions.
If you received this error while trying to use an app or access a website,
please contact the provider or website owner for assistance.
If you provide content to customers through CloudFront, you can find steps
to troubleshoot and help prevent this error by following steps in the
CloudFront documentation
(http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/http-
503-service-unavailable.html).
Generated by cloudfront (CloudFront)
Request ID: MsN6aG8qvI9ttt3_VLhQAqpY8kF2pHk3V095lAFVU_sWmDvF3IfqAA==
As the error message states it clearly : either the function is invalid or there is no permission to call the function.
To check if the function is valid : try to invoke it from the Lamba console. Use the Test button. You will need to pass a request as input. The console will propose you sample request that you can adjust to simulate your use case.
Also very in the doc the return value of the function. Is request the correct return value expected by Cloudfront ?
Once you are sure about the two above, verify the permission to invoke that function. What is the trigger ? Is Cloudfront authorized to invoke your function ?

Kong service with POST request to Lambda function and JSON payload

I'm just starting with Kong and setup a Lambda plugin on a service to try things out. The Lambda function I use had a simple method to parse the JSON body:
const getBody = (event: any): IBody => {
const body = JSON.parse(event.body)
return new Body(body)
}
So, although I was able to call the function and get a response from it, all I got was an error message similar to:
{"status":500,"message":"SyntaxError: Unexpected token u in JSON
at position 0"}
This is due the fact a Lambda request is different when invoked from the cli and when called from AWS API Gateway.
Basically event.body is only available when calling from the API Gateway, whilst when called from the cli, the correct property name is event.request_body.
So modifiying the method to the one below will allow me to receive calls both from AWS API Gateway and cli:
const getBody = (event: any): IBody => {
const body = JSON.parse(Object.is(event.request_body, undefined) ? event.body : event.request_body)
return new Body(body)
}

Resources