Skip to content

Commit

Permalink
Merge pull request #174 from gocardless/template-changes
Browse files Browse the repository at this point in the history
Template changes
  • Loading branch information
opsz2 authored Jan 18, 2024
2 parents 21b005e + d8efe6e commit 253467e
Show file tree
Hide file tree
Showing 8 changed files with 1,984 additions and 3,423 deletions.
5,323 changes: 1,931 additions & 3,392 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gocardless-nodejs",
"version": "3.20.0",
"version": "3.21.0",
"description": "Node.js client for the GoCardless API - a powerful, simple solution for the collection of recurring bank-to-bank payments",
"author": "GoCardless Ltd <[email protected]>",
"repository": {
Expand All @@ -26,6 +26,8 @@
"got": "^11.8.5",
"lodash": "^4.17.15",
"uuid": "^7.0.2",
"crypto-js": "3.2.1",
"buffer-equal-constant-time": "1.0.1",
"qs": "^6.9.1"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ enum Environments {
Sandbox = 'SANDBOX',
}

const CLIENT_VERSION = '3.20.0';
const CLIENT_VERSION = '3.21.0';
const API_VERSION = '2015-07-06';

export { Environments, CLIENT_VERSION, API_VERSION };
9 changes: 9 additions & 0 deletions src/services/billingRequestService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ interface BillingRequestConfirmPayerDetailsRequest {
// to 50 characters and values up to 500 characters.

metadata?: Types.JsonMap;

// This attribute can be set to true if the payer has indicated that multiple
// signatures are required for the mandate. As long as every other Billing
// Request actions have been completed, the payer will receive an email
// notification containing instructions on how to complete the additional
// signature. The dual signature flow can only be completed using GoCardless
// branded pages.

payer_requested_dual_signature?: boolean;
}

interface BillingRequestFulfilRequest {
Expand Down
3 changes: 3 additions & 0 deletions src/services/mandateImportEntryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ interface MandateImportEntryCreateRequest {
// Resources linked to this MandateImportEntry.
links: Types.MandateImportEntryCreateRequestLinks;

//
mandate?: Types.MandateImportEntryMandate;

// A unique identifier for this entry, which you can use (once the import has
// been
// processed by GoCardless) to identify the records that have been created.
Expand Down
17 changes: 17 additions & 0 deletions src/types/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ export interface BillingRequestMandateRequest {
// up to 50 characters and values up to 500 characters.
metadata?: JsonMap;

// This attribute can be set to true if the payer has indicated that multiple
// signatures are required for the mandate. As long as every other Billing
// Request actions have been completed, the payer will receive an email
// notification containing instructions on how to complete the additional
// signature. The dual signature flow can only be completed using GoCardless
// branded pages.
payer_requested_dual_signature?: boolean;

// A bank payment scheme. Currently "ach", "autogiro", "bacs", "becs",
// "becs_nz", "betalingsservice", "faster_payments", "pad", "pay_to" and
// "sepa_core" are supported. Optional for mandate only requests - if left
Expand Down Expand Up @@ -2683,6 +2691,15 @@ export interface MandateImportEntryCreateRequestLinks {
mandate_import: string;
}

/** Type for a mandateimportentrymandate resource. */
export interface MandateImportEntryMandate {
// Unique reference. Different schemes have different length and [character
// set](#appendix-character-sets) requirements. GoCardless will generate a
// unique reference satisfying the different scheme requirements if this field
// is left blank.
reference?: string | null;
}

/** Type for a mandateimportentrylinks resource. */
export interface MandateImportEntryLinks {
// The ID of the customer which was created when the mandate import was
Expand Down
12 changes: 1 addition & 11 deletions src/webhooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ const webhook = require("./webhooks");
const requestBody = JSON.stringify(
JSON.parse(fs.readFileSync("src/fixtures/webhook_body.json", "utf8"))
);
const requestBodyBuffer = Buffer.from(requestBody);

const webhookSecret = "ED7D658C-D8EB-4941-948B-3973214F2D49"
const signatureHeader = "2693754819d3e32d7e8fcb13c729631f316c6de8dc1cf634d6527f1c07276e7e";


describe(".parse", () => {
test("parses a string body with valid signature", () => {
test("parses a webhook response body with valid signature", () => {
const result = webhook.parse(requestBody, webhookSecret, signatureHeader);

expect(result.length).toBe(2);
Expand All @@ -20,15 +19,6 @@ describe(".parse", () => {
expect(firstEvent.id).toBe("EV00BD05S5VM2T");
});

test("parses a buffer body with valid signature", () => {
const result = webhook.parse(requestBodyBuffer, webhookSecret, signatureHeader);

expect(result.length).toBe(2);

const firstEvent = result[0];
expect(firstEvent.id).toBe("EV00BD05S5VM2T");
});

test("parses a webhook response body with an invalid signature", () => {
const badSignatureHeader = "NOTVERYCONVINCING";

Expand Down
37 changes: 19 additions & 18 deletions src/webhooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
* JSON object into an `GoCardless.Event` class.
*/

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

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

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

/**
* Validate the signature header. Note, we're using the `crypto.timingSafeEqual`
* Validate the signature header. Note, we're using the `buffer-equal-constant-time`
* library for the hash comparison, to protect against timing attacks.
*
* @body The raw webhook body.
* @webhookSecret The webhook endpoint secret for your webhook endpoint, as
* @body [string]: The raw webhook body.
* @webhookSecret [string]: The webhook endpoint secret for your webhook endpoint, as
* configured in your GoCardless Dashboard.
* @signatureHeader The signature included in the webhook request, as specified
* @signatureHeader [string]: The signature included in the webhook request, as specified
* by the `Webhook-Signature` header.
*/
function verifySignature(
body: crypto.BinaryLike,
body: string,
webhookSecret: string,
signatureHeader: string
) {
const bufferDigest = crypto.createHmac('sha256', webhookSecret).update(body).digest();
const bufferSignatureHeader = Buffer.from(signatureHeader, 'hex');
const rawDigest = cryptoJS.HmacSHA256(body, webhookSecret);

if ((bufferDigest.length !== bufferSignatureHeader.length) || !crypto.timingSafeEqual(bufferDigest, bufferSignatureHeader)) {
const bufferDigest = Buffer.from(rawDigest.toString(cryptoJS.enc.Hex));
const bufferSignatureHeader = Buffer.from(signatureHeader);

if (!safeCompare(bufferDigest, bufferSignatureHeader)) {
throw new InvalidSignatureError();
}
}

export { parse, verifySignature, InvalidSignatureError };
export { parse, InvalidSignatureError };

0 comments on commit 253467e

Please sign in to comment.