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

feature/webhook_plugin_signing #2703

Merged
merged 12 commits into from
Sep 29, 2024
9 changes: 9 additions & 0 deletions apps/backoffice-v2/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# @ballerine/backoffice-v2

## 0.7.40

### Patch Changes

- Updated dependencies
- @ballerine/common@0.9.29
- @ballerine/workflow-browser-sdk@0.6.40
- @ballerine/workflow-node-sdk@0.6.40

## 0.7.39

### Patch Changes
Expand Down
16 changes: 16 additions & 0 deletions examples/headless-example/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# @ballerine/headless-example

## 0.3.40

### Patch Changes

- Updated dependencies
- @ballerine/common@0.9.30
- @ballerine/workflow-browser-sdk@0.6.41

## 0.3.39

### Patch Changes

- Updated dependencies
- @ballerine/common@0.9.29
- @ballerine/workflow-browser-sdk@0.6.40

## 0.3.38

### Patch Changes
Expand Down
6 changes: 3 additions & 3 deletions examples/headless-example/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ballerine/headless-example",
"private": true,
"version": "0.3.38",
"version": "0.3.40",
"type": "module",
"scripts": {
"spellcheck": "cspell \"*\"",
Expand Down Expand Up @@ -34,8 +34,8 @@
"vite": "^4.5.3"
},
"dependencies": {
"@ballerine/common": "0.9.28",
"@ballerine/workflow-browser-sdk": "0.6.39",
"@ballerine/common": "0.9.30",
"@ballerine/workflow-browser-sdk": "0.6.41",
"@felte/reporter-svelte": "^1.1.5",
"@felte/validator-zod": "^1.0.13",
"@fontsource/inter": "^4.5.15",
Expand Down
12 changes: 12 additions & 0 deletions packages/common/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# @ballerine/common

## 0.9.30

### Patch Changes

- package sync

## 0.9.29

### Patch Changes

- Added signing logic with crypt

## 0.9.28

### Patch Changes
Expand Down
6 changes: 4 additions & 2 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"private": false,
"name": "@ballerine/common",
"author": "Ballerine <dev@ballerine.com>",
"version": "0.9.28",
"version": "0.9.30",
"description": "common",
"module": "./dist/esm/index.js",
"main": "./dist/cjs/index.js",
Expand Down Expand Up @@ -76,11 +76,13 @@
"ts-node": "^10.9.1",
"typescript": "4.9.5",
"vite": "^4.5.3",
"vitest": "^0.28.4"
"vitest": "^0.28.4",
"@types/crypto-js": "^4.2.2"
},
"dependencies": {
"@sinclair/typebox": "0.32.15",
"ajv": "^8.12.0",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.6",
"json-schema-to-zod": "^0.6.3",
"lodash.get": "^4.4.2",
Expand Down
2 changes: 2 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export {
getSeverityFromRiskScore,
booleanToYesOrNo,
checkIsIsoDate,
sign,
computeHash,
} from './utils';

export type { IErrorWithMessage } from './utils';
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ export { getSeverityFromRiskScore } from './get-severity-from-risk-score';
export { valueOrNA } from './value-or-na';
export { booleanToYesOrNo } from './boolean-to-yes-or-no';
export { type IErrorWithMessage } from './is-error-with-message';
export { sign, computeHash } from './sign';
1 change: 1 addition & 0 deletions packages/common/src/utils/sign/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { sign, computeHash } from './sign';
15 changes: 15 additions & 0 deletions packages/common/src/utils/sign/sign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import CryptoJS from 'crypto-js';

export const sign = ({ payload, key }: { payload: unknown; key: string }) => {
if (!key) {
return 'UNSIGNED';
}

return CryptoJS.HmacSHA256(JSON.stringify(payload), key).toString(CryptoJS.enc.Hex);
};

export const computeHash = (data: unknown): string => {
const md5hash = CryptoJS.MD5(JSON.stringify(data));

return md5hash.toString(CryptoJS.enc.Hex);
};
Comment on lines +11 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a more secure hash function and consider improving type safety.

The computeHash function correctly computes the hash of the input data and returns it as a hexadecimal string. However, there are two issues to consider:

  1. The function uses the MD5 hash function, which is known to have security vulnerabilities and is not recommended for use in new cryptographic protocols. Consider using a more secure hash function like SHA-256.

  2. The data parameter is of type unknown, which allows any type of data to be passed as input. This could potentially lead to issues if the data is not serializable. Consider changing the data type to a more specific type (e.g., Record<string, unknown>) or adding type checks to ensure the data is serializable before stringifying it. This can help catch potential issues early and improve the function's type safety.

Apply this diff to address the issues:

-export const computeHash = (data: unknown): string => {
-  const md5hash = CryptoJS.MD5(JSON.stringify(data));
+export const computeHash = (data: Record<string, unknown>): string => {
+  const sha256hash = CryptoJS.SHA256(JSON.stringify(data));
 
-  return md5hash.toString(CryptoJS.enc.Hex);
+  return sha256hash.toString(CryptoJS.enc.Hex);
 };
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const computeHash = (data: unknown): string => {
const md5hash = CryptoJS.MD5(JSON.stringify(data));
return md5hash.toString(CryptoJS.enc.Hex);
};
export const computeHash = (data: Record<string, unknown>): string => {
const sha256hash = CryptoJS.SHA256(JSON.stringify(data));
return sha256hash.toString(CryptoJS.enc.Hex);
};

Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { sign } from './sign';
import { describe, expect, it } from 'vitest';

const cases: Array<{
name: string;
Expand Down Expand Up @@ -28,7 +29,7 @@ const cases: Array<{

describe('sign', () => {
describe.each(cases)('$name', ({ payload, differentPayload, expectedSignature }) => {
test('When signing a payload, it should return a signature', () => {
it('signing a payload, it should return a signature', () => {
// Arrange
const key = 'secret';

Expand All @@ -39,7 +40,7 @@ describe('sign', () => {
expect(signature).toBe(expectedSignature);
});

test('When signing the same payload with a different key, it should return a different signature', () => {
it('signing the same payload with a different key, it should return a different signature', () => {
// Arrange
const key1 = 'secret';
const key2 = 'secret2';
Expand All @@ -52,7 +53,7 @@ describe('sign', () => {
expect(signature1).not.toBe(signature2);
});

test('When signing different payloads with the same key, it should return different signatures', () => {
it('signing different payloads with the same key, it should return different signatures', () => {
// Arrange
const key = 'secret';

Expand Down
14 changes: 14 additions & 0 deletions packages/workflow-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# @ballerine/workflow-core

## 0.6.41

### Patch Changes

- Updated dependencies
- @ballerine/common@0.9.30

## 0.6.40

### Patch Changes

- Updated dependencies
- @ballerine/common@0.9.29

## 0.6.39

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/workflow-core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@ballerine/workflow-core",
"author": "Ballerine <dev@ballerine.com>",
"version": "0.6.39",
"version": "0.6.41",
"description": "workflow-core",
"module": "./dist/esm/index.js",
"main": "./dist/cjs/index.js",
Expand Down Expand Up @@ -31,7 +31,7 @@
"node": ">=12"
},
"dependencies": {
"@ballerine/common": "0.9.28",
"@ballerine/common": "0.9.30",
"ajv": "^8.12.0",
"i18n-iso-countries": "^7.6.0",
"jmespath": "^0.16.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ApiPlugin } from './api-plugin';
import { TContext } from '../../utils/types';
import { IApiPluginParams } from './types';
import { logger } from '../../logger';
import { AnyRecord } from '@ballerine/common';
import { AnyRecord, sign } from '@ballerine/common';

export class WebhookPlugin extends ApiPlugin {
public static pluginType = 'http';
Expand Down Expand Up @@ -30,7 +30,7 @@ export class WebhookPlugin extends ApiPlugin {
urlWithoutPlaceholders,
this.method,
requestPayload,
await this.composeRequestHeaders(this.headers!, context),
await this.composeRequestSignedHeaders(this.headers!, context, requestPayload),
);

logger.log('Webhook Plugin - Received response', {
Expand Down Expand Up @@ -76,4 +76,30 @@ export class WebhookPlugin extends ApiPlugin {

return {};
}

async composeRequestSignedHeaders(
headers: HeadersInit,
context: TContext,
payload: AnyRecord | undefined,
) {
const secrets = await this.secretsManager?.getAll();
const webhookSharedSecret = secrets?.['webhookSharedSecret'];

if (secrets && webhookSharedSecret) {
headers = {
...headers,
'X-HMAC-Signature': sign({ payload, key: webhookSharedSecret }),
};
}

const headersEntries = await Promise.all(
Object.entries(headers).map(async header => [
header[0],
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
await this.replaceValuePlaceholders(header[1], context),
]),
);

return Object.fromEntries(headersEntries);
}
}
Loading