From 1b06272f5e5735d192f62f9a2addcaccf467d43c Mon Sep 17 00:00:00 2001 From: zelief Date: Sat, 23 Mar 2024 06:29:57 +0700 Subject: [PATCH 1/2] Add generateTestHeaderStringAsync function to Webhooks.ts --- src/Webhooks.ts | 32 ++++++++++++++++++++++++++++++++ test/Webhook.spec.ts | 24 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/Webhooks.ts b/src/Webhooks.ts index 038c205d29..8bf5377db3 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -65,6 +65,9 @@ export type WebhookObject = { receivedAt: number ) => Promise; generateTestHeaderString: (opts: WebhookTestHeaderOptions) => string; + generateTestHeaderStringAsync: ( + opts: WebhookTestHeaderOptions + ) => Promise; }; export function createWebhooks( @@ -166,6 +169,35 @@ export function createWebhooks( opts.scheme + '=' + opts.signature, ].join(','); + return generatedHeader; + }, + generateTestHeaderStringAsync: async function( + opts: WebhookTestHeaderOptions + ): Promise { + if (!opts) { + throw new StripeError({ + message: 'Options are required', + }); + } + + opts.timestamp = + Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000); + opts.scheme = opts.scheme || signature.EXPECTED_SCHEME; + + opts.cryptoProvider = opts.cryptoProvider || getCryptoProvider(); + + opts.signature = + opts.signature || + (await opts.cryptoProvider.computeHMACSignatureAsync( + opts.timestamp + '.' + opts.payload, + opts.secret + )); + + const generatedHeader = [ + 't=' + opts.timestamp, + opts.scheme + '=' + opts.signature, + ].join(','); + return generatedHeader; }, }; diff --git a/test/Webhook.spec.ts b/test/Webhook.spec.ts index 705d89aed6..deae6648de 100644 --- a/test/Webhook.spec.ts +++ b/test/Webhook.spec.ts @@ -41,6 +41,30 @@ function createWebhooksTestSuite(stripe) { }); }); + describe('.generateTestHeaderStringAsync', () => { + it('should throw when no opts are passed', async () => { + await expect( + stripe.webhooks.generateTestHeaderStringAsync() + ).to.be.rejectedWith('Options are required'); + }); + + it('should correctly construct a webhook header', async () => { + const header = await stripe.webhooks.generateTestHeaderStringAsync({ + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, + }); + + expect(header).to.not.be.undefined; + expect(header.split(',')).to.have.lengthOf(2); + expect(header).to.equal( + stripe.webhooks.generateTestHeaderString({ + payload: EVENT_PAYLOAD_STRING, + secret: SECRET, + }) + ); + }); + }); + const makeConstructEventTests = ( constructEventFn: typeof stripe.webhooks.construct ) => { From ef618a84353155269caeede43e03f38d7531ef72 Mon Sep 17 00:00:00 2001 From: zelief Date: Sat, 6 Jul 2024 15:33:42 +0700 Subject: [PATCH 2/2] refactor duplicated codes --- src/Webhooks.ts | 94 +++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/src/Webhooks.ts b/src/Webhooks.ts index 8bf5377db3..fc3dec8907 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -145,60 +145,30 @@ export function createWebhooks( * @property {CryptoProvider} cryptoProvider - Crypto provider to use for computing the signature if none was provided. Defaults to NodeCryptoProvider. */ generateTestHeaderString: function(opts: WebhookTestHeaderOptions): string { - if (!opts) { - throw new StripeError({ - message: 'Options are required', - }); - } - - opts.timestamp = - Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000); - opts.scheme = opts.scheme || signature.EXPECTED_SCHEME; + const preparedOpts = prepareOptions(opts); - opts.cryptoProvider = opts.cryptoProvider || getCryptoProvider(); - - opts.signature = - opts.signature || - opts.cryptoProvider.computeHMACSignature( - opts.timestamp + '.' + opts.payload, - opts.secret + const signature = + preparedOpts.signature || + preparedOpts.cryptoProvider.computeHMACSignature( + preparedOpts.payloadString, + preparedOpts.secret ); - const generatedHeader = [ - 't=' + opts.timestamp, - opts.scheme + '=' + opts.signature, - ].join(','); - - return generatedHeader; + return preparedOpts.generateHeaderString(signature); }, generateTestHeaderStringAsync: async function( opts: WebhookTestHeaderOptions - ): Promise { - if (!opts) { - throw new StripeError({ - message: 'Options are required', - }); - } - - opts.timestamp = - Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000); - opts.scheme = opts.scheme || signature.EXPECTED_SCHEME; - - opts.cryptoProvider = opts.cryptoProvider || getCryptoProvider(); - - opts.signature = - opts.signature || - (await opts.cryptoProvider.computeHMACSignatureAsync( - opts.timestamp + '.' + opts.payload, - opts.secret + ) { + const preparedOpts = prepareOptions(opts); + + const signature = + preparedOpts.signature || + (await preparedOpts.cryptoProvider.computeHMACSignatureAsync( + preparedOpts.payloadString, + preparedOpts.secret )); - const generatedHeader = [ - 't=' + opts.timestamp, - opts.scheme + '=' + opts.signature, - ].join(','); - - return generatedHeader; + return preparedOpts.generateHeaderString(signature); }, }; @@ -475,6 +445,38 @@ export function createWebhooks( return webhooksCryptoProviderInstance!; } + function prepareOptions( + opts: WebhookTestHeaderOptions + ): WebhookTestHeaderOptions & { + payloadString: string; + generateHeaderString: (signature: string) => string; + } { + if (!opts) { + throw new StripeError({ + message: 'Options are required', + }); + } + + const timestamp = + Math.floor(opts.timestamp) || Math.floor(Date.now() / 1000); + const scheme = opts.scheme || signature.EXPECTED_SCHEME; + const cryptoProvider = opts.cryptoProvider || getCryptoProvider(); + const payloadString = `${timestamp}.${opts.payload}`; + + const generateHeaderString = (signature: string): string => { + return `t=${timestamp},${scheme}=${signature}`; + }; + + return { + ...opts, + timestamp, + scheme, + cryptoProvider, + payloadString, + generateHeaderString, + }; + } + Webhook.signature = signature; return Webhook;