From fcf531ebb4356df09575f69f6079d4154780edc2 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:23:46 -0600 Subject: [PATCH 1/3] fix: webhook validation when float weight field is present (closes #467) --- CHANGELOG.md | 4 ++++ examples | 2 +- src/utils/util.js | 7 ++++++- test/helpers/fixture.js | 16 ++++++++++++---- test/services/webhook.test.js | 11 ++++------- 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3589e4826..45190a69a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## Next Release + +- Fixes webhook validation when the `weight` field contains a float by converting it back into a float after conversion from a string (closes #467) + ## v7.5.1 (2024-08-09) - Adds missing properties to `Rate` model diff --git a/examples b/examples index 7bafcddd4..9447e72e0 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 7bafcddd4e7e9f11827a734784127773887443b1 +Subproject commit 9447e72e087b823445b8c3e1351d28c070a8e6b1 diff --git a/src/utils/util.js b/src/utils/util.js index 2153d8bb5..cef638968 100644 --- a/src/utils/util.js +++ b/src/utils/util.js @@ -120,9 +120,14 @@ export default class Utils { const normalizedSecret = webhookSecret.normalize('NFKD'); const encodedSecret = Buffer.from(normalizedSecret, 'utf8'); + // Fixes Javascript's float to string conversion. See https://github.com/EasyPost/easypost-node/issues/467 + const correctedEventBody = Buffer.from(eventBody) + .toString('utf8') + .replace(/("weight":)(\d+)(?!\.\d)/g, '$1$2.0'); + const expectedSignature = crypto .createHmac('sha256', encodedSecret) - .update(eventBody, 'utf-8') + .update(correctedEventBody, 'utf-8') .digest('hex'); const digest = `hmac-sha256-hex=${expectedSignature}`; diff --git a/test/helpers/fixture.js b/test/helpers/fixture.js index f4155de13..0b5ba09fc 100644 --- a/test/helpers/fixture.js +++ b/test/helpers/fixture.js @@ -42,10 +42,6 @@ export default class Fixture { return '2022-04-11'; } - static webhookUrl() { - return this.readFixtureData().webhook_url; - } - static caAddress1() { return this.readFixtureData().addresses.ca_address_1; } @@ -143,6 +139,18 @@ export default class Fixture { return Buffer.from(JSON.stringify(eventBody), 'utf8'); } + static webhookHmacSignature() { + return this.readFixtureData().webhook_hmac_signature; + } + + static webhookSecret() { + return this.readFixtureData().webhook_secret; + } + + static webhookUrl() { + return this.readFixtureData().webhook_url; + } + static plannedShipDate() { return '2024-08-01'; } diff --git a/test/services/webhook.test.js b/test/services/webhook.test.js index 3d1a1811e..389417fe0 100644 --- a/test/services/webhook.test.js +++ b/test/services/webhook.test.js @@ -1,9 +1,9 @@ import { expect } from 'chai'; import EasyPostClient from '../../src/easypost'; +import SignatureVerificationError from '../../src/errors/general/signature_verification_error'; import Webhook from '../../src/models/webhook'; import Fixture from '../helpers/fixture'; -import SignatureVerificationError from '../../src/errors/general/signature_verification_error'; import * as setupPolly from '../helpers/setup_polly'; import { withoutParams } from '../helpers/utils'; @@ -87,20 +87,17 @@ describe('Webhook Service', function () { }); it('validates a webhook secret', function () { - const webhookSecret = 'sécret'; - const expectedHmacSignature = - 'hmac-sha256-hex=e93977c8ccb20363d51a62b3fe1fc402b7829be1152da9e88cf9e8d07115a46b'; const headers = { - 'X-Hmac-Signature': expectedHmacSignature, + 'X-Hmac-Signature': Fixture.webhookHmacSignature(), }; const webhookBody = this.client.Utils.validateWebhook( Fixture.eventBody(), headers, - webhookSecret, + Fixture.webhookSecret(), ); - expect(webhookBody.description).to.equal('batch.created'); + expect(webhookBody.description).to.equal('tracker.updated'); }); it('throws an error when a webhook secret is a differing length', function () { From 75f13f2c88db169c6e0ce54203ef6a19f6d58ea2 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:32:42 -0600 Subject: [PATCH 2/3] chore: add assertion in test --- src/utils/util.js | 2 +- test/services/webhook.test.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/util.js b/src/utils/util.js index cef638968..e8261b763 100644 --- a/src/utils/util.js +++ b/src/utils/util.js @@ -139,7 +139,7 @@ export default class Utils { Buffer.from(digest, 'utf8'), ) ) { - webhook = JSON.parse(eventBody.toString()); + webhook = JSON.parse(correctedEventBody); } else { throw new SignatureVerificationError({ message: Constants.WEBHOOK_DOES_NOT_MATCH }); } diff --git a/test/services/webhook.test.js b/test/services/webhook.test.js index 389417fe0..e60edefd4 100644 --- a/test/services/webhook.test.js +++ b/test/services/webhook.test.js @@ -98,6 +98,9 @@ describe('Webhook Service', function () { ); expect(webhookBody.description).to.equal('tracker.updated'); + // JS converts this from `136.0` in the response to `136` on the user's end which is unfortunate; however, we + // compare signatures with the correct number prior to JSON parsing it and returning it to the user. + expect(webhookBody.result.weight).to.equal(136.0); }); it('throws an error when a webhook secret is a differing length', function () { From 615361c9089bbea9c47f7e40e555fc8c8b788e79 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Wed, 14 Aug 2024 11:22:41 -0600 Subject: [PATCH 3/3] fix: edge case regex --- examples | 2 +- src/utils/util.js | 2 +- test/services/webhook.test.js | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples b/examples index 9447e72e0..0492e408e 160000 --- a/examples +++ b/examples @@ -1 +1 @@ -Subproject commit 9447e72e087b823445b8c3e1351d28c070a8e6b1 +Subproject commit 0492e408e1b37b2ec18bcb4a4d228ab0f458f59d diff --git a/src/utils/util.js b/src/utils/util.js index e8261b763..9d934dc61 100644 --- a/src/utils/util.js +++ b/src/utils/util.js @@ -123,7 +123,7 @@ export default class Utils { // Fixes Javascript's float to string conversion. See https://github.com/EasyPost/easypost-node/issues/467 const correctedEventBody = Buffer.from(eventBody) .toString('utf8') - .replace(/("weight":)(\d+)(?!\.\d)/g, '$1$2.0'); + .replace(/("weight":\s*)(\d+)(\s*)(?=,|\})/g, '$1$2.0'); const expectedSignature = crypto .createHmac('sha256', encodedSecret) diff --git a/test/services/webhook.test.js b/test/services/webhook.test.js index e60edefd4..57fc2ad38 100644 --- a/test/services/webhook.test.js +++ b/test/services/webhook.test.js @@ -98,9 +98,7 @@ describe('Webhook Service', function () { ); expect(webhookBody.description).to.equal('tracker.updated'); - // JS converts this from `136.0` in the response to `136` on the user's end which is unfortunate; however, we - // compare signatures with the correct number prior to JSON parsing it and returning it to the user. - expect(webhookBody.result.weight).to.equal(136.0); + expect(webhookBody.result.weight).to.equal(614.4); // Ensure we convert floats properly }); it('throws an error when a webhook secret is a differing length', function () {