mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-21 03:42:16 +00:00
refactor(core): Abstract away InstanceSettings and encryptionKey into injectable services (no-changelog) (#7471)
This change ensures that things like `encryptionKey` and `instanceId` are always available directly where they are needed, instead of passing them around throughout the code.
This commit is contained in:
committed by
GitHub
parent
519680c2cf
commit
b6de910cbe
19
packages/core/src/Cipher.ts
Normal file
19
packages/core/src/Cipher.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Service } from 'typedi';
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import { InstanceSettings } from './InstanceSettings';
|
||||
|
||||
@Service()
|
||||
export class Cipher {
|
||||
constructor(private readonly instanceSettings: InstanceSettings) {}
|
||||
|
||||
encrypt(data: string | object) {
|
||||
return AES.encrypt(
|
||||
typeof data === 'string' ? data : JSON.stringify(data),
|
||||
this.instanceSettings.encryptionKey,
|
||||
).toString();
|
||||
}
|
||||
|
||||
decrypt(data: string) {
|
||||
return AES.decrypt(data, this.instanceSettings.encryptionKey).toString(enc.Utf8);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,6 @@
|
||||
export const CUSTOM_EXTENSION_ENV = 'N8N_CUSTOM_EXTENSIONS';
|
||||
export const DOWNLOADED_NODES_SUBDIRECTORY = 'nodes';
|
||||
export const ENCRYPTION_KEY_ENV_OVERWRITE = 'N8N_ENCRYPTION_KEY';
|
||||
export const EXTENSIONS_SUBDIRECTORY = 'custom';
|
||||
export const USER_FOLDER_ENV_OVERWRITE = 'N8N_USER_FOLDER';
|
||||
export const USER_SETTINGS_FILE_NAME = 'config';
|
||||
export const USER_SETTINGS_SUBFOLDER = '.n8n';
|
||||
export const PLACEHOLDER_EMPTY_EXECUTION_ID = '__UNKNOWN__';
|
||||
export const PLACEHOLDER_EMPTY_WORKFLOW_ID = '__EMPTY__';
|
||||
export const TUNNEL_SUBDOMAIN_ENV = 'N8N_TUNNEL_SUBDOMAIN';
|
||||
|
||||
export const RESPONSE_ERROR_MESSAGES = {
|
||||
NO_ENCRYPTION_KEY: 'Encryption key is missing or was not set',
|
||||
};
|
||||
|
||||
export const CUSTOM_NODES_CATEGORY = 'Custom Nodes';
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import type {
|
||||
CredentialInformation,
|
||||
ICredentialDataDecryptedObject,
|
||||
ICredentialsEncrypted,
|
||||
} from 'n8n-workflow';
|
||||
import { ICredentials } from 'n8n-workflow';
|
||||
|
||||
import { AES, enc } from 'crypto-js';
|
||||
import { Container } from 'typedi';
|
||||
import type { ICredentialDataDecryptedObject, ICredentialsEncrypted } from 'n8n-workflow';
|
||||
import { ICredentials, jsonParse } from 'n8n-workflow';
|
||||
import { Cipher } from './Cipher';
|
||||
|
||||
export class Credentials extends ICredentials {
|
||||
private readonly cipher = Container.get(Cipher);
|
||||
|
||||
/**
|
||||
* Returns if the given nodeType has access to data
|
||||
*/
|
||||
@@ -24,30 +22,14 @@ export class Credentials extends ICredentials {
|
||||
/**
|
||||
* Sets new credential object
|
||||
*/
|
||||
setData(data: ICredentialDataDecryptedObject, encryptionKey: string): void {
|
||||
this.data = AES.encrypt(JSON.stringify(data), encryptionKey).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets new credentials for given key
|
||||
*/
|
||||
setDataKey(key: string, data: CredentialInformation, encryptionKey: string): void {
|
||||
let fullData;
|
||||
try {
|
||||
fullData = this.getData(encryptionKey);
|
||||
} catch (e) {
|
||||
fullData = {};
|
||||
}
|
||||
|
||||
fullData[key] = data;
|
||||
|
||||
return this.setData(fullData, encryptionKey);
|
||||
setData(data: ICredentialDataDecryptedObject): void {
|
||||
this.data = this.cipher.encrypt(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decrypted credential object
|
||||
*/
|
||||
getData(encryptionKey: string, nodeType?: string): ICredentialDataDecryptedObject {
|
||||
getData(nodeType?: string): ICredentialDataDecryptedObject {
|
||||
if (nodeType && !this.hasNodeAccess(nodeType)) {
|
||||
throw new Error(
|
||||
`The node of type "${nodeType}" does not have access to credentials "${this.name}" of type "${this.type}".`,
|
||||
@@ -58,11 +40,10 @@ export class Credentials extends ICredentials {
|
||||
throw new Error('No data is set so nothing can be returned.');
|
||||
}
|
||||
|
||||
const decryptedData = AES.decrypt(this.data, encryptionKey);
|
||||
const decryptedData = this.cipher.decrypt(this.data);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||
return JSON.parse(decryptedData.toString(enc.Utf8));
|
||||
return jsonParse(decryptedData);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
'Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.',
|
||||
@@ -70,23 +51,6 @@ export class Credentials extends ICredentials {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decrypted credentials for given key
|
||||
*/
|
||||
getDataKey(key: string, encryptionKey: string, nodeType?: string): CredentialInformation {
|
||||
const fullData = this.getData(encryptionKey, nodeType);
|
||||
|
||||
if (fullData === null) {
|
||||
throw new Error('No data was set.');
|
||||
}
|
||||
|
||||
if (!fullData.hasOwnProperty(key)) {
|
||||
throw new Error(`No data for key "${key}" exists.`);
|
||||
}
|
||||
|
||||
return fullData[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encrypted credentials to be saved
|
||||
*/
|
||||
|
||||
86
packages/core/src/InstanceSettings.ts
Normal file
86
packages/core/src/InstanceSettings.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import path from 'path';
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { createHash, randomBytes } from 'crypto';
|
||||
import { Service } from 'typedi';
|
||||
import { jsonParse } from 'n8n-workflow';
|
||||
|
||||
interface ReadOnlySettings {
|
||||
encryptionKey: string;
|
||||
instanceId: string;
|
||||
}
|
||||
|
||||
interface WritableSettings {
|
||||
tunnelSubdomain?: string;
|
||||
}
|
||||
|
||||
type Settings = ReadOnlySettings & WritableSettings;
|
||||
|
||||
@Service()
|
||||
export class InstanceSettings {
|
||||
readonly userHome = this.getUserHome();
|
||||
|
||||
/** The path to the n8n folder in which all n8n related data gets saved */
|
||||
readonly n8nFolder = path.join(this.userHome, '.n8n');
|
||||
|
||||
/** The path to the folder containing custom nodes and credentials */
|
||||
readonly customExtensionDir = path.join(this.n8nFolder, 'custom');
|
||||
|
||||
/** The path to the folder containing installed nodes (like community nodes) */
|
||||
readonly nodesDownloadDir = path.join(this.n8nFolder, 'nodes');
|
||||
|
||||
private readonly settingsFile = path.join(this.n8nFolder, 'config');
|
||||
|
||||
private settings = this.loadOrCreate();
|
||||
|
||||
get encryptionKey() {
|
||||
return this.settings.encryptionKey;
|
||||
}
|
||||
|
||||
get instanceId() {
|
||||
return this.settings.instanceId;
|
||||
}
|
||||
|
||||
get tunnelSubdomain() {
|
||||
return this.settings.tunnelSubdomain;
|
||||
}
|
||||
|
||||
update(newSettings: WritableSettings) {
|
||||
this.save({ ...this.settings, ...newSettings });
|
||||
}
|
||||
|
||||
/**
|
||||
* The home folder path of the user.
|
||||
* If none can be found it falls back to the current working directory
|
||||
*/
|
||||
private getUserHome() {
|
||||
const homeVarName = process.platform === 'win32' ? 'USERPROFILE' : 'HOME';
|
||||
return process.env.N8N_USER_FOLDER ?? process.env[homeVarName] ?? process.cwd();
|
||||
}
|
||||
|
||||
private loadOrCreate(): Settings {
|
||||
const { settingsFile } = this;
|
||||
if (existsSync(settingsFile)) {
|
||||
const content = readFileSync(settingsFile, 'utf8');
|
||||
return jsonParse(content, {
|
||||
errorMessage: `Error parsing n8n-config file "${settingsFile}". It does not seem to be valid JSON.`,
|
||||
});
|
||||
}
|
||||
|
||||
// If file doesn't exist, create new settings
|
||||
const encryptionKey = process.env.N8N_ENCRYPTION_KEY ?? randomBytes(24).toString('base64');
|
||||
const instanceId = createHash('sha256')
|
||||
.update(encryptionKey.slice(Math.round(encryptionKey.length / 2)))
|
||||
.digest('hex');
|
||||
|
||||
const settings = { encryptionKey, instanceId };
|
||||
mkdirSync(path.dirname(settingsFile));
|
||||
this.save(settings);
|
||||
console.log(`UserSettings were generated and saved to: ${settingsFile}`);
|
||||
return settings;
|
||||
}
|
||||
|
||||
private save(settings: Settings) {
|
||||
this.settings = settings;
|
||||
writeFileSync(this.settingsFile, JSON.stringify(settings, null, '\t'), 'utf-8');
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,6 @@ export interface IResponseError extends Error {
|
||||
statusCode?: number;
|
||||
}
|
||||
|
||||
export interface IUserSettings {
|
||||
encryptionKey?: string;
|
||||
tunnelSubdomain?: string;
|
||||
instanceId?: string;
|
||||
}
|
||||
|
||||
export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||
errorWorkflow?: string;
|
||||
timezone?: string;
|
||||
|
||||
@@ -3,14 +3,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-shadow */
|
||||
|
||||
import type {
|
||||
ClientOAuth2Options,
|
||||
ClientOAuth2RequestObject,
|
||||
@@ -143,9 +140,9 @@ import {
|
||||
setWorkflowExecutionMetadata,
|
||||
} from './WorkflowExecutionMetadata';
|
||||
import { getSecretsProxy } from './Secrets';
|
||||
import { getUserN8nFolderPath, getInstanceId } from './UserSettings';
|
||||
import Container from 'typedi';
|
||||
import type { BinaryData } from './BinaryData/types';
|
||||
import { InstanceSettings } from './InstanceSettings';
|
||||
|
||||
axios.defaults.timeout = 300000;
|
||||
// Prevent axios from adding x-form-www-urlencoded headers by default
|
||||
@@ -2510,7 +2507,7 @@ const getCommonWorkflowFunctions = (
|
||||
|
||||
getRestApiUrl: () => additionalData.restApiUrl,
|
||||
getInstanceBaseUrl: () => additionalData.instanceBaseUrl,
|
||||
getInstanceId: async () => getInstanceId(),
|
||||
getInstanceId: () => Container.get(InstanceSettings).instanceId,
|
||||
getTimezone: () => getTimezone(workflow, additionalData),
|
||||
|
||||
prepareOutputData: async (outputData) => [outputData],
|
||||
@@ -2600,7 +2597,6 @@ const getAllowedPaths = () => {
|
||||
function isFilePathBlocked(filePath: string): boolean {
|
||||
const allowedPaths = getAllowedPaths();
|
||||
const resolvedFilePath = path.resolve(filePath);
|
||||
const userFolder = getUserN8nFolderPath();
|
||||
const blockFileAccessToN8nFiles = process.env[BLOCK_FILE_ACCESS_TO_N8N_FILES] !== 'false';
|
||||
|
||||
//if allowed paths are defined, allow access only to those paths
|
||||
@@ -2616,7 +2612,8 @@ function isFilePathBlocked(filePath: string): boolean {
|
||||
|
||||
//restrict access to .n8n folder and other .env config related paths
|
||||
if (blockFileAccessToN8nFiles) {
|
||||
const restrictedPaths: string[] = [userFolder];
|
||||
const { n8nFolder } = Container.get(InstanceSettings);
|
||||
const restrictedPaths = [n8nFolder];
|
||||
|
||||
if (process.env[CONFIG_FILES]) {
|
||||
restrictedPaths.push(...process.env[CONFIG_FILES].split(','));
|
||||
@@ -2674,7 +2671,7 @@ const getFileSystemHelperFunctions = (node: INode): FileSystemHelperFunctions =>
|
||||
},
|
||||
|
||||
getStoragePath() {
|
||||
return path.join(getUserN8nFolderPath(), `storage/${node.type}`);
|
||||
return path.join(Container.get(InstanceSettings).n8nFolder, `storage/${node.type}`);
|
||||
},
|
||||
|
||||
async writeContentToFile(filePath, content, flag) {
|
||||
|
||||
@@ -1,272 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { createHash, randomBytes } from 'crypto';
|
||||
import { promisify } from 'util';
|
||||
import { deepCopy } from 'n8n-workflow';
|
||||
import {
|
||||
ENCRYPTION_KEY_ENV_OVERWRITE,
|
||||
EXTENSIONS_SUBDIRECTORY,
|
||||
DOWNLOADED_NODES_SUBDIRECTORY,
|
||||
RESPONSE_ERROR_MESSAGES,
|
||||
USER_FOLDER_ENV_OVERWRITE,
|
||||
USER_SETTINGS_FILE_NAME,
|
||||
USER_SETTINGS_SUBFOLDER,
|
||||
} from './Constants';
|
||||
import type { IUserSettings } from './Interfaces';
|
||||
|
||||
const fsAccess = promisify(fs.access);
|
||||
const fsReadFile = promisify(fs.readFile);
|
||||
const fsMkdir = promisify(fs.mkdir);
|
||||
const fsWriteFile = promisify(fs.writeFile);
|
||||
|
||||
let settingsCache: IUserSettings | undefined;
|
||||
|
||||
/**
|
||||
* Creates the user settings if they do not exist yet
|
||||
*
|
||||
*/
|
||||
export async function prepareUserSettings(): Promise<IUserSettings> {
|
||||
const settingsPath = getUserSettingsPath();
|
||||
|
||||
let userSettings = await getUserSettings(settingsPath);
|
||||
if (userSettings !== undefined) {
|
||||
// Settings already exist, check if they contain the encryptionKey
|
||||
if (userSettings.encryptionKey !== undefined) {
|
||||
// Key already exists
|
||||
if (userSettings.instanceId === undefined) {
|
||||
userSettings.instanceId = await generateInstanceId(userSettings.encryptionKey);
|
||||
settingsCache = userSettings;
|
||||
}
|
||||
|
||||
return userSettings;
|
||||
}
|
||||
} else {
|
||||
userSettings = {};
|
||||
}
|
||||
|
||||
if (process.env[ENCRYPTION_KEY_ENV_OVERWRITE] !== undefined) {
|
||||
// Use the encryption key which got set via environment
|
||||
userSettings.encryptionKey = process.env[ENCRYPTION_KEY_ENV_OVERWRITE];
|
||||
} else {
|
||||
// Generate a new encryption key
|
||||
userSettings.encryptionKey = randomBytes(24).toString('base64');
|
||||
}
|
||||
|
||||
userSettings.instanceId = await generateInstanceId(userSettings.encryptionKey);
|
||||
|
||||
console.log(`UserSettings were generated and saved to: ${settingsPath}`);
|
||||
|
||||
return writeUserSettings(userSettings, settingsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encryption key which is used to encrypt
|
||||
* the credentials.
|
||||
*
|
||||
*/
|
||||
|
||||
export async function getEncryptionKey(): Promise<string> {
|
||||
if (process.env[ENCRYPTION_KEY_ENV_OVERWRITE] !== undefined) {
|
||||
return process.env[ENCRYPTION_KEY_ENV_OVERWRITE];
|
||||
}
|
||||
|
||||
const userSettings = await getUserSettings();
|
||||
|
||||
if (userSettings?.encryptionKey === undefined) {
|
||||
throw new Error(RESPONSE_ERROR_MESSAGES.NO_ENCRYPTION_KEY);
|
||||
}
|
||||
|
||||
return userSettings.encryptionKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance ID
|
||||
*
|
||||
*/
|
||||
export async function getInstanceId(): Promise<string> {
|
||||
const userSettings = await getUserSettings();
|
||||
|
||||
if (userSettings === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (userSettings.instanceId === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return userSettings.instanceId;
|
||||
}
|
||||
|
||||
async function generateInstanceId(key?: string) {
|
||||
const hash = key
|
||||
? createHash('sha256')
|
||||
.update(key.slice(Math.round(key.length / 2)))
|
||||
.digest('hex')
|
||||
: undefined;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/Overwrite the given settings in the currently
|
||||
* saved user settings
|
||||
*
|
||||
* @param {IUserSettings} addSettings The settings to add/overwrite
|
||||
* @param {string} [settingsPath] Optional settings file path
|
||||
*/
|
||||
export async function addToUserSettings(
|
||||
addSettings: IUserSettings,
|
||||
settingsPath?: string,
|
||||
): Promise<IUserSettings> {
|
||||
if (settingsPath === undefined) {
|
||||
settingsPath = getUserSettingsPath();
|
||||
}
|
||||
|
||||
let userSettings = await getUserSettings(settingsPath);
|
||||
|
||||
if (userSettings === undefined) {
|
||||
userSettings = {};
|
||||
}
|
||||
|
||||
// Add the settings
|
||||
Object.assign(userSettings, addSettings);
|
||||
|
||||
return writeUserSettings(userSettings, settingsPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a user settings file
|
||||
*
|
||||
* @param {IUserSettings} userSettings The settings to write
|
||||
* @param {string} [settingsPath] Optional settings file path
|
||||
*/
|
||||
export async function writeUserSettings(
|
||||
userSettings: IUserSettings,
|
||||
settingsPath?: string,
|
||||
): Promise<IUserSettings> {
|
||||
if (settingsPath === undefined) {
|
||||
settingsPath = getUserSettingsPath();
|
||||
}
|
||||
|
||||
if (userSettings === undefined) {
|
||||
userSettings = {};
|
||||
}
|
||||
|
||||
// Check if parent folder exists if not create it.
|
||||
try {
|
||||
await fsAccess(path.dirname(settingsPath));
|
||||
} catch (error) {
|
||||
// Parent folder does not exist so create
|
||||
await fsMkdir(path.dirname(settingsPath));
|
||||
}
|
||||
|
||||
const settingsToWrite = { ...userSettings };
|
||||
if (settingsToWrite.instanceId !== undefined) {
|
||||
delete settingsToWrite.instanceId;
|
||||
}
|
||||
|
||||
await fsWriteFile(settingsPath, JSON.stringify(settingsToWrite, null, '\t'));
|
||||
settingsCache = deepCopy(userSettings);
|
||||
|
||||
return userSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content of the user settings
|
||||
*
|
||||
*/
|
||||
export async function getUserSettings(
|
||||
settingsPath?: string,
|
||||
ignoreCache?: boolean,
|
||||
): Promise<IUserSettings | undefined> {
|
||||
if (settingsCache !== undefined && ignoreCache !== true) {
|
||||
return settingsCache;
|
||||
}
|
||||
|
||||
if (settingsPath === undefined) {
|
||||
settingsPath = getUserSettingsPath();
|
||||
}
|
||||
|
||||
try {
|
||||
await fsAccess(settingsPath);
|
||||
} catch (error) {
|
||||
// The file does not exist
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const settingsFile = await fsReadFile(settingsPath, 'utf8');
|
||||
|
||||
try {
|
||||
settingsCache = JSON.parse(settingsFile);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Error parsing n8n-config file "${settingsPath}". It does not seem to be valid JSON.`,
|
||||
);
|
||||
}
|
||||
|
||||
return settingsCache as IUserSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the user settings
|
||||
*
|
||||
*/
|
||||
export function getUserSettingsPath(): string {
|
||||
const n8nFolder = getUserN8nFolderPath();
|
||||
|
||||
return path.join(n8nFolder, USER_SETTINGS_FILE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the n8n folder in which all n8n
|
||||
* related data gets saved
|
||||
*
|
||||
*/
|
||||
export function getUserN8nFolderPath(): string {
|
||||
return path.join(getUserHome(), USER_SETTINGS_SUBFOLDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the n8n user folder with the custom
|
||||
* extensions like nodes and credentials
|
||||
*
|
||||
*/
|
||||
export function getUserN8nFolderCustomExtensionPath(): string {
|
||||
return path.join(getUserN8nFolderPath(), EXTENSIONS_SUBDIRECTORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path to the n8n user folder with the nodes that
|
||||
* have been downloaded
|
||||
*
|
||||
*/
|
||||
export function getUserN8nFolderDownloadedNodesPath(): string {
|
||||
return path.join(getUserN8nFolderPath(), DOWNLOADED_NODES_SUBDIRECTORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the home folder path of the user if
|
||||
* none can be found it falls back to the current
|
||||
* working directory
|
||||
*
|
||||
*/
|
||||
export function getUserHome(): string {
|
||||
if (process.env[USER_FOLDER_ENV_OVERWRITE] !== undefined) {
|
||||
return process.env[USER_FOLDER_ENV_OVERWRITE];
|
||||
} else {
|
||||
let variableName = 'HOME';
|
||||
if (process.platform === 'win32') {
|
||||
variableName = 'USERPROFILE';
|
||||
}
|
||||
|
||||
if (process.env[variableName] === undefined) {
|
||||
// If for some reason the variable does not exist
|
||||
// fall back to current folder
|
||||
return process.cwd();
|
||||
}
|
||||
return process.env[variableName] as string;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
import * as NodeExecuteFunctions from './NodeExecuteFunctions';
|
||||
import * as UserSettings from './UserSettings';
|
||||
|
||||
export * from './ActiveWorkflows';
|
||||
export * from './BinaryData/BinaryData.service';
|
||||
export * from './BinaryData/types';
|
||||
export { Cipher } from './Cipher';
|
||||
export * from './ClassLoader';
|
||||
export * from './Constants';
|
||||
export * from './Credentials';
|
||||
export * from './DirectoryLoader';
|
||||
export * from './Interfaces';
|
||||
export { InstanceSettings } from './InstanceSettings';
|
||||
export * from './LoadMappingOptions';
|
||||
export * from './LoadNodeParameterOptions';
|
||||
export * from './LoadNodeListSearch';
|
||||
export * from './NodeExecuteFunctions';
|
||||
export * from './WorkflowExecute';
|
||||
export { NodeExecuteFunctions, UserSettings };
|
||||
export { NodeExecuteFunctions };
|
||||
export * from './errors';
|
||||
export { ObjectStoreService } from './ObjectStore/ObjectStore.service.ee';
|
||||
export { BinaryData } from './BinaryData/types';
|
||||
|
||||
Reference in New Issue
Block a user