Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ Follow [quickstart](https://developers.facebook.com/docs/messenger-platform/quic
```
sls variables set -k PAGE_ACCESS_TOKEN
sls variables set -k VERIFY_TOKEN
sls variables set -k APP_SECRET
```
`VERIFY_TOKEN` is used for subsciption verification.
`APP_SECRET` is used as a sha1 key for signature verification.

### 4. Deploy backend app
Deploy all functions and endpoints
Expand Down
20 changes: 20 additions & 0 deletions __tests__/webhook.POST.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import imageReceivedPayload from './assets/imageReceivedPayload.json';
import audioReceivedPayload from './assets/audioReceivedPayload.json';
import locationReceivedPayload from './assets/locationReceivedPayload.json';
import videoReceivedPayload from './assets/videoReceivedPayload.json';
import crypto from 'crypto';

describe('Webhook handler on POST request', () => {
let sandbox;
Expand All @@ -30,6 +31,7 @@ describe('Webhook handler on POST request', () => {
sandbox.stub(messageDelivered, "handleMessageDelivered");
sandbox.stub(postback, "handlePostback");
sandbox.stub(attachmentsReceived, "handleAttachmentsReceived");
process.env.APP_SECRET = 'app secret'
});

afterEach(function() {
Expand All @@ -46,6 +48,8 @@ describe('Webhook handler on POST request', () => {
]
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledOnce(authentication.handleAuthentication);
Expand All @@ -72,6 +76,8 @@ describe('Webhook handler on POST request', () => {
]
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledOnce(textReceived.handleTextReceived);
Expand All @@ -96,6 +102,8 @@ describe('Webhook handler on POST request', () => {
"entry": textBatchReceivedPayload
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledThrice(textReceived.handleTextReceived);
Expand Down Expand Up @@ -136,6 +144,8 @@ describe('Webhook handler on POST request', () => {
]
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledOnce(attachmentsReceived.handleAttachmentsReceived);
Expand All @@ -162,6 +172,8 @@ describe('Webhook handler on POST request', () => {
]
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledOnce(attachmentsReceived.handleAttachmentsReceived);
Expand All @@ -188,6 +200,8 @@ describe('Webhook handler on POST request', () => {
]
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledOnce(attachmentsReceived.handleAttachmentsReceived);
Expand All @@ -214,6 +228,8 @@ describe('Webhook handler on POST request', () => {
]
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledOnce(attachmentsReceived.handleAttachmentsReceived);
Expand All @@ -240,6 +256,8 @@ describe('Webhook handler on POST request', () => {
]
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledOnce(messageDelivered.handleMessageDelivered);
Expand All @@ -266,6 +284,8 @@ describe('Webhook handler on POST request', () => {
]
}
};
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(JSON.stringify(event.payload)).digest('hex');
event.signature = 'sha1=' + signatureHash;

handler(event, null, (error, response) => {
sinon.assert.calledOnce(postback.handlePostback);
Expand Down
6 changes: 6 additions & 0 deletions functions/webhook/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {handleTextReceived} from './handlers/textReceived';
import {handleMessageDelivered} from './handlers/messageDelivered';
import {handlePostback} from './handlers/postback';
import {handleAttachmentsReceived} from './handlers/attachmentsReceived';
import {checkSignature} from './utils/checkSignature';

module.exports.handler = function(event, context, callback) {
if (event.httpMethod === 'GET' && event.hubMode === "subscribe" && event.hubVerifyToken && event.hubChallenge) {
Expand All @@ -24,6 +25,11 @@ module.exports.handler = function(event, context, callback) {
}

if (event.httpMethod === 'POST') {

if (!checkSignature(event.signature, event.payload)) {
return callback("Invalid signature");
}

if (process.env.LOG_WEBHOOK_MESSAGES === 'true') {
console.log(JSON.stringify(event.payload, null, ' '));
}
Expand Down
4 changes: 3 additions & 1 deletion functions/webhook/s-function.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"requestTemplates": {
"application/json": {
"payload": "$input.json(\"$\")",
"signature": "$input.params().header.get('X-Hub-Signature')",
"httpMethod": "$context.httpMethod"
}
},
Expand Down Expand Up @@ -85,7 +86,8 @@
"SERVERLESS_REGION": "${region}",
"VERIFY_TOKEN": "${VERIFY_TOKEN}",
"PAGE_ACCESS_TOKEN": "${PAGE_ACCESS_TOKEN}",
"LOG_WEBHOOK_MESSAGES": "${LOG_WEBHOOK_MESSAGES}"
"LOG_WEBHOOK_MESSAGES": "${LOG_WEBHOOK_MESSAGES}",
"APP_SECRET": "${APP_SECRET}"
},
"vpc": {
"securityGroupIds": [],
Expand Down
14 changes: 14 additions & 0 deletions functions/webhook/utils/checkSignature.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

import crypto from 'crypto';
import jsesc from 'jsesc';

export function checkSignature(signature, payload) {
payload = jsesc(payload, {
escapeEverything: false,
lowercaseHex: true,
json: true,
});
const signatureHash = crypto.createHmac('sha1', process.env.APP_SECRET).update(payload).digest('hex');
return signature.substr(5) === signatureHash;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the substr(5)? Could you add a comment please?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be also:
return signature === 'sha1=' + signatureHash;

Not sure which one is nicer

};
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"serverless-offline": "^2.2.10",
"serverless-webpack-plugin": "^0.4.1",
"sinon": "^1.17.3",
"webpack": "^1.13.0"
"webpack": "^1.13.0",
"crypto": "^0.0.3",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really need this package? It looks pretty unmaintained and Node has it's own crypto

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, sure: #3 (diff)

"jsesc": "^1.2.0"
}
}