mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
feat(core): Use WebCrypto to generate all random numbers and strings (#9786)
This commit is contained in:
committed by
GitHub
parent
cfc4db00e3
commit
65c5609ab5
@@ -1,2 +1,5 @@
|
||||
/** @type {import('jest').Config} */
|
||||
module.exports = require('../../jest.config');
|
||||
module.exports = {
|
||||
...require('../../jest.config'),
|
||||
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { NodeParameterValue } from './Interfaces';
|
||||
|
||||
export const DIGITS = '0123456789';
|
||||
export const UPPERCASE_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
export const LOWERCASE_LETTERS = UPPERCASE_LETTERS.toLowerCase();
|
||||
export const ALPHABET = [DIGITS, UPPERCASE_LETTERS, LOWERCASE_LETTERS].join('');
|
||||
|
||||
export const BINARY_ENCODING = 'base64';
|
||||
export const WAIT_TIME_UNLIMITED = '3000-01-01T00:00:00.000Z';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { randomInt } from './utils';
|
||||
|
||||
interface BaseTriggerTime<T extends string> {
|
||||
mode: T;
|
||||
}
|
||||
@@ -47,7 +49,7 @@ export type TriggerTime =
|
||||
| EveryWeek
|
||||
| EveryMonth;
|
||||
|
||||
const randomSecond = () => Math.floor(Math.random() * 60).toString();
|
||||
const randomSecond = () => randomInt(60).toString();
|
||||
|
||||
export const toCronExpression = (item: TriggerTime): CronExpression => {
|
||||
if (item.mode === 'everyMinute') return `${randomSecond()} * * * * *`;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import deepEqual from 'deep-equal';
|
||||
import uniqWith from 'lodash/uniqWith';
|
||||
|
||||
import { ExpressionError } from '../errors/expression.error';
|
||||
import { ExpressionExtensionError } from '../errors/expression-extension.error';
|
||||
import type { Extension, ExtensionMap } from './Extensions';
|
||||
import { compact as oCompact } from './ObjectExtensions';
|
||||
import deepEqual from 'deep-equal';
|
||||
import uniqWith from 'lodash/uniqWith';
|
||||
import { randomInt } from '../utils';
|
||||
|
||||
function first(value: unknown[]): unknown {
|
||||
return value[0];
|
||||
@@ -49,7 +51,7 @@ function pluck(value: unknown[], extraArgs: unknown[]): unknown[] {
|
||||
|
||||
function randomItem(value: unknown[]): unknown {
|
||||
const len = value === undefined ? 0 : value.length;
|
||||
return len ? value[Math.floor(Math.random() * len)] : undefined;
|
||||
return len ? value[randomInt(len)] : undefined;
|
||||
}
|
||||
|
||||
function unique(value: unknown[], extraArgs: string[]): unknown[] {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isTraversableObject } from '../../utils';
|
||||
import type { IDataObject, INode, JsonObject } from '../..';
|
||||
import type { IDataObject, INode, JsonObject } from '@/Interfaces';
|
||||
import { ExecutionBaseError } from './execution-base.error';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { INode, JsonObject } from '..';
|
||||
import type { INode, JsonObject } from '@/Interfaces';
|
||||
import type { NodeOperationErrorOptions } from './node-api.error';
|
||||
import { NodeError } from './abstract/node.error';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { INode } from '..';
|
||||
import type { INode } from '@/Interfaces';
|
||||
import { ExecutionBaseError } from './abstract/execution-base.error';
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,8 @@ export {
|
||||
assert,
|
||||
removeCircularRefs,
|
||||
updateDisplayOptions,
|
||||
randomInt,
|
||||
randomString,
|
||||
} from './utils';
|
||||
export {
|
||||
isINodeProperties,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import FormData from 'form-data';
|
||||
import { merge } from 'lodash';
|
||||
|
||||
import { ALPHABET } from './Constants';
|
||||
import type { BinaryFileType, IDisplayOptions, INodeProperties, JsonObject } from './Interfaces';
|
||||
import { ApplicationError } from './errors/application.error';
|
||||
|
||||
import { merge } from 'lodash';
|
||||
|
||||
const readStreamClasses = new Set(['ReadStream', 'Readable', 'ReadableStream']);
|
||||
|
||||
// NOTE: BigInt.prototype.toJSON is not available, which causes JSON.stringify to throw an error
|
||||
@@ -179,3 +180,36 @@ export function updateDisplayOptions(
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function randomInt(max: number): number;
|
||||
export function randomInt(min: number, max: number): number;
|
||||
/**
|
||||
* Generates a random integer within a specified range.
|
||||
*
|
||||
* @param {number} min - The lower bound of the range. If `max` is not provided, this value is used as the upper bound and the lower bound is set to 0.
|
||||
* @param {number} [max] - The upper bound of the range, not inclusive.
|
||||
* @returns {number} A random integer within the specified range.
|
||||
*/
|
||||
export function randomInt(min: number, max?: number): number {
|
||||
if (max === undefined) {
|
||||
max = min;
|
||||
min = 0;
|
||||
}
|
||||
return min + (crypto.getRandomValues(new Uint32Array(1))[0] % (max - min));
|
||||
}
|
||||
|
||||
export function randomString(length: number): string;
|
||||
export function randomString(minLength: number, maxLength: number): string;
|
||||
/**
|
||||
* Generates a random alphanumeric string of a specified length, or within a range of lengths.
|
||||
*
|
||||
* @param {number} minLength - If `maxLength` is not provided, this is the length of the string to generate. Otherwise, this is the lower bound of the range of possible lengths.
|
||||
* @param {number} [maxLength] - The upper bound of the range of possible lengths. If provided, the actual length of the string will be a random number between `minLength` and `maxLength`, inclusive.
|
||||
* @returns {string} A random alphanumeric string of the specified length or within the specified range of lengths.
|
||||
*/
|
||||
export function randomString(minLength: number, maxLength?: number): string {
|
||||
const length = maxLength === undefined ? minLength : randomInt(minLength, maxLength + 1);
|
||||
return [...crypto.getRandomValues(new Uint32Array(length))]
|
||||
.map((byte) => ALPHABET[byte % ALPHABET.length])
|
||||
.join('');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { INode, INodeParameters, INodeProperties, INodeTypeDescription } from '@/Interfaces';
|
||||
import type { Workflow } from '../src';
|
||||
import type { Workflow } from '@/Workflow';
|
||||
|
||||
import { getNodeParameters, getNodeHints, isSingleExecution } from '@/NodeHelpers';
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import { v5 as uuidv5, v3 as uuidv3, v4 as uuidv4, v1 as uuidv1 } from 'uuid';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
import {
|
||||
ANONYMIZATION_CHARACTER as CHAR,
|
||||
generateNodesGraph,
|
||||
getDomainBase,
|
||||
getDomainPath,
|
||||
} from '@/TelemetryHelpers';
|
||||
import { ApplicationError, STICKY_NODE_TYPE, type IWorkflowBase } from '@/index';
|
||||
import { nodeTypes } from './ExpressionExtensions/Helpers';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import * as nodeHelpers from '@/NodeHelpers';
|
||||
import type { IWorkflowBase } from '@/Interfaces';
|
||||
import { STICKY_NODE_TYPE } from '@/Constants';
|
||||
import { ApplicationError } from '@/errors';
|
||||
import { randomInt } from '@/utils';
|
||||
|
||||
describe('getDomainBase should return protocol plus domain', () => {
|
||||
test('in valid URLs', () => {
|
||||
@@ -872,22 +876,12 @@ function uuidUrls(
|
||||
];
|
||||
}
|
||||
|
||||
function digit() {
|
||||
return Math.floor(Math.random() * 10);
|
||||
}
|
||||
|
||||
function positiveDigit(): number {
|
||||
const d = digit();
|
||||
|
||||
return d === 0 ? positiveDigit() : d;
|
||||
}
|
||||
|
||||
function numericId(length = positiveDigit()) {
|
||||
return Array.from({ length }, digit).join('');
|
||||
function numericId(length = randomInt(1, 10)) {
|
||||
return Array.from({ length }, () => randomInt(10)).join('');
|
||||
}
|
||||
|
||||
function alphanumericId() {
|
||||
return chooseRandomly([`john${numericId()}`, `title${numericId(1)}`, numericId()]);
|
||||
}
|
||||
|
||||
const chooseRandomly = <T>(array: T[]) => array[Math.floor(Math.random() * array.length)];
|
||||
const chooseRandomly = <T>(array: T[]) => array[randomInt(array.length)];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WorkflowActivationError } from '@/index';
|
||||
import { WorkflowActivationError } from '@/errors';
|
||||
|
||||
describe('WorkflowActivationError', () => {
|
||||
it('should default to `error` level', () => {
|
||||
|
||||
7
packages/workflow/test/setup.ts
Normal file
7
packages/workflow/test/setup.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { randomFillSync } from 'crypto';
|
||||
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
value: {
|
||||
getRandomValues: (buffer: NodeJS.ArrayBufferView) => randomFillSync(buffer),
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,14 @@
|
||||
import { ALPHABET } from '@/Constants';
|
||||
import { ApplicationError } from '@/errors/application.error';
|
||||
import { jsonParse, jsonStringify, deepCopy, isObjectEmpty, fileTypeFromMimeType } from '@/utils';
|
||||
import {
|
||||
jsonParse,
|
||||
jsonStringify,
|
||||
deepCopy,
|
||||
isObjectEmpty,
|
||||
fileTypeFromMimeType,
|
||||
randomInt,
|
||||
randomString,
|
||||
} from '@/utils';
|
||||
|
||||
describe('isObjectEmpty', () => {
|
||||
it('should handle null and undefined', () => {
|
||||
@@ -237,3 +246,47 @@ describe('fileTypeFromMimeType', () => {
|
||||
expect(fileTypeFromMimeType('application/pdf')).toEqual('pdf');
|
||||
});
|
||||
});
|
||||
|
||||
const repeat = (fn: () => void, times = 10) => Array(times).fill(0).forEach(fn);
|
||||
|
||||
describe('randomInt', () => {
|
||||
it('should generate random integers', () => {
|
||||
repeat(() => {
|
||||
const result = randomInt(10);
|
||||
expect(result).toBeLessThanOrEqual(10);
|
||||
expect(result).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate random in range', () => {
|
||||
repeat(() => {
|
||||
const result = randomInt(10, 100);
|
||||
expect(result).toBeLessThanOrEqual(100);
|
||||
expect(result).toBeGreaterThanOrEqual(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('randomString', () => {
|
||||
it('should return a random string of the specified length', () => {
|
||||
repeat(() => {
|
||||
const result = randomString(42);
|
||||
expect(result).toHaveLength(42);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a random string of the in the length range', () => {
|
||||
repeat(() => {
|
||||
const result = randomString(10, 100);
|
||||
expect(result.length).toBeGreaterThanOrEqual(10);
|
||||
expect(result.length).toBeLessThanOrEqual(100);
|
||||
});
|
||||
});
|
||||
|
||||
it('should only contain characters from the specified character set', () => {
|
||||
repeat(() => {
|
||||
const result = randomString(1000);
|
||||
result.split('').every((char) => ALPHABET.includes(char));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
},
|
||||
"lib": ["es2020", "es2022.error", "dom"],
|
||||
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
|
||||
},
|
||||
"include": ["src/**/*.ts", "test/**/*.ts"]
|
||||
|
||||
Reference in New Issue
Block a user