Skip to content

Commit e3a18a4

Browse files
authored
Merge pull request #175 from SteveOfficerSeccl/master
Reintroduce replacement of crypto-js
2 parents 6d833ff + d3b153a commit e3a18a4

File tree

3 files changed

+30
-32
lines changed

3 files changed

+30
-32
lines changed

package-lock.json

Lines changed: 0 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/webhooks.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ const requestBody = JSON.stringify(
55
JSON.parse(fs.readFileSync("src/fixtures/webhook_body.json", "utf8"))
66
);
77

8+
const requestBodyBuffer = Buffer.from(requestBody);
9+
810
const webhookSecret = "ED7D658C-D8EB-4941-948B-3973214F2D49"
911
const signatureHeader = "2693754819d3e32d7e8fcb13c729631f316c6de8dc1cf634d6527f1c07276e7e";
1012

1113

1214
describe(".parse", () => {
13-
test("parses a webhook response body with valid signature", () => {
15+
test("parses a string body with valid signature", () => {
1416
const result = webhook.parse(requestBody, webhookSecret, signatureHeader);
1517

1618
expect(result.length).toBe(2);
@@ -19,6 +21,15 @@ describe(".parse", () => {
1921
expect(firstEvent.id).toBe("EV00BD05S5VM2T");
2022
});
2123

24+
test("parses a buffer body with valid signature", () => {
25+
const result = webhook.parse(requestBodyBuffer, webhookSecret, signatureHeader);
26+
27+
expect(result.length).toBe(2);
28+
29+
const firstEvent = result[0];
30+
expect(firstEvent.id).toBe("EV00BD05S5VM2T");
31+
});
32+
2233
test("parses a webhook response body with an invalid signature", () => {
2334
const badSignatureHeader = "NOTVERYCONVINCING";
2435

src/webhooks.ts

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
* JSON object into an `GoCardless.Event` class.
1111
*/
1212

13-
import cryptoJS from 'crypto-js';
14-
import safeCompare from 'buffer-equal-constant-time';
13+
import crypto from 'crypto';
14+
import type { Event } from './types/Types';
1515

1616
function InvalidSignatureError() {
1717
this.message =
@@ -23,42 +23,41 @@ function InvalidSignatureError() {
2323
* Validates that a webhook was genuinely sent by GoCardless, then parses each `event`
2424
* object into an array of `GoCardless.Event` classes.
2525
*
26-
* @body [string]: The raw webhook body.
27-
* @webhookSecret [string]: The webhook endpoint secret for your webhook endpoint, as
26+
* @body The raw webhook body.
27+
* @webhookSecret The webhook endpoint secret for your webhook endpoint, as
2828
* configured in your GoCardless Dashboard.
29-
* @signatureHeader [string]: The signature included in the webhook request, as specified
29+
* @signatureHeader The signature included in the webhook request, as specified
3030
* by the `Webhook-Signature` header.
3131
*/
32-
function parse(body: string, webhookSecret: string, signatureHeader: string) {
32+
function parse(body: crypto.BinaryLike, webhookSecret: string, signatureHeader: string): Event[] {
3333
verifySignature(body, webhookSecret, signatureHeader);
3434

35-
const eventsData = JSON.parse(body)['events'];
36-
return eventsData.map(eventJson => eventJson);
35+
const bodyString = typeof body === 'string' ? body : body.toString();
36+
const eventsData = JSON.parse(bodyString) as { events: Event[] };
37+
return eventsData.events;
3738
}
3839

3940
/**
40-
* Validate the signature header. Note, we're using the `buffer-equal-constant-time`
41+
* Validate the signature header. Note, we're using the `crypto.timingSafeEqual`
4142
* library for the hash comparison, to protect against timing attacks.
4243
*
43-
* @body [string]: The raw webhook body.
44-
* @webhookSecret [string]: The webhook endpoint secret for your webhook endpoint, as
44+
* @body The raw webhook body.
45+
* @webhookSecret The webhook endpoint secret for your webhook endpoint, as
4546
* configured in your GoCardless Dashboard.
46-
* @signatureHeader [string]: The signature included in the webhook request, as specified
47+
* @signatureHeader The signature included in the webhook request, as specified
4748
* by the `Webhook-Signature` header.
4849
*/
4950
function verifySignature(
50-
body: string,
51+
body: crypto.BinaryLike,
5152
webhookSecret: string,
5253
signatureHeader: string
5354
) {
54-
const rawDigest = cryptoJS.HmacSHA256(body, webhookSecret);
55+
const bufferDigest = crypto.createHmac('sha256', webhookSecret).update(body).digest();
56+
const bufferSignatureHeader = Buffer.from(signatureHeader, 'hex');
5557

56-
const bufferDigest = Buffer.from(rawDigest.toString(cryptoJS.enc.Hex));
57-
const bufferSignatureHeader = Buffer.from(signatureHeader);
58-
59-
if (!safeCompare(bufferDigest, bufferSignatureHeader)) {
58+
if ((bufferDigest.length !== bufferSignatureHeader.length) || !crypto.timingSafeEqual(bufferDigest, bufferSignatureHeader)) {
6059
throw new InvalidSignatureError();
6160
}
6261
}
6362

64-
export { parse, InvalidSignatureError };
63+
export { parse, verifySignature, InvalidSignatureError };

0 commit comments

Comments
 (0)