feat(core): Use WebCrypto to generate all random numbers and strings (#9786)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2024-06-19 13:33:57 +02:00
committed by GitHub
parent cfc4db00e3
commit 65c5609ab5
49 changed files with 254 additions and 214 deletions

View File

@@ -1,7 +1,7 @@
import { v4 as uuid } from 'uuid';
import { Container } from 'typedi';
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 type { User } from '@db/entities/User';
@@ -15,11 +15,7 @@ import { OwnershipService } from '@/services/ownership.service';
import { PermissionChecker } from '@/UserManagement/PermissionChecker';
import { mockInstance } from '../shared/mocking';
import {
randomCredentialPayload as randomCred,
randomName,
randomPositiveDigit,
} from '../integration/shared/random';
import { randomCredentialPayload as randomCred, randomName } from '../integration/shared/random';
import { LicenseMocker } from '../integration/shared/license';
import * as testDb from '../integration/shared/testDb';
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 workflowDetails = {
id: randomPositiveDigit().toString(),
id: randomInt(1, 10).toString(),
name: 'test',
active: false,
connections: {},

View File

@@ -1,6 +1,7 @@
import { Container } from 'typedi';
import type { Scope } from '@sentry/node';
import { Credentials } from 'n8n-core';
import { randomString } from 'n8n-workflow';
import type { ListQuery } from '@/requests';
import type { User } from '@db/entities/User';
@@ -16,7 +17,6 @@ import {
randomCredentialPayload as payload,
randomCredentialPayload,
randomName,
randomString,
} from '../shared/random';
import {
saveCredential,

View File

@@ -1,6 +1,7 @@
import { Container } from 'typedi';
import { IsNull } from '@n8n/typeorm';
import validator from 'validator';
import { randomString } from 'n8n-workflow';
import config from '@/config';
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 { SUCCESS_RESPONSE_BODY } from './shared/constants';
import {
randomApiKey,
randomEmail,
randomName,
randomString,
randomValidPassword,
} from './shared/random';
import { randomApiKey, randomEmail, randomName, randomValidPassword } from './shared/random';
import * as testDb from './shared/testDb';
import * as utils from './shared/utils/';
import { addApiKey, createOwner, createUser, createUserShell } from './shared/db/users';

View File

@@ -1,15 +1,15 @@
import Container from 'typedi';
import { randomInt, randomString } from 'n8n-workflow';
import { AuthService } from '@/auth/auth.service';
import config from '@/config';
import type { User } from '@db/entities/User';
import { AuthUserRepository } from '@db/repositories/authUser.repository';
import { randomPassword } from '@/Ldap/helpers';
import { TOTPService } from '@/Mfa/totp.service';
import * as testDb from '../shared/testDb';
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';
jest.mock('@/telemetry');
@@ -150,18 +150,6 @@ describe('Disable MFA setup', () => {
});
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 () => {
await createUserWithMfaEnabled();
@@ -185,7 +173,7 @@ describe('Change password with MFA enabled', () => {
.send({
password: newPassword,
token: resetPasswordToken,
mfaToken: randomDigit(),
mfaToken: randomInt(10),
})
.expect(404);
});
@@ -226,7 +214,7 @@ describe('Change password with MFA enabled', () => {
describe('Login', () => {
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 });

View File

@@ -1,7 +1,10 @@
import { Container } from 'typedi';
import validator from 'validator';
import config from '@/config';
import type { User } from '@db/entities/User';
import { UserRepository } from '@db/repositories/user.repository';
import {
randomEmail,
randomInvalidPassword,
@@ -11,8 +14,6 @@ import {
import * as testDb from './shared/testDb';
import * as utils from './shared/utils/';
import { createUserShell } from './shared/db/users';
import { UserRepository } from '@db/repositories/user.repository';
import Container from 'typedi';
const testServer = utils.setupTestServer({ endpointGroups: ['owner'] });

View File

@@ -2,6 +2,7 @@ import { v4 as uuid } from 'uuid';
import { compare } from 'bcryptjs';
import { Container } from 'typedi';
import { mock } from 'jest-mock-extended';
import { randomString } from 'n8n-workflow';
import { AuthService } from '@/auth/auth.service';
import { License } from '@/License';
@@ -12,6 +13,7 @@ import { ExternalHooks } from '@/ExternalHooks';
import { JwtService } from '@/services/jwt.service';
import { UserManagementMailer } from '@/UserManagement/email';
import { UserRepository } from '@db/repositories/user.repository';
import { PasswordUtility } from '@/services/password.utility';
import { mockInstance } from '../shared/mocking';
import { getAuthToken, setupTestServer } from './shared/utils/';
@@ -19,12 +21,10 @@ import {
randomEmail,
randomInvalidPassword,
randomName,
randomString,
randomValidPassword,
} from './shared/random';
import * as testDb from './shared/testDb';
import { createUser } from './shared/db/users';
import { PasswordUtility } from '@/services/password.utility';
config.set('userManagement.jwtSecret', randomString(5, 10));

View File

@@ -1,10 +1,11 @@
import { Container } from 'typedi';
import { randomString } from 'n8n-workflow';
import type { User } from '@db/entities/User';
import { CredentialsRepository } from '@db/repositories/credentials.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 type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
import * as testDb from '../shared/testDb';

View File

@@ -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 type { CredentialPayload } from './types';
import { v4 as uuid } from 'uuid';
/**
* 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 const randomApiKey = () => `n8n_api_${randomString(40)}`;
export function randomApiKey() {
return `n8n_api_${randomBytes(20).toString('hex')}`;
}
export const chooseRandomly = <T>(array: T[]) => array[randomInt(array.length)];
export const chooseRandomly = <T>(array: T[]) => array[Math.floor(Math.random() * array.length)];
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(''));
const randomUppercaseLetter = () => chooseRandomly(UPPERCASE_LETTERS.split(''));
export const randomValidPassword = () =>
randomString(MIN_PASSWORD_CHAR_LENGTH, MAX_PASSWORD_CHAR_LENGTH - 2) +
randomUppercaseLetter() +
randomDigit();
randomInt(10);
export const randomInvalidPassword = () =>
chooseRandomly([
@@ -54,7 +34,7 @@ const POPULAR_TOP_LEVEL_DOMAINS = ['com', 'org', 'net', 'io', 'edu'];
const randomTopLevelDomain = () => chooseRandomly(POPULAR_TOP_LEVEL_DOMAINS);
export const randomName = () => randomString(4, 8);
export const randomName = () => randomString(4, 8).toLowerCase();
export const randomCredentialPayload = (): CredentialPayload => ({
name: randomName(),

View File

@@ -2,13 +2,12 @@ import type { DataSourceOptions, Repository } from '@n8n/typeorm';
import { DataSource as Connection } from '@n8n/typeorm';
import { Container } from 'typedi';
import type { Class } from 'n8n-core';
import { randomString } from 'n8n-workflow';
import config from '@/config';
import * as Db from '@/Db';
import { getOptionOverrides } from '@db/config';
import { randomString } from './random';
export const testDbPrefix = 'n8n_test_';
/**
@@ -16,7 +15,7 @@ export const testDbPrefix = 'n8n_test_';
*/
export async function init() {
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') {
const bootstrapPostgres = await new Connection(

View File

@@ -1,6 +1,8 @@
import { type Response } from 'express';
import { mock } from 'jest-mock-extended';
import { randomString } from 'n8n-workflow';
import type { IHttpRequestMethods } from 'n8n-workflow';
import type { IWebhookManager, WebhookCORSRequest, WebhookRequest } from '@/Interfaces';
import { webhookRequestHandler } from '@/WebhookHelpers';
@@ -82,7 +84,7 @@ describe('WebhookHelpers', () => {
});
it('should handle wildcard origin', async () => {
const randomOrigin = (Math.random() * 10e6).toString(16);
const randomOrigin = randomString(10);
const req = mock<WebhookRequest | WebhookCORSRequest>({
method: 'OPTIONS',
headers: {

View File

@@ -1,21 +1,21 @@
import { randomInt } from 'n8n-workflow';
import { User } from '@db/entities/User';
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
import { Project } from '@db/entities/Project';
import {
randomCredentialPayload,
randomEmail,
randomInteger,
randomName,
uniqueId,
} from '../../integration/shared/random';
import { Project } from '@/databases/entities/Project';
export const mockCredential = (): CredentialsEntity =>
Object.assign(new CredentialsEntity(), randomCredentialPayload());
export const mockUser = (): User =>
Object.assign(new User(), {
id: randomInteger(),
id: randomInt(1000),
email: randomEmail(),
firstName: randomName(),
lastName: randomName(),