Unable to validate Twilio request in Google cloud function - node.js

I have a Google cloud function to which Twilio sends POST requests with SMS statuses but I am unable to verify that the requests are coming from Twilio using any of the methods outlined in https://www.twilio.com/docs/usage/security
My first attempt consisted of using the validateRequest function, as shown in the code below
const twilio = require('twilio');
let url = 'https://....cloudfunctions.net/...'
let token = 'XXXX';
let header = request.headers['x-twilio-signature'];
let sortedKeys = Object.keys(request.body).sort();
let sortedParams = {};
sortedKeys.forEach(key => {
sortedParams[key] = request.body[key];
});
let validated = twilio.validateRequest(token, header, url, sortedParams);
I confirmed that the value of token matched the auth token from the Twilio account settings, sortedParams contained alphabetically sorted camel-cased Twilio request params and the url matched that which was passed to the Twilio client when creating the SMS. However, validateRequest would always return false.
My next attempt involved hashing the combination of the url and request params by copying the code from https://www.twilio.com/docs/libraries/reference/twilio-node/3.18.0/webhooks_webhooks.js.html
const crypto = require('crypto')
sortedKeys.forEach(key => {
url = `${url}${key}${request.body[key]}`;
});
let signature = crypto
.createHmac('sha1', token)
.update(Buffer.from(url, 'utf-8'))
.digest('base64');
Upon comparing the value of signature to that of the header, the two never matched.

Twilio developer evangelist here.
I recommend using the validateRequest method as that does most of the work for you.
You don't need to perform the parameter sorting that you've attempted, JavaScript objects are unordered and the library sorts and appends the parameters to the URL string already.
Things you need to check are that the URL is the exact webhook URL you set in your Twilio console, including the entire path and any query parameters that are included.
Also, have you ensured that request.body is populated and that your express app is using body-parser to parse the incoming request as url encoded form parameters?
app.use(bodyParser.urlencoded({ extended: false }));
If you are trying to validate the request as middleware, make sure that the request validation is done after body parsing.
Does any of that help at all?

It turns out that the there was nothing wrong with the validateRequest but rather the way I was declaring the token. Instead of hard-coding it in the function's code, it was being retrieved from a Google storage bucket as a buffer and then converted to a string. For unknown reasons, even though visually, the retrieved value matched the original token, a === comparison returned false. Once I hard-coded the token, everything worked.

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).

Access individual fields from request body inside cloud function

I'm sending a post request to a cloud function which contains the following body:
{message: "this is the message"}
If I try to print the entire body of the request, it shows it, but if I try to get the message field, I get undefined in the console.
Here's my function:
exports.myCloudFunction = functions.https.onRequest((req: any, res: any) => {
console.log(req.body)\\prints the body of the request fine
console.log(req.body.message)\\prints "undefined"
cors(req, res, () => {
const pl = req.body.message;
console.log(pl);\\prints "undefined"
});
return res.send("all done")
});
You don't need a body parser for Cloud Functions as described in the other answer here. Cloud Functions will automatically parse JSON and put the parsed JSON object in the body attribute. If you want this to happen automatically, you should set the content type of the request to "application/json" as described in the linked documentation. Then you can use req.body as you'd expect.
I personally haven't worked with firebase before, but from your code and the fact that req.body prints the body, it seems that you probably need to parse the request-body as json in order to be able to access the message property:
const body = JSON.parse(req.body);
console.log(body.message);
It could also be the case that you need to setup a bodyparser for json content. In a normal express-app you can do this using (note that you don't need the manual parsing from above anymore using bodyparser):
const bodyParser = require('body-parser');
app.use(bodyParser.json();
EDIT:
see Doug's answer for the correct way to do this, i.e. to fix your request and set the content-type to application/json in order for Cloud Functions to automatically parse and populate req.body with the request body.
You need to just convert the request body into a JSON object in cloud function.
const body = JSON.parse(req.body);
// message
console.log(body.message)

Gmail API nodejs messages.list labelIds not working

I am using node.js in a server to access a user's Gmail Inbox. The OAuth2 part works great, but when I try to specify the INBOX to get a message list, it seems to be ignored. I get the complete array of message ids, not just ones in the INBOX. In every attempt I get an array of the first 100 message ids, but the INBOX has only 5 messages. I've also tried with other labels such as 'UNREAD' with the same results.
It seems as though the 'labelIds' parameter is not being passed in the request (or is being ignored). Keep in mind that this is using the Node.js API without using Express.js.
Here are code snippets. What am I missing?
var http = require("http");
var url = require("url");
var fs = require('fs')
var google = require('googleapis');
var googleAuth = require('google-auth-library');
...
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
var gmail = google.gmail('v1');
gmail.users.messages.list ({
auth:oauth2Client,
userId:'me',
labelIds:"INBOX"}, function (err, result)
{
... etc.
User.message.list takes a label id. So first we search the users labels. Link
GET https://www.googleapis.com/gmail/v1/users/me/labels?fields=*&key={YOUR_API_KEY}
Response:
{
"id": "INBOX",
"name": "INBOX",
"messageListVisibility": "hide",
"labelListVisibility": "labelShow",
"type": "system"
},
So now we know the correct value to pass is INBOX link
GET https://www.googleapis.com/gmail/v1/users/me/messages?labelIds=INBOX&fields=*&key={YOUR_API_KEY}
Your code uses inbox when the proper value to pass is INBOX. You say you have tried this i suggest you try again and edit your question and post any errors you using INBOX should work.

How to sign a cookie manually using cookieParser?

For the sake of testing, I need to provide a signed cookie with HTTP request. So that, my Express app server can consider it as a signed cookie and put it into req.signedCookies object.
However I cannot find a appropriate method in docs.
I'd like to do the following:
let signed = cookieParser.signYourCookie({ cookieName: 'cookieValue' }, secretString);
// => cookieName=cookieValue.9PuJzypXeGq3tc2fFvlukjgNZ518jk
That is an operation opposite to cookieParser.signedCookie(str, secret) method. ExpressJS does it automatically under the hood, but there is a need to sign a cookie manually sometimes and the method seems missing.
To explain why I need this. I use Chai-http and need to set a cookie with the request. And I need it to be a signed cookie, so my server could find it it req.signedCookies object:
chai.request('http://foo.com')
.get('/url/path')
.set('my-signed-cookie', 'value-of-my-signed-cookie')
The plugin doesn't have public methods for that. Which is odd, actually. So I pulled the piece from plugin's code.
Do in your app:
var crypto = require('crypto');
function sign(val, secret){
return val + '.' + crypto
.createHmac('sha256', secret)
.update(val)
.digest('base64')
.replace(/=+$/, '');
};
// Pay attention to `s:` prefix. With that, plugin considers it as a signed cookie, apparently
.set('cookie', 'my-signed-cookie=s:' + sign('value-of-my-signed-cookie', 'my-cookie-secret'))
// Is equivalent to
.set('cookie', 'my-signed-cookie=s:value-of-my-signed-cookie.Dq+0CW44ZLfvzVWqEZEcK51X6auKaz771jFy4Zs4lWk')

Array as parameter in GET request in Postman

I have to send array of ids in GET request as paramenter.How can I test it in Postman(google chrome extension for API testing)?
The scenario is I have url, www.something.com/activity/poi_ids
poi_ids should conatain arrays of ids like [316,318]
At api side using express,
app.get('/activity/:poi_ids',function(req,res){
var poi_ids = req.params.poi_ids;
.......
.......
});
I have looked into it but it is only for post request
you can send them via query params ..
in your http query params assign all values to same variables like
GET activity?pid=12&pid=23&pid=34
and then inside your express get it like
var ids=req.query.pid; //[12,23,34]
It is unstructured text. If you want to "send an array" then you'll need to design some way to encode it and then write JavaScript to decode it.
For example:
GET /activity/316-318
and
var poi_ids = req.params.poi_ids.split("-");

Resources