From e30553b5e3c13069df9440470c1692a3a40f0587 Mon Sep 17 00:00:00 2001 From: paulasjes-stripe <46610432+paulasjes-stripe@users.noreply.github.com> Date: Wed, 8 May 2019 18:42:08 +0100 Subject: [PATCH] Move generateTestHeaderString to stripe.webhooks (#619) --- README.md | 3 +- lib/Webhooks.js | 31 +++++++++++++++++++++ lib/stripe.js | 6 ---- test/Webhook.spec.js | 65 ++++++++++++++++++++++++++++++++++---------- testUtils/index.js | 43 ----------------------------- 5 files changed, 83 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 8bfc261d52..e3f3551304 100644 --- a/README.md +++ b/README.md @@ -236,8 +236,9 @@ const payload = { const payloadString = JSON.stringify(payload, null, 2); const secret = 'whsec_test_secret'; -const header = stripe.generateWebhookHeaderString({ +const header = stripe.webhooks.generateTestHeaderString({ payload: payloadString, + secret, }); const event = stripe.webhooks.constructEvent(payloadString, header, secret); diff --git a/lib/Webhooks.js b/lib/Webhooks.js index ecbe09e603..d27c58d6a4 100644 --- a/lib/Webhooks.js +++ b/lib/Webhooks.js @@ -15,6 +15,37 @@ var Webhook = { var jsonPayload = JSON.parse(payload); return jsonPayload; }, + + /** + * Generates a header to be used for webhook mocking + * + * @typedef {object} opts + * @property {number} timestamp - Timestamp of the header. Defaults to Date.now() + * @property {string} payload - JSON stringified payload object, containing the 'id' and 'object' parameters + * @property {string} secret - Stripe webhook secret 'whsec_...' + * @property {string} scheme - Version of API to hit. Defaults to 'v1'. + * @property {string} signature - Computed webhook signature + */ + generateTestHeaderString: function(opts) { + if (!opts) { + throw new Error.StripeError({ + message: 'Options are required', + }); + } + + opts.timestamp = Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000); + opts.scheme = opts.scheme || signature.EXPECTED_SCHEME; + + opts.signature = opts.signature || + signature._computeSignature(opts.timestamp + '.' + opts.payload, opts.secret); + + var generatedHeader = [ + 't=' + opts.timestamp, + opts.scheme + '=' + opts.signature, + ].join(','); + + return generatedHeader; + }, }; var signature = { diff --git a/lib/stripe.js b/lib/stripe.js index 4c6a24826e..fa5d96f51b 100644 --- a/lib/stripe.js +++ b/lib/stripe.js @@ -28,7 +28,6 @@ var APP_INFO_PROPERTIES = ['name', 'version', 'url', 'partner_id']; var EventEmitter = require('events').EventEmitter; var utils = require('./utils'); -var testUtils = require('../testUtils'); var resourceNamespace = require('./ResourceNamespace'); @@ -341,11 +340,6 @@ Stripe.prototype = { return this._enableTelemetry; }, - // Generates a header to be used for webhook mocking - generateWebhookHeaderString: function(opts) { - return testUtils.generateHeaderString.apply(testUtils, opts); - }, - _prepResources: function() { for (var name in resources) { this[utils.pascalToCamelCase(name)] = new resources[name](this); diff --git a/test/Webhook.spec.js b/test/Webhook.spec.js index 8950574742..ab957f1489 100644 --- a/test/Webhook.spec.js +++ b/test/Webhook.spec.js @@ -1,20 +1,40 @@ 'use strict'; -var testUtils = require('../testUtils'); -var stripe = testUtils.getSpyableStripe(); -var generateHeaderString = testUtils.generateHeaderString; +var stripe = require('../testUtils').getSpyableStripe(); var expect = require('chai').expect; var Buffer = require('safe-buffer').Buffer; -var EVENT_PAYLOAD = testUtils.EVENT_PAYLOAD; -var EVENT_PAYLOAD_STRING = testUtils.EVENT_PAYLOAD_STRING; -var SECRET = testUtils.WEBHOOK_SECRET; +var EVENT_PAYLOAD = { + id: 'evt_test_webhook', + object: 'event', +}; +var EVENT_PAYLOAD_STRING = JSON.stringify(EVENT_PAYLOAD, null, 2); +var SECRET = 'whsec_test_secret'; describe('Webhooks', function() { + describe('.generateTestHeaderString', function() { + it('should throw when no opts are passed', function() { + expect(function() { + stripe.webhooks.generateTestHeaderString() + }).to.throw(); + }); + + it('should correctly construct a webhook header', function() { + var header = stripe.webhooks.generateTestHeaderString({ + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, + }); + + expect(header).to.not.be.undefined; + expect(header.split(',')).to.have.lengthOf(2); + }); + }); + describe('.constructEvent', function() { it('should return an Event instance from a valid JSON payload and valid signature header', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ payload: EVENT_PAYLOAD_STRING, + secret: SECRET, }); var event = stripe.webhooks.constructEvent(EVENT_PAYLOAD_STRING, header, SECRET); @@ -24,8 +44,9 @@ describe('Webhooks', function() { it('should raise a JSON error from invalid JSON payload', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ payload: '} I am not valid JSON; 123][', + secret: SECRET, }); expect(function() { stripe.webhooks.constructEvent('} I am not valid JSON; 123][', header, SECRET); @@ -66,7 +87,9 @@ describe('Webhooks', function() { }); it('should raise a SignatureVerificationError when there are no signatures with the expected scheme', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, scheme: 'v0', }); @@ -76,7 +99,9 @@ describe('Webhooks', function() { }); it('should raise a SignatureVerificationError when there are no valid signatures for the payload', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, signature: 'bad_signature', }); @@ -86,8 +111,10 @@ describe('Webhooks', function() { }); it('should raise a SignatureVerificationError when the timestamp is not within the tolerance', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ timestamp: (Date.now() / 1000) - 15, + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, }); expect(function() { @@ -98,16 +125,20 @@ describe('Webhooks', function() { it('should return true when the header contains a valid signature and ' + 'the timestamp is within the tolerance', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ timestamp: (Date.now() / 1000), + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, }); expect(stripe.webhooks.signature.verifyHeader(EVENT_PAYLOAD_STRING, header, SECRET, 10)).to.equal(true); }); it('should return true when the header contains at least one valid signature', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ timestamp: (Date.now() / 1000), + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, }); header += ',v1=potato'; @@ -118,16 +149,20 @@ describe('Webhooks', function() { it('should return true when the header contains a valid signature ' + 'and the timestamp is off but no tolerance is provided', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ timestamp: 12345, + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, }); expect(stripe.webhooks.signature.verifyHeader(EVENT_PAYLOAD_STRING, header, SECRET)).to.equal(true); }); it('should accept Buffer instances for the payload and header', function() { - var header = generateHeaderString({ + var header = stripe.webhooks.generateTestHeaderString({ timestamp: (Date.now() / 1000), + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, }); expect(stripe.webhooks.signature.verifyHeader(Buffer.from(EVENT_PAYLOAD_STRING), Buffer.from(header), SECRET, 10)).to.equal(true); diff --git a/testUtils/index.js b/testUtils/index.js index 1517645fa2..dcf8e5d086 100644 --- a/testUtils/index.js +++ b/testUtils/index.js @@ -9,18 +9,6 @@ require('chai').use(require('chai-as-promised')); var ResourceNamespace = require('../lib/ResourceNamespace').ResourceNamespace; var utils = module.exports = { - - EVENT_PAYLOAD: { - id: 'evt_test_webhook', - object: 'event', - }, - - get EVENT_PAYLOAD_STRING() { - return JSON.stringify(this.EVENT_PAYLOAD, null, 2); - }, - - WEBHOOK_SECRET: 'whsec_test_secret', - getUserStripeKey: function() { var key = process.env.STRIPE_TEST_API_KEY || 'tGN0bIwXnHdwOa85VABjPdSn8nWY7G7I'; @@ -168,35 +156,4 @@ var utils = module.exports = { return false; } }, - - /** - * Generates a header to be used for webhook mocking - * - * @typedef {object} opts - * @property {object} stripe - Instance of Stripe to use. Defaults to a spyable instance for testing. - * @property {number} timestamp - Timestamp of the header. Defaults to Date.now() - * @property {string} payload - JSON stringified payload object, containing the 'id' and 'object' parameters - * @property {string} secret - Stripe webhook secret 'whsec_...' - * @property {string} scheme - Version of API to hit. Defaults to 'v1'. - * @property {string} signature - Computed webhook signature - */ - generateHeaderString: function(opts) { - opts = opts || {}; - - opts.stripe = opts.stripe || utils.getSpyableStripe(); - opts.timestamp = Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000); - opts.payload = opts.payload || utils.EVENT_PAYLOAD_STRING; - opts.secret = opts.secret || utils.WEBHOOK_SECRET; - opts.scheme = opts.scheme || opts.stripe.webhooks.signature.EXPECTED_SCHEME; - - opts.signature = opts.signature || - opts.stripe.webhooks.signature._computeSignature(opts.timestamp + '.' + opts.payload, opts.secret); - - var generatedHeader = [ - 't=' + opts.timestamp, - opts.scheme + '=' + opts.signature, - ].join(','); - - return generatedHeader; - }, };