diff --git a/packages/cli/BREAKING-CHANGES.md b/packages/cli/BREAKING-CHANGES.md index 10326e784f..1e045ad553 100644 --- a/packages/cli/BREAKING-CHANGES.md +++ b/packages/cli/BREAKING-CHANGES.md @@ -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? diff --git a/packages/workflow/package.json b/packages/workflow/package.json index ba58d25bc5..66a1f20f79 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -39,12 +39,12 @@ "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": { @@ -52,14 +52,15 @@ "@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", diff --git a/packages/workflow/src/Extensions/StringExtensions.ts b/packages/workflow/src/Extensions/StringExtensions.ts index da991af75c..794290d224 100644 --- a/packages/workflow/src/Extensions/StringExtensions.ts +++ b/packages/workflow/src/Extensions/StringExtensions.ts @@ -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 = { - 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 = @@ -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); +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.`, + ); } - 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.`, - ); - } - return hashFunction(value.toString()).toString(); - // return createHash(format).update(value.toString()).digest('hex'); } function isEmpty(value: string): boolean { diff --git a/packages/workflow/test/ExpressionExtensions/StringExtensions.test.ts b/packages/workflow/test/ExpressionExtensions/StringExtensions.test.ts index ce67491ab2..16f298376f 100644 --- a/packages/workflow/test/ExpressionExtensions/StringExtensions.test.ts +++ b/packages/workflow/test/ExpressionExtensions/StringExtensions.test.ts @@ -1,8 +1,6 @@ /** * @jest-environment jsdom */ - -import { stringExtensions } from '@/Extensions/StringExtensions'; import { evaluate } from './Helpers'; describe('Data Transformation Functions', () => { @@ -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', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c861fb91e..c0cfc28807 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1494,9 +1494,6 @@ importers: callsites: specifier: 3.1.0 version: 3.1.0 - crypto-js: - specifier: 4.2.0 - version: 4.2.0 deep-equal: specifier: 2.2.0 version: 2.2.0 @@ -1512,12 +1509,18 @@ importers: js-base64: specifier: 3.7.2 version: 3.7.2 + jssha: + specifier: 3.3.1 + version: 3.3.1 lodash: specifier: 4.17.21 version: 4.17.21 luxon: specifier: 3.3.0 version: 3.3.0 + md5: + specifier: 2.3.0 + version: 2.3.0 recast: specifier: 0.21.5 version: 0.21.5 @@ -1531,9 +1534,6 @@ importers: specifier: ^0.5.0 version: 0.5.0 devDependencies: - '@types/crypto-js': - specifier: ^4.1.3 - version: 4.1.3 '@types/deep-equal': specifier: ^1.0.1 version: 1.0.1 @@ -1549,6 +1549,9 @@ importers: '@types/luxon': specifier: ^3.2.0 version: 3.2.0 + '@types/md5': + specifier: ^2.3.5 + version: 2.3.5 '@types/xml2js': specifier: ^0.4.11 version: 0.4.11 @@ -9095,10 +9098,6 @@ packages: '@types/node': 18.16.16 dev: true - /@types/crypto-js@4.1.3: - resolution: {integrity: sha512-YP1sYYayLe7Eg5oXyLLvOLfxBfZ5Fgpz6sVWkpB18wDMywCLPWmqzRz+9gyuOoLF0fzDTTFwlyNbx7koONUwqA==} - dev: true - /@types/dateformat@3.0.1: resolution: {integrity: sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==} dev: true @@ -9364,6 +9363,10 @@ packages: '@types/linkify-it': 3.0.2 '@types/mdurl': 1.0.2 + /@types/md5@2.3.5: + resolution: {integrity: sha512-/i42wjYNgE6wf0j2bcTX6kuowmdL/6PE4IVitMpm2eYKBUuYCprdcWVK+xEF0gcV6ufMCRhtxmReGfc6hIK7Jw==} + dev: true + /@types/mdurl@1.0.2: resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} @@ -17661,6 +17664,10 @@ packages: resolution: {integrity: sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w==} dev: false + /jssha@3.3.1: + resolution: {integrity: sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==} + dev: false + /jstransformer@1.0.0: resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} dependencies: