Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move generateTestHeaderString to stripe.webhooks #619

Merged
merged 1 commit into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 31 additions & 0 deletions lib/Webhooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
6 changes: 0 additions & 6 deletions lib/stripe.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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);
Expand Down
65 changes: 50 additions & 15 deletions test/Webhook.spec.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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',
});

Expand All @@ -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',
});

Expand All @@ -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() {
Expand All @@ -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';
Expand All @@ -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);
Expand Down
43 changes: 0 additions & 43 deletions testUtils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
},
};