Skip to content

Commit

Permalink
fix: event webhook interface to process raw body (#1153)
Browse files Browse the repository at this point in the history
  • Loading branch information
eshanholtz authored Jun 23, 2020
1 parent 0c0bd1c commit 865f09d
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 12 deletions.
43 changes: 43 additions & 0 deletions docs/use-cases/event-webhook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
First, follow the guide to [set up signature verification for event webhooks](https://sendgrid.com/docs/for-developers/tracking-events/getting-started-event-webhook-security-features/)

An example of a server to process incoming event webhooks:
```javascript
const bodyParser = require("body-parser");
const express = require('express');
const functions = require("firebase-functions");
const app = express();

const {EventWebhook, EventWebhookHeader} = require('@sendgrid/eventwebhook');

const verifyRequest = function (publicKey, payload, signature, timestamp) {
const eventWebhook = EventWebhook();
const ecPublicKey = eventWebhook.convertPublicKeyToECDSA(publicKey);
return eventWebhook.verifySignature(ecPublicKey, payload, signature, timestamp);
}

// Using bodyParser middleware to process the body
app.use(bodyParser.text({ type: 'application/json' }));

app.post("/sendgrid/webhook", async (req, resp) => {
try {
const key = '<your_public_key>';
// Alternatively, you can get your key from your firebase function cloud config
// const key = getConfig().sendgrid.webhook_verification_key;

const signature = req.get(EventWebhookHeader.SIGNATURE());
const timestamp = req.get(EventWebhookHeader.TIMESTAMP());

const requestBody = req.body;
// Alternatively, if using firebase cloud functions, remove the middleware and use:
// const requestBody = (req as functions.https.Request).rawBody;

if (verifyRequest(key, requestBody, signature, timestamp)) {
resp.send(204);
} else {
resp.send(403);
}
} catch (error) {
resp.status(500).send(error);
}
})
```
7 changes: 5 additions & 2 deletions packages/eventwebhook/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import EventWebhook = require('./src/eventwebhook');
import {EventWebhook, EventWebhookHeader} = require('./src/eventwebhook');

export = EventWebhook;
export {
EventWebhook,
EventWebhookHeader
};
4 changes: 2 additions & 2 deletions packages/eventwebhook/src/eventwebhook.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ declare class EventWebhook {
/**
*
* @param {PublicKey} publicKey elliptic curve public key
* @param {object|string} payload event payload in the request body
* @param {string|Buffer} payload event payload in the request body
* @param {string} signature value obtained from the 'X-Twilio-Email-Event-Webhook-Signature' header
* @param {string} timestamp value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header
* @return {Boolean} true or false if signature is valid
*/
verifySignature(publicKey: PublicKey, payload: object|string, signature: string, timestamp: string): boolean;
verifySignature(publicKey: PublicKey, payload: string|Buffer, signature: string, timestamp: string): boolean;
}

export = EventWebhook;
23 changes: 20 additions & 3 deletions packages/eventwebhook/src/eventwebhook.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,35 @@ class EventWebhook {
* Verify signed event webhook requests.
*
* @param {PublicKey} publicKey elliptic curve public key
* @param {Object|string} payload event payload in the request body
* @param {string|Buffer} payload event payload in the request body
* @param {string} signature value obtained from the 'X-Twilio-Email-Event-Webhook-Signature' header
* @param {string} timestamp value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header
* @return {Boolean} true or false if signature is valid
*/
verifySignature(publicKey, payload, signature, timestamp) {
let timestampPayload = typeof payload === 'object' ? JSON.stringify(payload) : payload;
let timestampPayload = Buffer.isBuffer(payload) ? payload.toString() : payload;
timestampPayload = timestamp + timestampPayload;
const decodedSignature = Signature.fromBase64(signature);

return Ecdsa.verify(timestampPayload, decodedSignature, publicKey);
}
}

module.exports = EventWebhook;
/*
* This class lists headers that get posted to the webhook. Read the docs for
* more details: https://sendgrid.com/docs/for-developers/tracking-events/event
*/
class EventWebhookHeader {
static SIGNATURE() {
return 'X-Twilio-Email-Event-Webhook-Signature';
}

static TIMESTAMP() {
return 'X-Twilio-Email-Event-Webhook-Timestamp';
}
}

module.exports = {
EventWebhook,
EventWebhookHeader,
};
16 changes: 13 additions & 3 deletions packages/eventwebhook/src/eventwebhook.spec.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
const EventWebhook = require('./eventwebhook');
const {EventWebhook, EventWebhookHeader} = require('./eventwebhook');

describe('EventWebhook', () => {
const PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA==';
const SIGNATURE = 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0=';
const TIMESTAMP = '1588788367';
const PAYLOAD = {
const PAYLOAD = JSON.stringify({
category: 'example_payload',
event: 'test_event',
message_id: 'message_id',
};
});

describe('#verifySignature()', () => {
it('should verify a valid signature', () => {
Expand Down Expand Up @@ -58,6 +58,16 @@ describe('EventWebhook', () => {
});
});

describe('EventWebhookHeader', () => {
it('sets the signature header', () => {
expect(EventWebhookHeader.SIGNATURE()).to.equal('X-Twilio-Email-Event-Webhook-Signature');
});

it('sets the timestamp header', () => {
expect(EventWebhookHeader.TIMESTAMP()).to.equal('X-Twilio-Email-Event-Webhook-Timestamp');
});
});

function verify(publicKey, payload, signature, timestamp) {
const ew = new EventWebhook();
const key = ew.convertPublicKeyToECDSA(publicKey);
Expand Down
4 changes: 2 additions & 2 deletions test/typescript/eventwebhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ var ew = new EventWebhook();
const PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA==";
const SIGNATURE = "MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0=";
const TIMESTAMP = "1588788367";
const PAYLOAD = {
const PAYLOAD = JSON.stringify({
event: 'test_event',
category: 'example_payload',
message_id: 'message_id',
};
});
var key = ew.convertPublicKeyToECDSA(PUBLIC_KEY);
console.log(ew.verifySignature(key, PAYLOAD, SIGNATURE, TIMESTAMP));

0 comments on commit 865f09d

Please sign in to comment.