diff --git a/.travis.yml b/.travis.yml index 1daa07b9b..bbc0df0c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ node_js: - 10 env: - version=6 - - version=7 - version=8 - version=10 - version=lts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6a2054a53..cefb15adf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ We welcome direct contributions to the sendgrid-nodejs code base. Thank you! ##### Prerequisites ##### -- Node.js version 6, 7 or 8 +- Node.js version 6, 8 or >=10 - Please see [package.json](https://github.com/sendgrid/sendgrid-nodejs/tree/master/package.json) ##### Initial setup: ##### diff --git a/docs/examples/webhooks-docker/CONTRIBUTING.md b/docs/examples/webhooks-docker/CONTRIBUTING.md index b4d4316e9..b05f2b2c2 100644 --- a/docs/examples/webhooks-docker/CONTRIBUTING.md +++ b/docs/examples/webhooks-docker/CONTRIBUTING.md @@ -63,7 +63,7 @@ We welcome direct contributions to the sendgrid-nodejs code base. Thank you! ##### Prerequisites ##### -- Node.js version 6, 7 or 8 +- Node.js versions 6, 8, or >=10 - Please see [package.json](https://github.com/sendgrid/sendgrid-nodejs/tree/master/package.json) ##### Initial setup: ##### diff --git a/package.json b/package.json index 8619f964e..2701f7f9c 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "test:helpers": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/helpers/**/*.spec.js\"", "test:client": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/client/**/*.spec.js\"", "test:mail": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/mail/**/*.spec.js\"", + "test:eventwebhook": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/eventwebhook/**/*.spec.js\"", "test:inbound": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/inbound-mail-parser/**/*.spec.js\"", "test:contact": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"packages/contact-importer/**/*.spec.js\"", "test:files": "babel-node ./node_modules/istanbul/lib/cli cover ./node_modules/mocha/bin/_mocha \"test/files.spec.js\"", diff --git a/packages/client/README.md b/packages/client/README.md index 8504c24b7..0afbc74b9 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -13,7 +13,7 @@ To be notified when this package is updated, please subscribe to email [notifica ## Prerequisites -- Node.js version 6, 7 or 8 +- Node.js version 6, 8 or >=10 - A Twilio SendGrid account, [sign up for free](https://sendgrid.com/free?source=sendgrid-nodejs) to send up to 40,000 emails for the first 30 days or check out [our pricing](https://sendgrid.com/pricing?source=sendgrid-nodejs). ## Obtain an API Key diff --git a/packages/client/package.json b/packages/client/package.json index 2967df1d4..3a643ac95 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -24,7 +24,7 @@ }, "main": "index.js", "engines": { - "node": ">=6.0.0" + "node": "6.* || 8.* || >=10.*" }, "dependencies": { "@sendgrid/helpers": "^7.0.1", diff --git a/packages/contact-importer/package.json b/packages/contact-importer/package.json index 87aff9109..8e13e61f4 100644 --- a/packages/contact-importer/package.json +++ b/packages/contact-importer/package.json @@ -20,7 +20,7 @@ }, "main": "src/importer.js", "engines": { - "node": ">=6.0.0" + "node": "6.* || 8.* || >=10.*" }, "publishConfig": { "access": "public" diff --git a/packages/eventwebhook/index.d.ts b/packages/eventwebhook/index.d.ts new file mode 100644 index 000000000..d2f6d68bf --- /dev/null +++ b/packages/eventwebhook/index.d.ts @@ -0,0 +1,3 @@ +import EventWebhook = require('./src/eventwebhook'); + +export = EventWebhook; diff --git a/packages/eventwebhook/index.js b/packages/eventwebhook/index.js new file mode 100644 index 000000000..44a6aaeaf --- /dev/null +++ b/packages/eventwebhook/index.js @@ -0,0 +1,5 @@ +'use strict'; + +const EventWebhook = require('./src/eventwebhook'); + +module.exports = EventWebhook; diff --git a/packages/eventwebhook/package.json b/packages/eventwebhook/package.json new file mode 100644 index 000000000..4ea546f02 --- /dev/null +++ b/packages/eventwebhook/package.json @@ -0,0 +1,28 @@ +{ + "name": "@sendgrid/eventwebhook", + "description": "Twilio SendGrid NodeJS Event Webhook", + "version": "1.0.0", + "author": "Twilio SendGrid (sendgrid.com)", + "contributors": [ + "Elise Shanholtz " + ], + "license": "MIT", + "homepage": "https://sendgrid.com", + "repository": { + "type": "git", + "url": "git://github.com/sendgrid/sendgrid-nodejs.git" + }, + "publishConfig": { + "access": "public" + }, + "main": "src/eventwebhook.js", + "engines": { + "node": "6.* || 8.* || >=10.*" + }, + "dependencies": { + "@starkbank/ecdsa": "^0.0.3" + }, + "tags": [ + "sendgrid" + ] +} diff --git a/packages/eventwebhook/src/eventwebhook.d.ts b/packages/eventwebhook/src/eventwebhook.d.ts new file mode 100644 index 000000000..0e5aeb814 --- /dev/null +++ b/packages/eventwebhook/src/eventwebhook.d.ts @@ -0,0 +1,22 @@ +import {PublicKey} from "@starkbank/ecdsa"; + +declare class EventWebhook { + /** + * + * @param {string} publicKey verification key under Mail Settings + * @return {PublicKey} A public key using the ECDSA algorithm + */ + convertPublicKeyToECDSA(publicKey: string): PublicKey; + + /** + * + * @param {PublicKey} publicKey elliptic curve public key + * @param {object|string} 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; +} + +export = EventWebhook; diff --git a/packages/eventwebhook/src/eventwebhook.js b/packages/eventwebhook/src/eventwebhook.js new file mode 100644 index 000000000..de2ddbaf3 --- /dev/null +++ b/packages/eventwebhook/src/eventwebhook.js @@ -0,0 +1,35 @@ +'use strict'; + +const ecdsa = require('@starkbank/ecdsa'); +const Ecdsa = ecdsa.Ecdsa; +const Signature = ecdsa.Signature; +const PublicKey = ecdsa.PublicKey; + +class EventWebhook { + /** + * + * @param {string} publicKey verification key under Mail Settings + * @return {PublicKey} A public key using the ECDSA algorithm + */ + convertPublicKeyToECDSA(publicKey) { + return PublicKey.fromPem(publicKey); + } + + /** + * + * @param {PublicKey} publicKey elliptic curve public key + * @param {Object|string} 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; + timestampPayload = timestamp + timestampPayload; + const decodedSignature = Signature.fromBase64(signature); + + return Ecdsa.verify(timestampPayload, decodedSignature, publicKey); + } +} + +module.exports = EventWebhook; diff --git a/packages/eventwebhook/src/eventwebhook.spec.js b/packages/eventwebhook/src/eventwebhook.spec.js new file mode 100644 index 000000000..8588b6a13 --- /dev/null +++ b/packages/eventwebhook/src/eventwebhook.spec.js @@ -0,0 +1,65 @@ +const EventWebhook = require('./eventwebhook'); + +describe('EventWebhook', () => { + const PUBLIC_KEY = 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA=='; + const SIGNATURE = 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0='; + const TIMESTAMP = '1588788367'; + const PAYLOAD = { + event: 'test_event', + category: 'example_payload', + message_id: 'message_id', + }; + + describe('#verifySignature()', () => { + it('should verify a valid signature', () => { + expect(verify( + PUBLIC_KEY, + PAYLOAD, + SIGNATURE, + TIMESTAMP + )).to.be.true; + }); + + it('should reject for invalid key', () => { + expect(verify( + 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqTxd43gyp8IOEto2LdIfjRQrIbsd4SXZkLW6jDutdhXSJCWHw8REntlo7aNDthvj+y7GjUuFDb/R1NGe1OPzpA==', + PAYLOAD, + SIGNATURE, + TIMESTAMP + )).to.be.false; + }); + + it('should reject for bad payload', () => { + expect(verify( + PUBLIC_KEY, + 'payload', + SIGNATURE, + TIMESTAMP + )).to.be.false; + }); + + it('should reject for bad signature', () => { + expect(verify( + PUBLIC_KEY, + PAYLOAD, + 'MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH3j/0=', + TIMESTAMP + )).to.be.false; + }); + + it('should reject for bad timestamp', () => { + expect(verify( + PUBLIC_KEY, + PAYLOAD, + SIGNATURE, + 'timestamp' + )).to.be.false; + }); + }); +}); + +function verify(publicKey, payload, signature, timestamp) { + const ew = new EventWebhook(); + const key = ew.convertPublicKeyToECDSA(publicKey); + return ew.verifySignature(key, payload, signature, timestamp); +} diff --git a/packages/inbound-mail-parser/README.md b/packages/inbound-mail-parser/README.md index 475ddc72e..84b9c8211 100644 --- a/packages/inbound-mail-parser/README.md +++ b/packages/inbound-mail-parser/README.md @@ -11,7 +11,7 @@ To be notified when this package is updated, please subscribe to email [notifica ## Prerequisites -- Node.js version 6, 7 or 8 +- Node.js version 6, 8 or >=10 - A Twilio SendGrid account, [sign up for free](https://sendgrid.com/free?source=sendgrid-nodejs) to send up to 40,000 emails for the first 30 days or check out [our pricing](https://sendgrid.com/pricing?source=sendgrid-nodejs). ## Obtain an API Key diff --git a/packages/inbound-mail-parser/package.json b/packages/inbound-mail-parser/package.json index 724edfe70..156f354bc 100644 --- a/packages/inbound-mail-parser/package.json +++ b/packages/inbound-mail-parser/package.json @@ -23,7 +23,7 @@ }, "main": "src/parser.js", "engines": { - "node": ">=6.0.0" + "node": "6.* || 8.* || >=10.*" }, "dependencies": { "@sendgrid/helpers": "^7.0.1", diff --git a/packages/mail/README.md b/packages/mail/README.md index 9a0b9962c..c27c39321 100644 --- a/packages/mail/README.md +++ b/packages/mail/README.md @@ -13,7 +13,7 @@ To be notified when this package is updated, please subscribe to email [notifica ## Prerequisites -- Node.js version 6, 7 or 8 +- Node.js version 6, 8 or >=10 - A Twilio SendGrid account, [sign up for free](https://sendgrid.com/free?source=sendgrid-nodejs) to send up to 40,000 emails for the first 30 days or check out [our pricing](https://sendgrid.com/pricing?source=sendgrid-nodejs). ## Obtain an API Key diff --git a/packages/mail/package.json b/packages/mail/package.json index dbbc30a87..9089d0e1f 100644 --- a/packages/mail/package.json +++ b/packages/mail/package.json @@ -21,7 +21,7 @@ }, "main": "index.js", "engines": { - "node": ">=6.0.0" + "node": "6.* || 8.* || >=10.*" }, "publishConfig": { "access": "public" diff --git a/packages/subscription-widget/package.json b/packages/subscription-widget/package.json index ff19149cc..ba14ab9e4 100644 --- a/packages/subscription-widget/package.json +++ b/packages/subscription-widget/package.json @@ -21,6 +21,6 @@ "access": "public" }, "engines": { - "node": ">=6.0.0" + "node": "6.* || 8.* || >=10.*" } } diff --git a/test/typescript/eventwebhook.ts b/test/typescript/eventwebhook.ts new file mode 100644 index 000000000..158853796 --- /dev/null +++ b/test/typescript/eventwebhook.ts @@ -0,0 +1,13 @@ +import EventWebhook = require('@sendgrid/eventwebhook'); + +var ew = new EventWebhook(); +const PUBLIC_KEY = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEDr2LjtURuePQzplybdC+u4CwrqDqBaWjcMMsTbhdbcwHBcepxo7yAQGhHPTnlvFYPAZFceEu/1FwCM/QmGUhA=="; +const SIGNATURE = "MEUCIQCtIHJeH93Y+qpYeWrySphQgpNGNr/U+UyUlBkU6n7RAwIgJTz2C+8a8xonZGi6BpSzoQsbVRamr2nlxFDWYNH2j/0="; +const TIMESTAMP = "1588788367"; +const PAYLOAD = { + event: 'test_event', + category: 'example_payload', + message_id: 'message_id', +}; +var key = ew.convertPublicKeyToECDSA(PUBLIC_KEY); +console.log(ew.verifySignature(key, PAYLOAD, SIGNATURE, TIMESTAMP));