feat(core): Remove discontinued crypto-js (#8104)

Since crypto-js was
[discontinued](1da3dabf93),
[we migrated all our backend encryption to native
crypto](https://github.com/n8n-io/n8n/pull/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
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-12-21 14:13:02 +01:00 committed by GitHub
parent b67b5ae6b2
commit 01e9a79238
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 93 additions and 65 deletions

View file

@ -2,6 +2,17 @@
This list shows all the versions which include breaking changes and how to upgrade. 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 ## 1.15.0
### What changed? ### What changed?

View file

@ -39,12 +39,12 @@
"dist/**/*" "dist/**/*"
], ],
"devDependencies": { "devDependencies": {
"@types/crypto-js": "^4.1.3",
"@types/deep-equal": "^1.0.1", "@types/deep-equal": "^1.0.1",
"@types/express": "^4.17.6", "@types/express": "^4.17.6",
"@types/jmespath": "^0.15.0", "@types/jmespath": "^0.15.0",
"@types/lodash": "^4.14.195", "@types/lodash": "^4.14.195",
"@types/luxon": "^3.2.0", "@types/luxon": "^3.2.0",
"@types/md5": "^2.3.5",
"@types/xml2js": "^0.4.11" "@types/xml2js": "^0.4.11"
}, },
"dependencies": { "dependencies": {
@ -52,14 +52,15 @@
"@n8n_io/riot-tmpl": "4.0.0", "@n8n_io/riot-tmpl": "4.0.0",
"ast-types": "0.15.2", "ast-types": "0.15.2",
"callsites": "3.1.0", "callsites": "3.1.0",
"crypto-js": "4.2.0",
"deep-equal": "2.2.0", "deep-equal": "2.2.0",
"esprima-next": "5.8.4", "esprima-next": "5.8.4",
"form-data": "4.0.0", "form-data": "4.0.0",
"jmespath": "0.16.0", "jmespath": "0.16.0",
"js-base64": "3.7.2", "js-base64": "3.7.2",
"jssha": "3.3.1",
"lodash": "4.17.21", "lodash": "4.17.21",
"luxon": "3.3.0", "luxon": "3.3.0",
"md5": "2.3.0",
"recast": "0.21.5", "recast": "0.21.5",
"title-case": "3.0.3", "title-case": "3.0.3",
"transliteration": "2.3.5", "transliteration": "2.3.5",

View file

@ -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 { titleCase } from 'title-case';
import type { ExtensionMap } from './Extensions'; import type { ExtensionMap } from './Extensions';
import CryptoJS from 'crypto-js';
import { encode } from 'js-base64';
import { transliterate } from 'transliteration'; import { transliterate } from 'transliteration';
import { ExpressionExtensionError } from '../errors/expression-extension.error'; import { ExpressionExtensionError } from '../errors/expression-extension.error';
const hashFunctions: Record<string, typeof CryptoJS.MD5> = { export const SupportedHashAlgorithms = [
md5: CryptoJS.MD5, 'md5',
sha1: CryptoJS.SHA1, 'sha1',
sha224: CryptoJS.SHA224, 'sha224',
sha256: CryptoJS.SHA256, 'sha256',
sha384: CryptoJS.SHA384, 'sha384',
sha512: CryptoJS.SHA512, 'sha512',
sha3: CryptoJS.SHA3, 'sha3',
ripemd160: CryptoJS.RIPEMD160, ] as const;
};
// All symbols from https://www.xe.com/symbols/ as for 2022/11/09 // All symbols from https://www.xe.com/symbols/ as for 2022/11/09
const CURRENCY_REGEXP = const CURRENCY_REGEXP =
@ -113,23 +112,35 @@ const URL_REGEXP =
const CHAR_TEST_REGEXP = /\p{L}/u; const CHAR_TEST_REGEXP = /\p{L}/u;
const PUNC_TEST_REGEXP = /[!?.]/; const PUNC_TEST_REGEXP = /[!?.]/;
function hash(value: string, extraArgs?: unknown): string { function hash(value: string, extraArgs: string[]): string {
const [algorithm = 'MD5'] = extraArgs as string[]; const algorithm = extraArgs[0]?.toLowerCase() ?? 'md5';
if (algorithm.toLowerCase() === 'base64') { switch (algorithm) {
// We're using a library instead of btoa because btoa only case 'base64':
// works on ASCII return encode(value);
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 { function isEmpty(value: string): boolean {

View file

@ -1,8 +1,6 @@
/** /**
* @jest-environment jsdom * @jest-environment jsdom
*/ */
import { stringExtensions } from '@/Extensions/StringExtensions';
import { evaluate } from './Helpers'; import { evaluate } from './Helpers';
describe('Data Transformation Functions', () => { describe('Data Transformation Functions', () => {
@ -15,28 +13,28 @@ describe('Data Transformation Functions', () => {
expect(evaluate('={{"".isEmpty()}}')).toEqual(true); expect(evaluate('={{"".isEmpty()}}')).toEqual(true);
}); });
test('.hash() should work correctly on a string', () => { describe('.hash()', () => {
expect(evaluate('={{ "12345".hash("sha256") }}')).toEqual( test.each([
stringExtensions.functions.hash('12345', ['sha256']), ['md5', '827ccb0eea8a706c4c34a16891f84e7b'],
); ['sha1', '8cb2237d0679ca88db6464eac60da96345513964'],
['sha224', 'a7470858e79c282bc2f6adfd831b132672dfd1224c1e78cbf5bcd057'],
expect(evaluate('={{ "12345".hash("sha256") }}')).not.toEqual( ['sha256', '5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5'],
stringExtensions.functions.hash('12345', ['MD5']), [
); 'sha384',
'0fa76955abfa9dafd83facca8343a92aa09497f98101086611b0bfa95dbc0dcc661d62e9568a5a032ba81960f3e55d4a',
expect(evaluate('={{ "12345".hash("MD5") }}')).toEqual( ],
stringExtensions.functions.hash('12345', ['MD5']), [
); 'sha512',
'3627909a29c31381a071ec27f7c9ca97726182aed29a7ddd2e54353322cfb30abb9e3a6df2ac2c20fe23436311d678564d0c8d305930575f60e2d3d048184d79',
expect(evaluate('={{ "12345".hash("sha256") }}')).toEqual( ],
'5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5', [
); 'sha3',
}); '0a2a1719bf3ce682afdbedf3b23857818d526efbe7fcb372b31347c26239a0f916c398b7ad8dd0ee76e8e388604d0b0f925d5e913ad2d3165b9b35b3844cd5e6',
],
test('.hash() alias should work correctly on a string', () => { ])('should work for %p', (hashFn, hashValue) => {
expect(evaluate('={{ "12345".hash("sha256") }}')).toEqual( expect(evaluate(`={{ "12345".hash("${hashFn}") }}`)).toEqual(hashValue);
'5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5', expect(evaluate(`={{ "12345".hash("${hashFn.toLowerCase()}") }}`)).toEqual(hashValue);
); });
}); });
test('.urlDecode should work correctly on a string', () => { test('.urlDecode should work correctly on a string', () => {

View file

@ -1494,9 +1494,6 @@ importers:
callsites: callsites:
specifier: 3.1.0 specifier: 3.1.0
version: 3.1.0 version: 3.1.0
crypto-js:
specifier: 4.2.0
version: 4.2.0
deep-equal: deep-equal:
specifier: 2.2.0 specifier: 2.2.0
version: 2.2.0 version: 2.2.0
@ -1512,12 +1509,18 @@ importers:
js-base64: js-base64:
specifier: 3.7.2 specifier: 3.7.2
version: 3.7.2 version: 3.7.2
jssha:
specifier: 3.3.1
version: 3.3.1
lodash: lodash:
specifier: 4.17.21 specifier: 4.17.21
version: 4.17.21 version: 4.17.21
luxon: luxon:
specifier: 3.3.0 specifier: 3.3.0
version: 3.3.0 version: 3.3.0
md5:
specifier: 2.3.0
version: 2.3.0
recast: recast:
specifier: 0.21.5 specifier: 0.21.5
version: 0.21.5 version: 0.21.5
@ -1531,9 +1534,6 @@ importers:
specifier: ^0.5.0 specifier: ^0.5.0
version: 0.5.0 version: 0.5.0
devDependencies: devDependencies:
'@types/crypto-js':
specifier: ^4.1.3
version: 4.1.3
'@types/deep-equal': '@types/deep-equal':
specifier: ^1.0.1 specifier: ^1.0.1
version: 1.0.1 version: 1.0.1
@ -1549,6 +1549,9 @@ importers:
'@types/luxon': '@types/luxon':
specifier: ^3.2.0 specifier: ^3.2.0
version: 3.2.0 version: 3.2.0
'@types/md5':
specifier: ^2.3.5
version: 2.3.5
'@types/xml2js': '@types/xml2js':
specifier: ^0.4.11 specifier: ^0.4.11
version: 0.4.11 version: 0.4.11
@ -9095,10 +9098,6 @@ packages:
'@types/node': 18.16.16 '@types/node': 18.16.16
dev: true dev: true
/@types/crypto-js@4.1.3:
resolution: {integrity: sha512-YP1sYYayLe7Eg5oXyLLvOLfxBfZ5Fgpz6sVWkpB18wDMywCLPWmqzRz+9gyuOoLF0fzDTTFwlyNbx7koONUwqA==}
dev: true
/@types/dateformat@3.0.1: /@types/dateformat@3.0.1:
resolution: {integrity: sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==} resolution: {integrity: sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==}
dev: true dev: true
@ -9364,6 +9363,10 @@ packages:
'@types/linkify-it': 3.0.2 '@types/linkify-it': 3.0.2
'@types/mdurl': 1.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: /@types/mdurl@1.0.2:
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==} resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
@ -17661,6 +17664,10 @@ packages:
resolution: {integrity: sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w==} resolution: {integrity: sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w==}
dev: false dev: false
/jssha@3.3.1:
resolution: {integrity: sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==}
dev: false
/jstransformer@1.0.0: /jstransformer@1.0.0:
resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
dependencies: dependencies: