mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
feat(core): Use WebCrypto to generate all random numbers and strings (#9786)
This commit is contained in:
parent
cfc4db00e3
commit
65c5609ab5
|
@ -1,3 +1,4 @@
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
import { successToast } from '../pages/notifications';
|
import { successToast } from '../pages/notifications';
|
||||||
|
@ -85,7 +86,7 @@ describe('Code node', () => {
|
||||||
cy.getByTestId('ask-ai-cta-tooltip-no-prompt').should('exist');
|
cy.getByTestId('ask-ai-cta-tooltip-no-prompt').should('exist');
|
||||||
cy.getByTestId('ask-ai-prompt-input')
|
cy.getByTestId('ask-ai-prompt-input')
|
||||||
// Type random 14 character string
|
// Type random 14 character string
|
||||||
.type([...Array(14)].map(() => ((Math.random() * 36) | 0).toString(36)).join(''));
|
.type(nanoid(14));
|
||||||
|
|
||||||
cy.getByTestId('ask-ai-cta').realHover();
|
cy.getByTestId('ask-ai-cta').realHover();
|
||||||
cy.getByTestId('ask-ai-cta-tooltip-prompt-too-short').should('exist');
|
cy.getByTestId('ask-ai-cta-tooltip-prompt-too-short').should('exist');
|
||||||
|
@ -93,14 +94,14 @@ describe('Code node', () => {
|
||||||
cy.getByTestId('ask-ai-prompt-input')
|
cy.getByTestId('ask-ai-prompt-input')
|
||||||
.clear()
|
.clear()
|
||||||
// Type random 15 character string
|
// Type random 15 character string
|
||||||
.type([...Array(15)].map(() => ((Math.random() * 36) | 0).toString(36)).join(''));
|
.type(nanoid(15));
|
||||||
cy.getByTestId('ask-ai-cta').should('be.enabled');
|
cy.getByTestId('ask-ai-cta').should('be.enabled');
|
||||||
|
|
||||||
cy.getByTestId('ask-ai-prompt-counter').should('contain.text', '15 / 600');
|
cy.getByTestId('ask-ai-prompt-counter').should('contain.text', '15 / 600');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send correct schema and replace code', () => {
|
it('should send correct schema and replace code', () => {
|
||||||
const prompt = [...Array(20)].map(() => ((Math.random() * 36) | 0).toString(36)).join('');
|
const prompt = nanoid(20);
|
||||||
cy.get('#tab-ask-ai').click();
|
cy.get('#tab-ask-ai').click();
|
||||||
ndv.actions.executePrevious();
|
ndv.actions.executePrevious();
|
||||||
|
|
||||||
|
@ -130,7 +131,7 @@ describe('Code node', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show error based on status code', () => {
|
it('should show error based on status code', () => {
|
||||||
const prompt = [...Array(20)].map(() => ((Math.random() * 36) | 0).toString(36)).join('');
|
const prompt = nanoid(20);
|
||||||
cy.get('#tab-ask-ai').click();
|
cy.get('#tab-ask-ai').click();
|
||||||
ndv.actions.executePrevious();
|
ndv.actions.executePrevious();
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
import type { IDataObject, IPinData, ITaskData, ITaskDataConnections } from 'n8n-workflow';
|
import type { IDataObject, IPinData, ITaskData, ITaskDataConnections } from 'n8n-workflow';
|
||||||
import { clickExecuteWorkflowButton } from '../composables/workflow';
|
import { clickExecuteWorkflowButton } from '../composables/workflow';
|
||||||
|
|
||||||
|
@ -85,7 +86,7 @@ export function runMockWorkflowExecution({
|
||||||
runData: Array<ReturnType<typeof createMockNodeExecutionData>>;
|
runData: Array<ReturnType<typeof createMockNodeExecutionData>>;
|
||||||
workflowExecutionData?: ReturnType<typeof createMockWorkflowExecutionData>;
|
workflowExecutionData?: ReturnType<typeof createMockWorkflowExecutionData>;
|
||||||
}) {
|
}) {
|
||||||
const executionId = Math.random().toString(36).substring(4);
|
const executionId = nanoid(8);
|
||||||
|
|
||||||
cy.intercept('POST', '/rest/workflows/**/run', {
|
cy.intercept('POST', '/rest/workflows/**/run', {
|
||||||
statusCode: 201,
|
statusCode: 201,
|
||||||
|
|
|
@ -7,6 +7,7 @@ module.exports = {
|
||||||
globalSetup: '<rootDir>/test/setup.ts',
|
globalSetup: '<rootDir>/test/setup.ts',
|
||||||
globalTeardown: '<rootDir>/test/teardown.ts',
|
globalTeardown: '<rootDir>/test/teardown.ts',
|
||||||
setupFilesAfterEnv: [
|
setupFilesAfterEnv: [
|
||||||
|
'n8n-workflow/test/setup.ts',
|
||||||
'<rootDir>/test/setup-test-folder.ts',
|
'<rootDir>/test/setup-test-folder.ts',
|
||||||
'<rootDir>/test/setup-mocks.ts',
|
'<rootDir>/test/setup-mocks.ts',
|
||||||
'<rootDir>/test/extend-expect.ts',
|
'<rootDir>/test/extend-expect.ts',
|
||||||
|
|
|
@ -3,6 +3,8 @@ import type { Entry as LdapUser } from 'ldapts';
|
||||||
import { Filter } from 'ldapts/filters/Filter';
|
import { Filter } from 'ldapts/filters/Filter';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { validate } from 'jsonschema';
|
import { validate } from 'jsonschema';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { User } from '@db/entities/User';
|
import { User } from '@db/entities/User';
|
||||||
|
@ -38,13 +40,6 @@ export const getLdapLoginLabel = (): string => config.getEnv(LDAP_LOGIN_LABEL);
|
||||||
*/
|
*/
|
||||||
export const isLdapLoginEnabled = (): boolean => config.getEnv(LDAP_LOGIN_ENABLED);
|
export const isLdapLoginEnabled = (): boolean => config.getEnv(LDAP_LOGIN_ENABLED);
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a random password to be assigned to the LDAP users
|
|
||||||
*/
|
|
||||||
export const randomPassword = (): string => {
|
|
||||||
return Math.random().toString(36).slice(-8);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the structure of the LDAP configuration schema
|
* Validate the structure of the LDAP configuration schema
|
||||||
*/
|
*/
|
||||||
|
@ -161,7 +156,7 @@ export const mapLdapUserToDbUser = (
|
||||||
Object.assign(user, data);
|
Object.assign(user, data);
|
||||||
if (toCreate) {
|
if (toCreate) {
|
||||||
user.role = 'global:member';
|
user.role = 'global:member';
|
||||||
user.password = randomPassword();
|
user.password = randomString(8);
|
||||||
user.disabled = false;
|
user.disabled = false;
|
||||||
} else {
|
} else {
|
||||||
user.disabled = true;
|
user.disabled = true;
|
||||||
|
@ -278,7 +273,7 @@ export const createLdapAuthIdentity = async (user: User, ldapId: string) => {
|
||||||
|
|
||||||
export const createLdapUserOnLocalDb = async (data: Partial<User>, ldapId: string) => {
|
export const createLdapUserOnLocalDb = async (data: Partial<User>, ldapId: string) => {
|
||||||
const { user } = await Container.get(UserRepository).createUserWithProject({
|
const { user } = await Container.get(UserRepository).createUserWithProject({
|
||||||
password: randomPassword(),
|
password: randomString(8),
|
||||||
role: 'global:member',
|
role: 'global:member',
|
||||||
...data,
|
...data,
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { createReadStream, createWriteStream, existsSync } from 'fs';
|
||||||
import { pipeline } from 'stream/promises';
|
import { pipeline } from 'stream/promises';
|
||||||
import replaceStream from 'replacestream';
|
import replaceStream from 'replacestream';
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
import { jsonParse } from 'n8n-workflow';
|
import { jsonParse, randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { ActiveExecutions } from '@/ActiveExecutions';
|
import { ActiveExecutions } from '@/ActiveExecutions';
|
||||||
|
@ -265,12 +265,7 @@ export class Start extends BaseCommand {
|
||||||
|
|
||||||
if (tunnelSubdomain === '') {
|
if (tunnelSubdomain === '') {
|
||||||
// When no tunnel subdomain did exist yet create a new random one
|
// When no tunnel subdomain did exist yet create a new random one
|
||||||
const availableCharacters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
tunnelSubdomain = randomString(24).toLowerCase();
|
||||||
tunnelSubdomain = Array.from({ length: 24 })
|
|
||||||
.map(() =>
|
|
||||||
availableCharacters.charAt(Math.floor(Math.random() * availableCharacters.length)),
|
|
||||||
)
|
|
||||||
.join('');
|
|
||||||
|
|
||||||
this.instanceSettings.update({ tunnelSubdomain });
|
this.instanceSettings.update({ tunnelSubdomain });
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
|
import { ALPHABET } from 'n8n-workflow';
|
||||||
import type { N8nInstanceType } from '@/Interfaces';
|
import type { N8nInstanceType } from '@/Interfaces';
|
||||||
|
|
||||||
const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 16);
|
const nanoid = customAlphabet(ALPHABET, 16);
|
||||||
|
|
||||||
export function generateNanoId() {
|
export function generateNanoId() {
|
||||||
return nanoid();
|
return nanoid();
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
import { stringify } from 'flatted';
|
import { stringify } from 'flatted';
|
||||||
|
import { NodeConnectionType, randomInt } from 'n8n-workflow';
|
||||||
|
|
||||||
import { mockInstance } from '@test/mocking';
|
import { mockInstance } from '@test/mocking';
|
||||||
import { randomInteger } from '@test-integration/random';
|
|
||||||
import { createWorkflow } from '@test-integration/db/workflows';
|
import { createWorkflow } from '@test-integration/db/workflows';
|
||||||
import { createExecution } from '@test-integration/db/executions';
|
import { createExecution } from '@test-integration/db/executions';
|
||||||
import * as testDb from '@test-integration/testDb';
|
import * as testDb from '@test-integration/testDb';
|
||||||
|
|
||||||
import { NodeConnectionType } from 'n8n-workflow';
|
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import { OrchestrationService } from '@/services/orchestration.service';
|
import { OrchestrationService } from '@/services/orchestration.service';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { ExecutionRecoveryService } from '@/executions/execution-recovery.service';
|
import { ExecutionRecoveryService } from '@/executions/execution-recovery.service';
|
||||||
import { ExecutionRepository } from '@/databases/repositories/execution.repository';
|
import { ExecutionRepository } from '@/databases/repositories/execution.repository';
|
||||||
|
import type { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
|
||||||
import { InternalHooks } from '@/InternalHooks';
|
import { InternalHooks } from '@/InternalHooks';
|
||||||
import { Push } from '@/push';
|
import { Push } from '@/push';
|
||||||
import { ARTIFICIAL_TASK_DATA } from '@/constants';
|
import { ARTIFICIAL_TASK_DATA } from '@/constants';
|
||||||
|
@ -20,9 +20,7 @@ import { NodeCrashedError } from '@/errors/node-crashed.error';
|
||||||
import { WorkflowCrashedError } from '@/errors/workflow-crashed.error';
|
import { WorkflowCrashedError } from '@/errors/workflow-crashed.error';
|
||||||
import { EventMessageNode } from '@/eventbus/EventMessageClasses/EventMessageNode';
|
import { EventMessageNode } from '@/eventbus/EventMessageClasses/EventMessageNode';
|
||||||
import { EventMessageWorkflow } from '@/eventbus/EventMessageClasses/EventMessageWorkflow';
|
import { EventMessageWorkflow } from '@/eventbus/EventMessageClasses/EventMessageWorkflow';
|
||||||
|
|
||||||
import type { EventMessageTypes as EventMessage } from '@/eventbus/EventMessageClasses';
|
import type { EventMessageTypes as EventMessage } from '@/eventbus/EventMessageClasses';
|
||||||
import type { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
|
|
||||||
import type { Logger } from '@/Logger';
|
import type { Logger } from '@/Logger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -301,7 +299,7 @@ describe('ExecutionRecoveryService', () => {
|
||||||
/**
|
/**
|
||||||
* Arrange
|
* Arrange
|
||||||
*/
|
*/
|
||||||
const inexistentExecutionId = randomInteger(100).toString();
|
const inexistentExecutionId = randomInt(100).toString();
|
||||||
const noMessages: EventMessage[] = [];
|
const noMessages: EventMessage[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -373,7 +371,7 @@ describe('ExecutionRecoveryService', () => {
|
||||||
/**
|
/**
|
||||||
* Arrange
|
* Arrange
|
||||||
*/
|
*/
|
||||||
const inexistentExecutionId = randomInteger(100).toString();
|
const inexistentExecutionId = randomInt(100).toString();
|
||||||
const messages = setupMessages(inexistentExecutionId, 'Some workflow');
|
const messages = setupMessages(inexistentExecutionId, 'Some workflow');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import type { FlowResult } from 'samlify/types/src/flow';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
|
import { UserRepository } from '@db/repositories/user.repository';
|
||||||
|
import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository';
|
||||||
|
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||||
|
import { AuthError } from '@/errors/response-errors/auth.error';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { PasswordUtility } from '@/services/password.utility';
|
import { PasswordUtility } from '@/services/password.utility';
|
||||||
|
|
||||||
import type { SamlPreferences } from './types/samlPreferences';
|
import type { SamlPreferences } from './types/samlPreferences';
|
||||||
import type { SamlUserAttributes } from './types/samlUserAttributes';
|
import type { SamlUserAttributes } from './types/samlUserAttributes';
|
||||||
import type { FlowResult } from 'samlify/types/src/flow';
|
|
||||||
import type { SamlAttributeMapping } from './types/samlAttributeMapping';
|
import type { SamlAttributeMapping } from './types/samlAttributeMapping';
|
||||||
import { SAML_LOGIN_ENABLED, SAML_LOGIN_LABEL } from './constants';
|
import { SAML_LOGIN_ENABLED, SAML_LOGIN_LABEL } from './constants';
|
||||||
import {
|
import {
|
||||||
|
@ -17,10 +24,6 @@ import {
|
||||||
} from '../ssoHelpers';
|
} from '../ssoHelpers';
|
||||||
import { getServiceProviderConfigTestReturnUrl } from './serviceProvider.ee';
|
import { getServiceProviderConfigTestReturnUrl } from './serviceProvider.ee';
|
||||||
import type { SamlConfiguration } from './types/requests';
|
import type { SamlConfiguration } from './types/requests';
|
||||||
import { UserRepository } from '@db/repositories/user.repository';
|
|
||||||
import { AuthIdentityRepository } from '@db/repositories/authIdentity.repository';
|
|
||||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
|
||||||
import { AuthError } from '@/errors/response-errors/auth.error';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the SAML feature is licensed and enabled in the instance
|
* Check whether the SAML feature is licensed and enabled in the instance
|
||||||
|
@ -73,39 +76,18 @@ export const isSamlPreferences = (candidate: unknown): candidate is SamlPreferen
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generatePassword(): string {
|
|
||||||
const length = 18;
|
|
||||||
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
||||||
const charsetNoNumbers = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
||||||
const randomNumber = Math.floor(Math.random() * 10);
|
|
||||||
const randomUpper = charset.charAt(Math.floor(Math.random() * charsetNoNumbers.length));
|
|
||||||
const randomNumberPosition = Math.floor(Math.random() * length);
|
|
||||||
const randomUpperPosition = Math.floor(Math.random() * length);
|
|
||||||
let password = '';
|
|
||||||
for (let i = 0, n = charset.length; i < length; ++i) {
|
|
||||||
password += charset.charAt(Math.floor(Math.random() * n));
|
|
||||||
}
|
|
||||||
password =
|
|
||||||
password.substring(0, randomNumberPosition) +
|
|
||||||
randomNumber.toString() +
|
|
||||||
password.substring(randomNumberPosition);
|
|
||||||
password =
|
|
||||||
password.substring(0, randomUpperPosition) +
|
|
||||||
randomUpper +
|
|
||||||
password.substring(randomUpperPosition);
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createUserFromSamlAttributes(attributes: SamlUserAttributes): Promise<User> {
|
export async function createUserFromSamlAttributes(attributes: SamlUserAttributes): Promise<User> {
|
||||||
return await Container.get(UserRepository).manager.transaction(async (trx) => {
|
const randomPassword = randomString(18);
|
||||||
const { user } = await Container.get(UserRepository).createUserWithProject(
|
const userRepository = Container.get(UserRepository);
|
||||||
|
return await userRepository.manager.transaction(async (trx) => {
|
||||||
|
const { user } = await userRepository.createUserWithProject(
|
||||||
{
|
{
|
||||||
email: attributes.email.toLowerCase(),
|
email: attributes.email.toLowerCase(),
|
||||||
firstName: attributes.firstName,
|
firstName: attributes.firstName,
|
||||||
lastName: attributes.lastName,
|
lastName: attributes.lastName,
|
||||||
role: 'global:member',
|
role: 'global:member',
|
||||||
// generates a password that is not used or known to the user
|
// generates a password that is not used or known to the user
|
||||||
password: await Container.get(PasswordUtility).hash(generatePassword()),
|
password: await Container.get(PasswordUtility).hash(randomPassword),
|
||||||
},
|
},
|
||||||
trx,
|
trx,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
import { CliWorkflowOperationError, SubworkflowOperationError } from 'n8n-workflow';
|
import { CliWorkflowOperationError, SubworkflowOperationError } from 'n8n-workflow';
|
||||||
import type { INode } from 'n8n-workflow';
|
import type { INode } from 'n8n-workflow';
|
||||||
import { STARTING_NODES } from './constants';
|
import { STARTING_NODES } from '@/constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the given id is a valid workflow id
|
* Returns if the given id is a valid workflow id
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import type { INode, WorkflowSettings } from 'n8n-workflow';
|
import type { INode, WorkflowSettings } from 'n8n-workflow';
|
||||||
import { SubworkflowOperationError, Workflow } from 'n8n-workflow';
|
import { SubworkflowOperationError, Workflow, randomInt } from 'n8n-workflow';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
|
@ -15,11 +15,7 @@ import { OwnershipService } from '@/services/ownership.service';
|
||||||
import { PermissionChecker } from '@/UserManagement/PermissionChecker';
|
import { PermissionChecker } from '@/UserManagement/PermissionChecker';
|
||||||
|
|
||||||
import { mockInstance } from '../shared/mocking';
|
import { mockInstance } from '../shared/mocking';
|
||||||
import {
|
import { randomCredentialPayload as randomCred, randomName } from '../integration/shared/random';
|
||||||
randomCredentialPayload as randomCred,
|
|
||||||
randomName,
|
|
||||||
randomPositiveDigit,
|
|
||||||
} from '../integration/shared/random';
|
|
||||||
import { LicenseMocker } from '../integration/shared/license';
|
import { LicenseMocker } from '../integration/shared/license';
|
||||||
import * as testDb from '../integration/shared/testDb';
|
import * as testDb from '../integration/shared/testDb';
|
||||||
import type { SaveCredentialFunction } from '../integration/shared/types';
|
import type { SaveCredentialFunction } from '../integration/shared/types';
|
||||||
|
@ -77,7 +73,7 @@ const ownershipService = mockInstance(OwnershipService);
|
||||||
|
|
||||||
const createWorkflow = async (nodes: INode[], workflowOwner?: User): Promise<WorkflowEntity> => {
|
const createWorkflow = async (nodes: INode[], workflowOwner?: User): Promise<WorkflowEntity> => {
|
||||||
const workflowDetails = {
|
const workflowDetails = {
|
||||||
id: randomPositiveDigit().toString(),
|
id: randomInt(1, 10).toString(),
|
||||||
name: 'test',
|
name: 'test',
|
||||||
active: false,
|
active: false,
|
||||||
connections: {},
|
connections: {},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import type { Scope } from '@sentry/node';
|
import type { Scope } from '@sentry/node';
|
||||||
import { Credentials } from 'n8n-core';
|
import { Credentials } from 'n8n-core';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import type { ListQuery } from '@/requests';
|
import type { ListQuery } from '@/requests';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
|
@ -16,7 +17,6 @@ import {
|
||||||
randomCredentialPayload as payload,
|
randomCredentialPayload as payload,
|
||||||
randomCredentialPayload,
|
randomCredentialPayload,
|
||||||
randomName,
|
randomName,
|
||||||
randomString,
|
|
||||||
} from '../shared/random';
|
} from '../shared/random';
|
||||||
import {
|
import {
|
||||||
saveCredential,
|
saveCredential,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { IsNull } from '@n8n/typeorm';
|
import { IsNull } from '@n8n/typeorm';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
|
@ -8,13 +9,7 @@ import { UserRepository } from '@db/repositories/user.repository';
|
||||||
import { ProjectRepository } from '@db/repositories/project.repository';
|
import { ProjectRepository } from '@db/repositories/project.repository';
|
||||||
|
|
||||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||||
import {
|
import { randomApiKey, randomEmail, randomName, randomValidPassword } from './shared/random';
|
||||||
randomApiKey,
|
|
||||||
randomEmail,
|
|
||||||
randomName,
|
|
||||||
randomString,
|
|
||||||
randomValidPassword,
|
|
||||||
} from './shared/random';
|
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import * as utils from './shared/utils/';
|
import * as utils from './shared/utils/';
|
||||||
import { addApiKey, createOwner, createUser, createUserShell } from './shared/db/users';
|
import { addApiKey, createOwner, createUser, createUserShell } from './shared/db/users';
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import Container from 'typedi';
|
import Container from 'typedi';
|
||||||
|
import { randomInt, randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import { AuthService } from '@/auth/auth.service';
|
import { AuthService } from '@/auth/auth.service';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { AuthUserRepository } from '@db/repositories/authUser.repository';
|
import { AuthUserRepository } from '@db/repositories/authUser.repository';
|
||||||
import { randomPassword } from '@/Ldap/helpers';
|
|
||||||
import { TOTPService } from '@/Mfa/totp.service';
|
import { TOTPService } from '@/Mfa/totp.service';
|
||||||
|
|
||||||
import * as testDb from '../shared/testDb';
|
import * as testDb from '../shared/testDb';
|
||||||
import * as utils from '../shared/utils';
|
import * as utils from '../shared/utils';
|
||||||
import { randomDigit, randomString, randomValidPassword, uniqueId } from '../shared/random';
|
import { randomValidPassword, uniqueId } from '../shared/random';
|
||||||
import { createUser, createUserWithMfaEnabled } from '../shared/db/users';
|
import { createUser, createUserWithMfaEnabled } from '../shared/db/users';
|
||||||
|
|
||||||
jest.mock('@/telemetry');
|
jest.mock('@/telemetry');
|
||||||
|
@ -150,18 +150,6 @@ describe('Disable MFA setup', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Change password with MFA enabled', () => {
|
describe('Change password with MFA enabled', () => {
|
||||||
test('PATCH /me/password should fail due to missing MFA token', async () => {
|
|
||||||
const { user, rawPassword } = await createUserWithMfaEnabled();
|
|
||||||
|
|
||||||
const newPassword = randomPassword();
|
|
||||||
|
|
||||||
await testServer
|
|
||||||
.authAgentFor(user)
|
|
||||||
.patch('/me/password')
|
|
||||||
.send({ currentPassword: rawPassword, newPassword })
|
|
||||||
.expect(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('POST /change-password should fail due to missing MFA token', async () => {
|
test('POST /change-password should fail due to missing MFA token', async () => {
|
||||||
await createUserWithMfaEnabled();
|
await createUserWithMfaEnabled();
|
||||||
|
|
||||||
|
@ -185,7 +173,7 @@ describe('Change password with MFA enabled', () => {
|
||||||
.send({
|
.send({
|
||||||
password: newPassword,
|
password: newPassword,
|
||||||
token: resetPasswordToken,
|
token: resetPasswordToken,
|
||||||
mfaToken: randomDigit(),
|
mfaToken: randomInt(10),
|
||||||
})
|
})
|
||||||
.expect(404);
|
.expect(404);
|
||||||
});
|
});
|
||||||
|
@ -226,7 +214,7 @@ describe('Change password with MFA enabled', () => {
|
||||||
|
|
||||||
describe('Login', () => {
|
describe('Login', () => {
|
||||||
test('POST /login with email/password should succeed when mfa is disabled', async () => {
|
test('POST /login with email/password should succeed when mfa is disabled', async () => {
|
||||||
const password = randomPassword();
|
const password = randomString(8);
|
||||||
|
|
||||||
const user = await createUser({ password });
|
const user = await createUser({ password });
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
import { Container } from 'typedi';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
|
import { UserRepository } from '@db/repositories/user.repository';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
randomEmail,
|
randomEmail,
|
||||||
randomInvalidPassword,
|
randomInvalidPassword,
|
||||||
|
@ -11,8 +14,6 @@ import {
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import * as utils from './shared/utils/';
|
import * as utils from './shared/utils/';
|
||||||
import { createUserShell } from './shared/db/users';
|
import { createUserShell } from './shared/db/users';
|
||||||
import { UserRepository } from '@db/repositories/user.repository';
|
|
||||||
import Container from 'typedi';
|
|
||||||
|
|
||||||
const testServer = utils.setupTestServer({ endpointGroups: ['owner'] });
|
const testServer = utils.setupTestServer({ endpointGroups: ['owner'] });
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { v4 as uuid } from 'uuid';
|
||||||
import { compare } from 'bcryptjs';
|
import { compare } from 'bcryptjs';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import { AuthService } from '@/auth/auth.service';
|
import { AuthService } from '@/auth/auth.service';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
|
@ -12,6 +13,7 @@ import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { JwtService } from '@/services/jwt.service';
|
import { JwtService } from '@/services/jwt.service';
|
||||||
import { UserManagementMailer } from '@/UserManagement/email';
|
import { UserManagementMailer } from '@/UserManagement/email';
|
||||||
import { UserRepository } from '@db/repositories/user.repository';
|
import { UserRepository } from '@db/repositories/user.repository';
|
||||||
|
import { PasswordUtility } from '@/services/password.utility';
|
||||||
|
|
||||||
import { mockInstance } from '../shared/mocking';
|
import { mockInstance } from '../shared/mocking';
|
||||||
import { getAuthToken, setupTestServer } from './shared/utils/';
|
import { getAuthToken, setupTestServer } from './shared/utils/';
|
||||||
|
@ -19,12 +21,10 @@ import {
|
||||||
randomEmail,
|
randomEmail,
|
||||||
randomInvalidPassword,
|
randomInvalidPassword,
|
||||||
randomName,
|
randomName,
|
||||||
randomString,
|
|
||||||
randomValidPassword,
|
randomValidPassword,
|
||||||
} from './shared/random';
|
} from './shared/random';
|
||||||
import * as testDb from './shared/testDb';
|
import * as testDb from './shared/testDb';
|
||||||
import { createUser } from './shared/db/users';
|
import { createUser } from './shared/db/users';
|
||||||
import { PasswordUtility } from '@/services/password.utility';
|
|
||||||
|
|
||||||
config.set('userManagement.jwtSecret', randomString(5, 10));
|
config.set('userManagement.jwtSecret', randomString(5, 10));
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
import { CredentialsRepository } from '@db/repositories/credentials.repository';
|
||||||
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository';
|
||||||
|
|
||||||
import { randomApiKey, randomName, randomString } from '../shared/random';
|
import { randomApiKey, randomName } from '../shared/random';
|
||||||
import * as utils from '../shared/utils/';
|
import * as utils from '../shared/utils/';
|
||||||
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
|
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
|
||||||
import * as testDb from '../shared/testDb';
|
import * as testDb from '../shared/testDb';
|
||||||
|
|
|
@ -1,39 +1,19 @@
|
||||||
import { randomBytes } from 'crypto';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { randomInt, randomString, UPPERCASE_LETTERS } from 'n8n-workflow';
|
||||||
|
|
||||||
import { MIN_PASSWORD_CHAR_LENGTH, MAX_PASSWORD_CHAR_LENGTH } from '@/constants';
|
import { MIN_PASSWORD_CHAR_LENGTH, MAX_PASSWORD_CHAR_LENGTH } from '@/constants';
|
||||||
import type { CredentialPayload } from './types';
|
import type { CredentialPayload } from './types';
|
||||||
import { v4 as uuid } from 'uuid';
|
|
||||||
|
|
||||||
/**
|
export const randomApiKey = () => `n8n_api_${randomString(40)}`;
|
||||||
* Create a random alphanumeric string of random length between two limits, both inclusive.
|
|
||||||
* Limits should be even numbers (round down otherwise).
|
|
||||||
*/
|
|
||||||
export function randomString(min: number, max: number) {
|
|
||||||
const randomInteger = Math.floor(Math.random() * (max - min) + min) + 1;
|
|
||||||
return randomBytes(randomInteger / 2).toString('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function randomApiKey() {
|
export const chooseRandomly = <T>(array: T[]) => array[randomInt(array.length)];
|
||||||
return `n8n_api_${randomBytes(20).toString('hex')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const chooseRandomly = <T>(array: T[]) => array[Math.floor(Math.random() * array.length)];
|
const randomUppercaseLetter = () => chooseRandomly(UPPERCASE_LETTERS.split(''));
|
||||||
|
|
||||||
export const randomInteger = (max = 1000) => Math.floor(Math.random() * max);
|
|
||||||
|
|
||||||
export const randomDigit = () => Math.floor(Math.random() * 10);
|
|
||||||
|
|
||||||
export const randomPositiveDigit = (): number => {
|
|
||||||
const digit = randomDigit();
|
|
||||||
|
|
||||||
return digit === 0 ? randomPositiveDigit() : digit;
|
|
||||||
};
|
|
||||||
|
|
||||||
const randomUppercaseLetter = () => chooseRandomly('ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''));
|
|
||||||
|
|
||||||
export const randomValidPassword = () =>
|
export const randomValidPassword = () =>
|
||||||
randomString(MIN_PASSWORD_CHAR_LENGTH, MAX_PASSWORD_CHAR_LENGTH - 2) +
|
randomString(MIN_PASSWORD_CHAR_LENGTH, MAX_PASSWORD_CHAR_LENGTH - 2) +
|
||||||
randomUppercaseLetter() +
|
randomUppercaseLetter() +
|
||||||
randomDigit();
|
randomInt(10);
|
||||||
|
|
||||||
export const randomInvalidPassword = () =>
|
export const randomInvalidPassword = () =>
|
||||||
chooseRandomly([
|
chooseRandomly([
|
||||||
|
@ -54,7 +34,7 @@ const POPULAR_TOP_LEVEL_DOMAINS = ['com', 'org', 'net', 'io', 'edu'];
|
||||||
|
|
||||||
const randomTopLevelDomain = () => chooseRandomly(POPULAR_TOP_LEVEL_DOMAINS);
|
const randomTopLevelDomain = () => chooseRandomly(POPULAR_TOP_LEVEL_DOMAINS);
|
||||||
|
|
||||||
export const randomName = () => randomString(4, 8);
|
export const randomName = () => randomString(4, 8).toLowerCase();
|
||||||
|
|
||||||
export const randomCredentialPayload = (): CredentialPayload => ({
|
export const randomCredentialPayload = (): CredentialPayload => ({
|
||||||
name: randomName(),
|
name: randomName(),
|
||||||
|
|
|
@ -2,13 +2,12 @@ import type { DataSourceOptions, Repository } from '@n8n/typeorm';
|
||||||
import { DataSource as Connection } from '@n8n/typeorm';
|
import { DataSource as Connection } from '@n8n/typeorm';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import type { Class } from 'n8n-core';
|
import type { Class } from 'n8n-core';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import { getOptionOverrides } from '@db/config';
|
import { getOptionOverrides } from '@db/config';
|
||||||
|
|
||||||
import { randomString } from './random';
|
|
||||||
|
|
||||||
export const testDbPrefix = 'n8n_test_';
|
export const testDbPrefix = 'n8n_test_';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,7 +15,7 @@ export const testDbPrefix = 'n8n_test_';
|
||||||
*/
|
*/
|
||||||
export async function init() {
|
export async function init() {
|
||||||
const dbType = config.getEnv('database.type');
|
const dbType = config.getEnv('database.type');
|
||||||
const testDbName = `${testDbPrefix}${randomString(6, 10)}_${Date.now()}`;
|
const testDbName = `${testDbPrefix}${randomString(6, 10).toLowerCase()}_${Date.now()}`;
|
||||||
|
|
||||||
if (dbType === 'postgresdb') {
|
if (dbType === 'postgresdb') {
|
||||||
const bootstrapPostgres = await new Connection(
|
const bootstrapPostgres = await new Connection(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { type Response } from 'express';
|
import { type Response } from 'express';
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
import type { IHttpRequestMethods } from 'n8n-workflow';
|
import type { IHttpRequestMethods } from 'n8n-workflow';
|
||||||
|
|
||||||
import type { IWebhookManager, WebhookCORSRequest, WebhookRequest } from '@/Interfaces';
|
import type { IWebhookManager, WebhookCORSRequest, WebhookRequest } from '@/Interfaces';
|
||||||
import { webhookRequestHandler } from '@/WebhookHelpers';
|
import { webhookRequestHandler } from '@/WebhookHelpers';
|
||||||
|
|
||||||
|
@ -82,7 +84,7 @@ describe('WebhookHelpers', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle wildcard origin', async () => {
|
it('should handle wildcard origin', async () => {
|
||||||
const randomOrigin = (Math.random() * 10e6).toString(16);
|
const randomOrigin = randomString(10);
|
||||||
const req = mock<WebhookRequest | WebhookCORSRequest>({
|
const req = mock<WebhookRequest | WebhookCORSRequest>({
|
||||||
method: 'OPTIONS',
|
method: 'OPTIONS',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
|
import { randomInt } from 'n8n-workflow';
|
||||||
import { User } from '@db/entities/User';
|
import { User } from '@db/entities/User';
|
||||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
|
import { Project } from '@db/entities/Project';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
randomCredentialPayload,
|
randomCredentialPayload,
|
||||||
randomEmail,
|
randomEmail,
|
||||||
randomInteger,
|
|
||||||
randomName,
|
randomName,
|
||||||
uniqueId,
|
uniqueId,
|
||||||
} from '../../integration/shared/random';
|
} from '../../integration/shared/random';
|
||||||
import { Project } from '@/databases/entities/Project';
|
|
||||||
|
|
||||||
export const mockCredential = (): CredentialsEntity =>
|
export const mockCredential = (): CredentialsEntity =>
|
||||||
Object.assign(new CredentialsEntity(), randomCredentialPayload());
|
Object.assign(new CredentialsEntity(), randomCredentialPayload());
|
||||||
|
|
||||||
export const mockUser = (): User =>
|
export const mockUser = (): User =>
|
||||||
Object.assign(new User(), {
|
Object.assign(new User(), {
|
||||||
id: randomInteger(),
|
id: randomInt(1000),
|
||||||
email: randomEmail(),
|
email: randomEmail(),
|
||||||
firstName: randomName(),
|
firstName: randomName(),
|
||||||
lastName: randomName(),
|
lastName: randomName(),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { snakeCase } from 'lodash-es';
|
||||||
import { useSessionStorage } from '@vueuse/core';
|
import { useSessionStorage } from '@vueuse/core';
|
||||||
|
|
||||||
import { N8nButton, N8nInput, N8nTooltip } from 'n8n-design-system/components';
|
import { N8nButton, N8nInput, N8nTooltip } from 'n8n-design-system/components';
|
||||||
|
import { randomInt } from 'n8n-workflow';
|
||||||
import type { CodeExecutionMode, INodeExecutionData } from 'n8n-workflow';
|
import type { CodeExecutionMode, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
|
||||||
import type { BaseTextKey } from '@/plugins/i18n';
|
import type { BaseTextKey } from '@/plugins/i18n';
|
||||||
|
@ -208,7 +209,7 @@ function triggerLoadingChange() {
|
||||||
|
|
||||||
// Loading phrase change
|
// Loading phrase change
|
||||||
if (!lastPhraseChange || timestamp - lastPhraseChange >= loadingPhraseUpdateMs) {
|
if (!lastPhraseChange || timestamp - lastPhraseChange >= loadingPhraseUpdateMs) {
|
||||||
loadingPhraseIndex.value = Math.floor(Math.random() * loadingPhrasesCount);
|
loadingPhraseIndex.value = randomInt(loadingPhrasesCount);
|
||||||
lastPhraseChange = timestamp;
|
lastPhraseChange = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { faker } from '@faker-js/faker';
|
||||||
import { STORES, VIEWS } from '@/constants';
|
import { STORES, VIEWS } from '@/constants';
|
||||||
import ExecutionsList from '@/components/executions/global/GlobalExecutionsList.vue';
|
import ExecutionsList from '@/components/executions/global/GlobalExecutionsList.vue';
|
||||||
import type { IWorkflowDb } from '@/Interface';
|
import type { IWorkflowDb } from '@/Interface';
|
||||||
import type { ExecutionSummary } from 'n8n-workflow';
|
import { randomInt, type ExecutionSummary } from 'n8n-workflow';
|
||||||
import { retry, SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils';
|
import { retry, SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils';
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import { waitFor } from '@testing-library/vue';
|
import { waitFor } from '@testing-library/vue';
|
||||||
|
@ -22,7 +22,7 @@ vi.mock('vue-router', () => ({
|
||||||
let pinia: ReturnType<typeof createTestingPinia>;
|
let pinia: ReturnType<typeof createTestingPinia>;
|
||||||
|
|
||||||
const generateUndefinedNullOrString = () => {
|
const generateUndefinedNullOrString = () => {
|
||||||
switch (Math.floor(Math.random() * 4)) {
|
switch (randomInt(4)) {
|
||||||
case 0:
|
case 0:
|
||||||
return undefined;
|
return undefined;
|
||||||
case 1:
|
case 1:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event';
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
import { createPinia, PiniaVuePlugin, setActivePinia } from 'pinia';
|
import { createPinia, PiniaVuePlugin, setActivePinia } from 'pinia';
|
||||||
import type { ExecutionSummary } from 'n8n-workflow';
|
import { randomInt, type ExecutionSummary } from 'n8n-workflow';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import WorkflowExecutionsPreview from '@/components/executions/workflow/WorkflowExecutionsPreview.vue';
|
import WorkflowExecutionsPreview from '@/components/executions/workflow/WorkflowExecutionsPreview.vue';
|
||||||
import { EnterpriseEditionFeature, VIEWS } from '@/constants';
|
import { EnterpriseEditionFeature, VIEWS } from '@/constants';
|
||||||
|
@ -33,7 +33,7 @@ const $route = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUndefinedNullOrString = () => {
|
const generateUndefinedNullOrString = () => {
|
||||||
switch (Math.floor(Math.random() * 4)) {
|
switch (randomInt(4)) {
|
||||||
case 0:
|
case 0:
|
||||||
return undefined;
|
return undefined;
|
||||||
case 1:
|
case 1:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { CLOUD_BASE_URL_PRODUCTION, CLOUD_BASE_URL_STAGING, STORES } from '@/constants';
|
import { CLOUD_BASE_URL_PRODUCTION, CLOUD_BASE_URL_STAGING, STORES } from '@/constants';
|
||||||
import type { RootState } from '@/Interface';
|
import type { RootState } from '@/Interface';
|
||||||
import { setGlobalState } from 'n8n-workflow';
|
import { randomString, setGlobalState } from 'n8n-workflow';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ export const useRootStore = defineStore(STORES.ROOT, () => {
|
||||||
versionCli: '0.0.0',
|
versionCli: '0.0.0',
|
||||||
oauthCallbackUrls: {},
|
oauthCallbackUrls: {},
|
||||||
n8nMetadata: {},
|
n8nMetadata: {},
|
||||||
pushRef: Math.random().toString(36).substring(2, 15),
|
pushRef: randomString(10).toLowerCase(),
|
||||||
urlBaseWebhook: 'http://localhost:5678/',
|
urlBaseWebhook: 'http://localhost:5678/',
|
||||||
urlBaseEditor: 'http://localhost:5678',
|
urlBaseEditor: 'http://localhost:5678',
|
||||||
isNpmAvailable: false,
|
isNpmAvailable: false,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
IWebhookResponseData,
|
IWebhookResponseData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import { helpscoutApiRequest, helpscoutApiRequestAllItems } from './GenericFunctions';
|
import { helpscoutApiRequest, helpscoutApiRequestAllItems } from './GenericFunctions';
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ export class HelpScoutTrigger implements INodeType {
|
||||||
const body = {
|
const body = {
|
||||||
url: webhookUrl,
|
url: webhookUrl,
|
||||||
events,
|
events,
|
||||||
secret: Math.random().toString(36).substring(2, 15),
|
secret: randomString(10).toLowerCase(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const responseData = await helpscoutApiRequest.call(
|
const responseData = await helpscoutApiRequest.call(
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
INodeTypeBaseDescription,
|
INodeTypeBaseDescription,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError, randomInt } from 'n8n-workflow';
|
||||||
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
@ -52,7 +52,7 @@ const flattenKeys = (obj: IDataObject, path: string[] = []): IDataObject => {
|
||||||
|
|
||||||
const shuffleArray = (array: any[]) => {
|
const shuffleArray = (array: any[]) => {
|
||||||
for (let i = array.length - 1; i > 0; i--) {
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = randomInt(i + 1);
|
||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
IPairedItemData,
|
IPairedItemData,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeOperationError, deepCopy } from 'n8n-workflow';
|
import { NodeOperationError, deepCopy, randomInt } from 'n8n-workflow';
|
||||||
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
@ -53,7 +53,7 @@ const flattenKeys = (obj: IDataObject, path: string[] = []): IDataObject => {
|
||||||
|
|
||||||
const shuffleArray = (array: any[]) => {
|
const shuffleArray = (array: any[]) => {
|
||||||
for (let i = array.length - 1; i > 0; i--) {
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = randomInt(i + 1);
|
||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
INodeExecutionData,
|
INodeExecutionData,
|
||||||
GenericValue,
|
GenericValue,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { ApplicationError, NodeOperationError } from 'n8n-workflow';
|
import { ApplicationError, NodeOperationError, randomInt } from 'n8n-workflow';
|
||||||
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import isEqual from 'lodash/isEqual';
|
import isEqual from 'lodash/isEqual';
|
||||||
|
@ -47,7 +47,7 @@ export const flattenKeys = (obj: IDataObject, path: string[] = []): IDataObject
|
||||||
|
|
||||||
export const shuffleArray = (array: any[]) => {
|
export const shuffleArray = (array: any[]) => {
|
||||||
for (let i = array.length - 1; i > 0; i--) {
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = randomInt(i + 1);
|
||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type {
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
import { randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import * as mqtt from 'mqtt';
|
import * as mqtt from 'mqtt';
|
||||||
import { formatPrivateKey } from '@utils/utilities';
|
import { formatPrivateKey } from '@utils/utilities';
|
||||||
|
@ -116,7 +117,7 @@ export class Mqtt implements INodeType {
|
||||||
const brokerUrl = `${protocol}://${host}`;
|
const brokerUrl = `${protocol}://${host}`;
|
||||||
const port = (credentials.port as number) || 1883;
|
const port = (credentials.port as number) || 1883;
|
||||||
const clientId =
|
const clientId =
|
||||||
(credentials.clientId as string) || `mqttjs_${Math.random().toString(16).substr(2, 8)}`;
|
(credentials.clientId as string) || `mqttjs_${randomString(8).toLowerCase()}`;
|
||||||
const clean = credentials.clean as boolean;
|
const clean = credentials.clean as boolean;
|
||||||
const ssl = credentials.ssl as boolean;
|
const ssl = credentials.ssl as boolean;
|
||||||
const ca = formatPrivateKey(credentials.ca as string);
|
const ca = formatPrivateKey(credentials.ca as string);
|
||||||
|
@ -189,8 +190,7 @@ export class Mqtt implements INodeType {
|
||||||
const host = credentials.host as string;
|
const host = credentials.host as string;
|
||||||
const brokerUrl = `${protocol}://${host}`;
|
const brokerUrl = `${protocol}://${host}`;
|
||||||
const port = (credentials.port as number) || 1883;
|
const port = (credentials.port as number) || 1883;
|
||||||
const clientId =
|
const clientId = (credentials.clientId as string) || `mqttjs_${randomString(8).toLowerCase()}`;
|
||||||
(credentials.clientId as string) || `mqttjs_${Math.random().toString(16).substr(2, 8)}`;
|
|
||||||
const clean = credentials.clean as boolean;
|
const clean = credentials.clean as boolean;
|
||||||
const ssl = credentials.ssl as boolean;
|
const ssl = credentials.ssl as boolean;
|
||||||
const ca = credentials.ca as string;
|
const ca = credentials.ca as string;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
IDeferredPromise,
|
IDeferredPromise,
|
||||||
IRun,
|
IRun,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError, randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import * as mqtt from 'mqtt';
|
import * as mqtt from 'mqtt';
|
||||||
import { formatPrivateKey } from '@utils/utilities';
|
import { formatPrivateKey } from '@utils/utilities';
|
||||||
|
@ -109,8 +109,7 @@ export class MqttTrigger implements INodeType {
|
||||||
const host = credentials.host as string;
|
const host = credentials.host as string;
|
||||||
const brokerUrl = `${protocol}://${host}`;
|
const brokerUrl = `${protocol}://${host}`;
|
||||||
const port = (credentials.port as number) || 1883;
|
const port = (credentials.port as number) || 1883;
|
||||||
const clientId =
|
const clientId = (credentials.clientId as string) || `mqttjs_${randomString(8).toLowerCase()}`;
|
||||||
(credentials.clientId as string) || `mqttjs_${Math.random().toString(16).substr(2, 8)}`;
|
|
||||||
const clean = credentials.clean as boolean;
|
const clean = credentials.clean as boolean;
|
||||||
const ssl = credentials.ssl as boolean;
|
const ssl = credentials.ssl as boolean;
|
||||||
const ca = formatPrivateKey(credentials.ca as string);
|
const ca = formatPrivateKey(credentials.ca as string);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import type {
|
||||||
JsonObject,
|
JsonObject,
|
||||||
IRequestOptions,
|
IRequestOptions,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeApiError } from 'n8n-workflow';
|
import { NodeApiError, randomInt } from 'n8n-workflow';
|
||||||
|
|
||||||
const serviceJSONRPC = 'object';
|
const serviceJSONRPC = 'object';
|
||||||
const methodJSONRPC = 'execute';
|
const methodJSONRPC = 'execute';
|
||||||
|
@ -65,7 +65,7 @@ export interface IOdooNameValueFields {
|
||||||
}>;
|
}>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IOdooResponceFields {
|
export interface IOdooResponseFields {
|
||||||
fields: Array<{
|
fields: Array<{
|
||||||
field: string;
|
field: string;
|
||||||
fromList?: boolean;
|
fromList?: boolean;
|
||||||
|
@ -97,8 +97,8 @@ export function processNameValueFields(value: IDataObject) {
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// function processResponceFields(value: IDataObject) {
|
// function processResponseFields(value: IDataObject) {
|
||||||
// const data = value as unknown as IOdooResponceFields;
|
// const data = value as unknown as IOdooResponseFields;
|
||||||
// return data?.fields?.map((entry) => entry.field);
|
// return data?.fields?.map((entry) => entry.field);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
@ -121,13 +121,13 @@ export async function odooJSONRPCRequest(
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const responce = await this.helpers.request(options);
|
const response = await this.helpers.request(options);
|
||||||
if (responce.error) {
|
if (response.error) {
|
||||||
throw new NodeApiError(this.getNode(), responce.error.data as JsonObject, {
|
throw new NodeApiError(this.getNode(), response.error.data as JsonObject, {
|
||||||
message: responce.error.data.message,
|
message: response.error.data.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return responce.result;
|
return response.result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ export async function odooGetModelFields(
|
||||||
['string', 'type', 'help', 'required', 'name'],
|
['string', 'type', 'help', 'required', 'name'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await odooJSONRPCRequest.call(this, body, url);
|
const result = await odooJSONRPCRequest.call(this, body, url);
|
||||||
|
@ -194,7 +194,7 @@ export async function odooCreate(
|
||||||
newItem || {},
|
newItem || {},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await odooJSONRPCRequest.call(this, body, url);
|
const result = await odooJSONRPCRequest.call(this, body, url);
|
||||||
|
@ -238,7 +238,7 @@ export async function odooGet(
|
||||||
fieldsToReturn || [],
|
fieldsToReturn || [],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await odooJSONRPCRequest.call(this, body, url);
|
const result = await odooJSONRPCRequest.call(this, body, url);
|
||||||
|
@ -279,7 +279,7 @@ export async function odooGetAll(
|
||||||
limit,
|
limit,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await odooJSONRPCRequest.call(this, body, url);
|
const result = await odooJSONRPCRequest.call(this, body, url);
|
||||||
|
@ -329,7 +329,7 @@ export async function odooUpdate(
|
||||||
fieldsToUpdate,
|
fieldsToUpdate,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
await odooJSONRPCRequest.call(this, body, url);
|
await odooJSONRPCRequest.call(this, body, url);
|
||||||
|
@ -371,7 +371,7 @@ export async function odooDelete(
|
||||||
itemsID ? [+itemsID] : [],
|
itemsID ? [+itemsID] : [],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
await odooJSONRPCRequest.call(this, body, url);
|
await odooJSONRPCRequest.call(this, body, url);
|
||||||
|
@ -397,7 +397,7 @@ export async function odooGetUserID(
|
||||||
method: 'login',
|
method: 'login',
|
||||||
args: [db, username, password],
|
args: [db, username, password],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
const loginResult = await odooJSONRPCRequest.call(this, body, url);
|
const loginResult = await odooJSONRPCRequest.call(this, body, url);
|
||||||
return loginResult as unknown as number;
|
return loginResult as unknown as number;
|
||||||
|
@ -419,7 +419,7 @@ export async function odooGetServerVersion(
|
||||||
method: 'version',
|
method: 'version',
|
||||||
args: [],
|
args: [],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
const result = await odooJSONRPCRequest.call(this, body, url);
|
const result = await odooJSONRPCRequest.call(this, body, url);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type {
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
IRequestOptions,
|
IRequestOptions,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { deepCopy } from 'n8n-workflow';
|
import { deepCopy, randomInt } from 'n8n-workflow';
|
||||||
|
|
||||||
import { capitalCase } from 'change-case';
|
import { capitalCase } from 'change-case';
|
||||||
import {
|
import {
|
||||||
|
@ -115,8 +115,8 @@ export class Odoo implements INodeType {
|
||||||
const db = odooGetDBName(credentials.db as string, url);
|
const db = odooGetDBName(credentials.db as string, url);
|
||||||
const userID = await odooGetUserID.call(this, db, username, password, url);
|
const userID = await odooGetUserID.call(this, db, username, password, url);
|
||||||
|
|
||||||
const responce = await odooGetModelFields.call(this, db, userID, password, resource, url);
|
const response = await odooGetModelFields.call(this, db, userID, password, resource, url);
|
||||||
const options = Object.values(responce).map((field) => {
|
const options = Object.values(response).map((field) => {
|
||||||
const optionField = field as { [key: string]: string };
|
const optionField = field as { [key: string]: string };
|
||||||
let name = '';
|
let name = '';
|
||||||
try {
|
try {
|
||||||
|
@ -158,12 +158,12 @@ export class Odoo implements INodeType {
|
||||||
['name', 'model', 'modules'],
|
['name', 'model', 'modules'],
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
const responce = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
|
const response = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
|
||||||
|
|
||||||
const options = responce.map((model) => {
|
const options = response.map((model) => {
|
||||||
return {
|
return {
|
||||||
name: model.name,
|
name: model.name,
|
||||||
value: model.model,
|
value: model.model,
|
||||||
|
@ -188,12 +188,12 @@ export class Odoo implements INodeType {
|
||||||
method: 'execute',
|
method: 'execute',
|
||||||
args: [db, userID, password, 'res.country.state', 'search_read', [], ['id', 'name']],
|
args: [db, userID, password, 'res.country.state', 'search_read', [], ['id', 'name']],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
const responce = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
|
const response = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
|
||||||
|
|
||||||
const options = responce.map((state) => {
|
const options = response.map((state) => {
|
||||||
return {
|
return {
|
||||||
name: state.name as string,
|
name: state.name as string,
|
||||||
value: state.id,
|
value: state.id,
|
||||||
|
@ -217,12 +217,12 @@ export class Odoo implements INodeType {
|
||||||
method: 'execute',
|
method: 'execute',
|
||||||
args: [db, userID, password, 'res.country', 'search_read', [], ['id', 'name']],
|
args: [db, userID, password, 'res.country', 'search_read', [], ['id', 'name']],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
const responce = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
|
const response = (await odooJSONRPCRequest.call(this, body, url)) as IDataObject[];
|
||||||
|
|
||||||
const options = responce.map((country) => {
|
const options = response.map((country) => {
|
||||||
return {
|
return {
|
||||||
name: country.name as string,
|
name: country.name as string,
|
||||||
value: country.id,
|
value: country.id,
|
||||||
|
@ -252,7 +252,7 @@ export class Odoo implements INodeType {
|
||||||
credentials?.password,
|
credentials?.password,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
id: Math.floor(Math.random() * 100),
|
id: randomInt(100),
|
||||||
};
|
};
|
||||||
|
|
||||||
const options: IRequestOptions = {
|
const options: IRequestOptions = {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { NodeVM } from '@n8n/vm2';
|
import { NodeVM } from '@n8n/vm2';
|
||||||
import { type IExecuteFunctions, type INodeExecutionData, NodeOperationError } from 'n8n-workflow';
|
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { NodeOperationError, randomInt } from 'n8n-workflow';
|
||||||
|
|
||||||
export const shuffleArray = (array: any[]) => {
|
export const shuffleArray = (array: any[]) => {
|
||||||
for (let i = array.length - 1; i > 0; i--) {
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = randomInt(i + 1);
|
||||||
[array[i], array[j]] = [array[j], array[i]];
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,7 +10,7 @@ import type {
|
||||||
IWebhookResponseData,
|
IWebhookResponseData,
|
||||||
JsonObject,
|
JsonObject,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeApiError } from 'n8n-workflow';
|
import { NodeApiError, randomString } from 'n8n-workflow';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ITypeformAnswer,
|
ITypeformAnswer,
|
||||||
|
@ -177,7 +177,7 @@ export class TypeformTrigger implements INodeType {
|
||||||
const webhookUrl = this.getNodeWebhookUrl('default');
|
const webhookUrl = this.getNodeWebhookUrl('default');
|
||||||
|
|
||||||
const formId = this.getNodeParameter('formId') as string;
|
const formId = this.getNodeParameter('formId') as string;
|
||||||
const webhookId = 'n8n-' + Math.random().toString(36).substring(2, 15);
|
const webhookId = 'n8n-' + randomString(10).toLowerCase();
|
||||||
|
|
||||||
const endpoint = `forms/${formId}/webhooks/${webhookId}`;
|
const endpoint = `forms/${formId}/webhooks/${webhookId}`;
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
/** @type {import('jest').Config} */
|
/** @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';
|
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 BINARY_ENCODING = 'base64';
|
||||||
export const WAIT_TIME_UNLIMITED = '3000-01-01T00:00:00.000Z';
|
export const WAIT_TIME_UNLIMITED = '3000-01-01T00:00:00.000Z';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { randomInt } from './utils';
|
||||||
|
|
||||||
interface BaseTriggerTime<T extends string> {
|
interface BaseTriggerTime<T extends string> {
|
||||||
mode: T;
|
mode: T;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +49,7 @@ export type TriggerTime =
|
||||||
| EveryWeek
|
| EveryWeek
|
||||||
| EveryMonth;
|
| EveryMonth;
|
||||||
|
|
||||||
const randomSecond = () => Math.floor(Math.random() * 60).toString();
|
const randomSecond = () => randomInt(60).toString();
|
||||||
|
|
||||||
export const toCronExpression = (item: TriggerTime): CronExpression => {
|
export const toCronExpression = (item: TriggerTime): CronExpression => {
|
||||||
if (item.mode === 'everyMinute') return `${randomSecond()} * * * * *`;
|
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 { ExpressionError } from '../errors/expression.error';
|
||||||
import { ExpressionExtensionError } from '../errors/expression-extension.error';
|
import { ExpressionExtensionError } from '../errors/expression-extension.error';
|
||||||
import type { Extension, ExtensionMap } from './Extensions';
|
import type { Extension, ExtensionMap } from './Extensions';
|
||||||
import { compact as oCompact } from './ObjectExtensions';
|
import { compact as oCompact } from './ObjectExtensions';
|
||||||
import deepEqual from 'deep-equal';
|
import { randomInt } from '../utils';
|
||||||
import uniqWith from 'lodash/uniqWith';
|
|
||||||
|
|
||||||
function first(value: unknown[]): unknown {
|
function first(value: unknown[]): unknown {
|
||||||
return value[0];
|
return value[0];
|
||||||
|
@ -49,7 +51,7 @@ function pluck(value: unknown[], extraArgs: unknown[]): unknown[] {
|
||||||
|
|
||||||
function randomItem(value: unknown[]): unknown {
|
function randomItem(value: unknown[]): unknown {
|
||||||
const len = value === undefined ? 0 : value.length;
|
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[] {
|
function unique(value: unknown[], extraArgs: string[]): unknown[] {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { isTraversableObject } from '../../utils';
|
import { isTraversableObject } from '../../utils';
|
||||||
import type { IDataObject, INode, JsonObject } from '../..';
|
import type { IDataObject, INode, JsonObject } from '@/Interfaces';
|
||||||
import { ExecutionBaseError } from './execution-base.error';
|
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 type { NodeOperationErrorOptions } from './node-api.error';
|
||||||
import { NodeError } from './abstract/node.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';
|
import { ExecutionBaseError } from './abstract/execution-base.error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -34,6 +34,8 @@ export {
|
||||||
assert,
|
assert,
|
||||||
removeCircularRefs,
|
removeCircularRefs,
|
||||||
updateDisplayOptions,
|
updateDisplayOptions,
|
||||||
|
randomInt,
|
||||||
|
randomString,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
export {
|
export {
|
||||||
isINodeProperties,
|
isINodeProperties,
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import FormData from 'form-data';
|
import FormData from 'form-data';
|
||||||
|
import { merge } from 'lodash';
|
||||||
|
|
||||||
|
import { ALPHABET } from './Constants';
|
||||||
import type { BinaryFileType, IDisplayOptions, INodeProperties, JsonObject } from './Interfaces';
|
import type { BinaryFileType, IDisplayOptions, INodeProperties, JsonObject } from './Interfaces';
|
||||||
import { ApplicationError } from './errors/application.error';
|
import { ApplicationError } from './errors/application.error';
|
||||||
|
|
||||||
import { merge } from 'lodash';
|
|
||||||
|
|
||||||
const readStreamClasses = new Set(['ReadStream', 'Readable', 'ReadableStream']);
|
const readStreamClasses = new Set(['ReadStream', 'Readable', 'ReadableStream']);
|
||||||
|
|
||||||
// NOTE: BigInt.prototype.toJSON is not available, which causes JSON.stringify to throw an error
|
// 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 { INode, INodeParameters, INodeProperties, INodeTypeDescription } from '@/Interfaces';
|
||||||
import type { Workflow } from '../src';
|
import type { Workflow } from '@/Workflow';
|
||||||
|
|
||||||
import { getNodeParameters, getNodeHints, isSingleExecution } from '@/NodeHelpers';
|
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 { v5 as uuidv5, v3 as uuidv3, v4 as uuidv4, v1 as uuidv1 } from 'uuid';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ANONYMIZATION_CHARACTER as CHAR,
|
ANONYMIZATION_CHARACTER as CHAR,
|
||||||
generateNodesGraph,
|
generateNodesGraph,
|
||||||
getDomainBase,
|
getDomainBase,
|
||||||
getDomainPath,
|
getDomainPath,
|
||||||
} from '@/TelemetryHelpers';
|
} from '@/TelemetryHelpers';
|
||||||
import { ApplicationError, STICKY_NODE_TYPE, type IWorkflowBase } from '@/index';
|
|
||||||
import { nodeTypes } from './ExpressionExtensions/Helpers';
|
import { nodeTypes } from './ExpressionExtensions/Helpers';
|
||||||
import { mock } from 'jest-mock-extended';
|
|
||||||
import * as nodeHelpers from '@/NodeHelpers';
|
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', () => {
|
describe('getDomainBase should return protocol plus domain', () => {
|
||||||
test('in valid URLs', () => {
|
test('in valid URLs', () => {
|
||||||
|
@ -872,22 +876,12 @@ function uuidUrls(
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function digit() {
|
function numericId(length = randomInt(1, 10)) {
|
||||||
return Math.floor(Math.random() * 10);
|
return Array.from({ length }, () => randomInt(10)).join('');
|
||||||
}
|
|
||||||
|
|
||||||
function positiveDigit(): number {
|
|
||||||
const d = digit();
|
|
||||||
|
|
||||||
return d === 0 ? positiveDigit() : d;
|
|
||||||
}
|
|
||||||
|
|
||||||
function numericId(length = positiveDigit()) {
|
|
||||||
return Array.from({ length }, digit).join('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function alphanumericId() {
|
function alphanumericId() {
|
||||||
return chooseRandomly([`john${numericId()}`, `title${numericId(1)}`, numericId()]);
|
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', () => {
|
describe('WorkflowActivationError', () => {
|
||||||
it('should default to `error` level', () => {
|
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 { 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', () => {
|
describe('isObjectEmpty', () => {
|
||||||
it('should handle null and undefined', () => {
|
it('should handle null and undefined', () => {
|
||||||
|
@ -237,3 +246,47 @@ describe('fileTypeFromMimeType', () => {
|
||||||
expect(fileTypeFromMimeType('application/pdf')).toEqual('pdf');
|
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": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./*"]
|
||||||
},
|
},
|
||||||
|
"lib": ["es2020", "es2022.error", "dom"],
|
||||||
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
|
"tsBuildInfoFile": "dist/typecheck.tsbuildinfo"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "test/**/*.ts"]
|
"include": ["src/**/*.ts", "test/**/*.ts"]
|
||||||
|
|
Loading…
Reference in a new issue