Skip to content

Commit

Permalink
feat(core): Remove discontinued crypto-js (#8104)
Browse files Browse the repository at this point in the history
Since crypto-js was
[discontinued](brix/crypto-js@1da3dab),
[we migrated all our backend encryption to native
crypto](#7556).
However I decided back then to not remove crypto-js just yet in
expressions, as I wanted to use `SubtleCrypto`. Unfortunately for that
to work, we'd need to make expressions async.
So, to get rid of `crypto-js`, I propose this interim solution. 

## Related tickets and issues
N8N-7020

## Review / Merge checklist
- [x] PR title and summary are descriptive
- [x] Tests included
  • Loading branch information
netroy authored Dec 21, 2023
1 parent b67b5ae commit 01e9a79
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 65 deletions.
11 changes: 11 additions & 0 deletions packages/cli/BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

This list shows all the versions which include breaking changes and how to upgrade.

## 1.22.0

### What changed?

Hash algorithm `ripemd160` is dropped from `.hash()` expressions.
`sha3` hash algorithm now returns a valid sha3-512 has, unlike the previous implementation that returned a `Keccak` hash instead.

### When is action necessary?

If you are using `.hash` helpers in expressions with hash algorithm `ripemd160`, you need to switch to one of the other supported algorithms.

## 1.15.0

### What changed?
Expand Down
5 changes: 3 additions & 2 deletions packages/workflow/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,28 @@
"dist/**/*"
],
"devDependencies": {
"@types/crypto-js": "^4.1.3",
"@types/deep-equal": "^1.0.1",
"@types/express": "^4.17.6",
"@types/jmespath": "^0.15.0",
"@types/lodash": "^4.14.195",
"@types/luxon": "^3.2.0",
"@types/md5": "^2.3.5",
"@types/xml2js": "^0.4.11"
},
"dependencies": {
"@n8n/tournament": "1.0.2",
"@n8n_io/riot-tmpl": "4.0.0",
"ast-types": "0.15.2",
"callsites": "3.1.0",
"crypto-js": "4.2.0",
"deep-equal": "2.2.0",
"esprima-next": "5.8.4",
"form-data": "4.0.0",
"jmespath": "0.16.0",
"js-base64": "3.7.2",
"jssha": "3.3.1",
"lodash": "4.17.21",
"luxon": "3.3.0",
"md5": "2.3.0",
"recast": "0.21.5",
"title-case": "3.0.3",
"transliteration": "2.3.5",
Expand Down
69 changes: 40 additions & 29 deletions packages/workflow/src/Extensions/StringExtensions.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
// import { createHash } from 'crypto';
import SHA from 'jssha';
import MD5 from 'md5';
import { encode } from 'js-base64';
import { titleCase } from 'title-case';
import type { ExtensionMap } from './Extensions';
import CryptoJS from 'crypto-js';
import { encode } from 'js-base64';
import { transliterate } from 'transliteration';
import { ExpressionExtensionError } from '../errors/expression-extension.error';

const hashFunctions: Record<string, typeof CryptoJS.MD5> = {
md5: CryptoJS.MD5,
sha1: CryptoJS.SHA1,
sha224: CryptoJS.SHA224,
sha256: CryptoJS.SHA256,
sha384: CryptoJS.SHA384,
sha512: CryptoJS.SHA512,
sha3: CryptoJS.SHA3,
ripemd160: CryptoJS.RIPEMD160,
};
export const SupportedHashAlgorithms = [
'md5',
'sha1',
'sha224',
'sha256',
'sha384',
'sha512',
'sha3',
] as const;

// All symbols from https://www.xe.com/symbols/ as for 2022/11/09
const CURRENCY_REGEXP =
Expand Down Expand Up @@ -113,23 +112,35 @@ const URL_REGEXP =
const CHAR_TEST_REGEXP = /\p{L}/u;
const PUNC_TEST_REGEXP = /[!?.]/;

function hash(value: string, extraArgs?: unknown): string {
const [algorithm = 'MD5'] = extraArgs as string[];
if (algorithm.toLowerCase() === 'base64') {
// We're using a library instead of btoa because btoa only
// works on ASCII
return encode(value);
}
const hashFunction = hashFunctions[algorithm.toLowerCase()];
if (!hashFunction) {
throw new ExpressionExtensionError(
`Unknown algorithm ${algorithm}. Available algorithms are: ${Object.keys(hashFunctions)
.map((s) => s.toUpperCase())
.join(', ')}, and Base64.`,
);
function hash(value: string, extraArgs: string[]): string {
const algorithm = extraArgs[0]?.toLowerCase() ?? 'md5';
switch (algorithm) {
case 'base64':
return encode(value);
case 'md5':
return MD5(value);
case 'sha1':
case 'sha224':
case 'sha256':
case 'sha384':
case 'sha512':
case 'sha3':
const variant = (
{
sha1: 'SHA-1',
sha224: 'SHA-224',
sha256: 'SHA-256',
sha384: 'SHA-384',
sha512: 'SHA-512',
sha3: 'SHA3-512',
} as const
)[algorithm];
return new SHA(variant, 'TEXT').update(value).getHash('HEX');
default:
throw new ExpressionExtensionError(
`Unknown algorithm ${algorithm}. Available algorithms are: ${SupportedHashAlgorithms.join()}, and Base64.`,
);
}
return hashFunction(value.toString()).toString();
// return createHash(format).update(value.toString()).digest('hex');
}

function isEmpty(value: string): boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/**
* @jest-environment jsdom
*/

import { stringExtensions } from '@/Extensions/StringExtensions';
import { evaluate } from './Helpers';

describe('Data Transformation Functions', () => {
Expand All @@ -15,28 +13,28 @@ describe('Data Transformation Functions', () => {
expect(evaluate('={{"".isEmpty()}}')).toEqual(true);
});

test('.hash() should work correctly on a string', () => {
expect(evaluate('={{ "12345".hash("sha256") }}')).toEqual(
stringExtensions.functions.hash('12345', ['sha256']),
);

expect(evaluate('={{ "12345".hash("sha256") }}')).not.toEqual(
stringExtensions.functions.hash('12345', ['MD5']),
);

expect(evaluate('={{ "12345".hash("MD5") }}')).toEqual(
stringExtensions.functions.hash('12345', ['MD5']),
);

expect(evaluate('={{ "12345".hash("sha256") }}')).toEqual(
'5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5',
);
});

test('.hash() alias should work correctly on a string', () => {
expect(evaluate('={{ "12345".hash("sha256") }}')).toEqual(
'5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5',
);
describe('.hash()', () => {
test.each([
['md5', '827ccb0eea8a706c4c34a16891f84e7b'],
['sha1', '8cb2237d0679ca88db6464eac60da96345513964'],
['sha224', 'a7470858e79c282bc2f6adfd831b132672dfd1224c1e78cbf5bcd057'],
['sha256', '5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5'],
[
'sha384',
'0fa76955abfa9dafd83facca8343a92aa09497f98101086611b0bfa95dbc0dcc661d62e9568a5a032ba81960f3e55d4a',
],
[
'sha512',
'3627909a29c31381a071ec27f7c9ca97726182aed29a7ddd2e54353322cfb30abb9e3a6df2ac2c20fe23436311d678564d0c8d305930575f60e2d3d048184d79',
],
[
'sha3',
'0a2a1719bf3ce682afdbedf3b23857818d526efbe7fcb372b31347c26239a0f916c398b7ad8dd0ee76e8e388604d0b0f925d5e913ad2d3165b9b35b3844cd5e6',
],
])('should work for %p', (hashFn, hashValue) => {
expect(evaluate(`={{ "12345".hash("${hashFn}") }}`)).toEqual(hashValue);
expect(evaluate(`={{ "12345".hash("${hashFn.toLowerCase()}") }}`)).toEqual(hashValue);
});
});

test('.urlDecode should work correctly on a string', () => {
Expand Down
27 changes: 17 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 01e9a79

Please sign in to comment.