mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 10:02:05 +00:00
feat: Add support for $env in the js task runner (no-changelog) (#11177)
This commit is contained in:
@@ -22,6 +22,7 @@ import type { WorkflowActivationError } from './errors/workflow-activation.error
|
||||
import type { WorkflowOperationError } from './errors/workflow-operation.error';
|
||||
import type { ExecutionStatus } from './ExecutionStatus';
|
||||
import type { Workflow } from './Workflow';
|
||||
import type { EnvProviderState } from './WorkflowDataProxyEnvProvider';
|
||||
import type { WorkflowHooks } from './WorkflowHooks';
|
||||
|
||||
export interface IAdditionalCredentialOptions {
|
||||
@@ -2256,6 +2257,7 @@ export interface IWorkflowExecuteAdditionalData {
|
||||
connectionInputData: INodeExecutionData[],
|
||||
siblingParameters: INodeParameters,
|
||||
mode: WorkflowExecuteMode,
|
||||
envProviderState: EnvProviderState,
|
||||
executeData?: IExecuteData,
|
||||
defaultReturnRunIndex?: number,
|
||||
selfData?: IDataObject,
|
||||
|
||||
@@ -30,6 +30,8 @@ import {
|
||||
import * as NodeHelpers from './NodeHelpers';
|
||||
import { deepCopy } from './utils';
|
||||
import type { Workflow } from './Workflow';
|
||||
import type { EnvProviderState } from './WorkflowDataProxyEnvProvider';
|
||||
import { createEnvProvider, createEnvProviderState } from './WorkflowDataProxyEnvProvider';
|
||||
import { getPinDataIfManualExecution } from './WorkflowDataProxyHelpers';
|
||||
|
||||
export function isResourceLocatorValue(value: unknown): value is INodeParameterResourceLocator {
|
||||
@@ -66,6 +68,7 @@ export class WorkflowDataProxy {
|
||||
private defaultReturnRunIndex = -1,
|
||||
private selfData: IDataObject = {},
|
||||
private contextNodeName: string = activeNodeName,
|
||||
private envProviderState?: EnvProviderState,
|
||||
) {
|
||||
this.runExecutionData = isScriptingNode(this.contextNodeName, workflow)
|
||||
? runExecutionData !== null
|
||||
@@ -487,40 +490,6 @@ export class WorkflowDataProxy {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a proxy to query data from the environment
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private envGetter() {
|
||||
const that = this;
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
has: () => true,
|
||||
get(_, name) {
|
||||
if (name === 'isProxy') return true;
|
||||
|
||||
if (typeof process === 'undefined') {
|
||||
throw new ExpressionError('not accessible via UI, please run node', {
|
||||
runIndex: that.runIndex,
|
||||
itemIndex: that.itemIndex,
|
||||
});
|
||||
}
|
||||
if (process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true') {
|
||||
throw new ExpressionError('access to env vars denied', {
|
||||
causeDetailed:
|
||||
'If you need access please contact the administrator to remove the environment variable ‘N8N_BLOCK_ENV_ACCESS_IN_NODE‘',
|
||||
runIndex: that.runIndex,
|
||||
itemIndex: that.itemIndex,
|
||||
});
|
||||
}
|
||||
return process.env[name.toString()];
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private prevNodeGetter() {
|
||||
const allowedValues = ['name', 'outputIndex', 'runIndex'];
|
||||
const that = this;
|
||||
@@ -1303,7 +1272,11 @@ export class WorkflowDataProxy {
|
||||
|
||||
$binary: {}, // Placeholder
|
||||
$data: {}, // Placeholder
|
||||
$env: this.envGetter(),
|
||||
$env: createEnvProvider(
|
||||
that.runIndex,
|
||||
that.itemIndex,
|
||||
that.envProviderState ?? createEnvProviderState(),
|
||||
),
|
||||
$evaluateExpression: (expression: string, itemIndex?: number) => {
|
||||
itemIndex = itemIndex || that.itemIndex;
|
||||
return that.workflow.expression.getParameterValue(
|
||||
|
||||
75
packages/workflow/src/WorkflowDataProxyEnvProvider.ts
Normal file
75
packages/workflow/src/WorkflowDataProxyEnvProvider.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { ExpressionError } from './errors/expression.error';
|
||||
|
||||
export type EnvProviderState = {
|
||||
isProcessAvailable: boolean;
|
||||
isEnvAccessBlocked: boolean;
|
||||
env: Record<string, string>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Captures a snapshot of the environment variables and configuration
|
||||
* that can be used to initialize an environment provider.
|
||||
*/
|
||||
export function createEnvProviderState(): EnvProviderState {
|
||||
const isProcessAvailable = typeof process !== 'undefined';
|
||||
const isEnvAccessBlocked = isProcessAvailable
|
||||
? process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE === 'true'
|
||||
: false;
|
||||
const env: Record<string, string> =
|
||||
!isProcessAvailable || isEnvAccessBlocked ? {} : (process.env as Record<string, string>);
|
||||
|
||||
return {
|
||||
isProcessAvailable,
|
||||
isEnvAccessBlocked,
|
||||
env,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a proxy that provides access to the environment variables
|
||||
* in the `WorkflowDataProxy`. Use the `createEnvProviderState` to
|
||||
* create the default state object that is needed for the proxy,
|
||||
* unless you need something specific.
|
||||
*
|
||||
* @example
|
||||
* createEnvProvider(
|
||||
* runIndex,
|
||||
* itemIndex,
|
||||
* createEnvProviderState(),
|
||||
* )
|
||||
*/
|
||||
export function createEnvProvider(
|
||||
runIndex: number,
|
||||
itemIndex: number,
|
||||
providerState: EnvProviderState,
|
||||
): Record<string, string> {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
has() {
|
||||
return true;
|
||||
},
|
||||
|
||||
get(_, name) {
|
||||
if (name === 'isProxy') return true;
|
||||
|
||||
if (!providerState.isProcessAvailable) {
|
||||
throw new ExpressionError('not accessible via UI, please run node', {
|
||||
runIndex,
|
||||
itemIndex,
|
||||
});
|
||||
}
|
||||
if (providerState.isEnvAccessBlocked) {
|
||||
throw new ExpressionError('access to env vars denied', {
|
||||
causeDetailed:
|
||||
'If you need access please contact the administrator to remove the environment variable ‘N8N_BLOCK_ENV_ACCESS_IN_NODE‘',
|
||||
runIndex,
|
||||
itemIndex,
|
||||
});
|
||||
}
|
||||
|
||||
return providerState.env[name.toString()];
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ export * from './NodeHelpers';
|
||||
export * from './RoutingNode';
|
||||
export * from './Workflow';
|
||||
export * from './WorkflowDataProxy';
|
||||
export * from './WorkflowDataProxyEnvProvider';
|
||||
export * from './WorkflowHooks';
|
||||
export * from './VersionedNodeType';
|
||||
export * from './TypeValidation';
|
||||
|
||||
87
packages/workflow/test/WorkflowDataProxyEnvProvider.test.ts
Normal file
87
packages/workflow/test/WorkflowDataProxyEnvProvider.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { ExpressionError } from '../src/errors/expression.error';
|
||||
import { createEnvProvider, createEnvProviderState } from '../src/WorkflowDataProxyEnvProvider';
|
||||
|
||||
describe('createEnvProviderState', () => {
|
||||
afterEach(() => {
|
||||
delete process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE;
|
||||
});
|
||||
|
||||
it('should return the state with process available and env access allowed', () => {
|
||||
expect(createEnvProviderState()).toEqual({
|
||||
isProcessAvailable: true,
|
||||
isEnvAccessBlocked: false,
|
||||
env: process.env,
|
||||
});
|
||||
});
|
||||
|
||||
it('should block env access when N8N_BLOCK_ENV_ACCESS_IN_NODE is set to "true"', () => {
|
||||
process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE = 'true';
|
||||
|
||||
expect(createEnvProviderState()).toEqual({
|
||||
isProcessAvailable: true,
|
||||
isEnvAccessBlocked: true,
|
||||
env: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle process not being available', () => {
|
||||
const originalProcess = global.process;
|
||||
try {
|
||||
// @ts-expect-error process is read-only
|
||||
global.process = undefined;
|
||||
|
||||
expect(createEnvProviderState()).toEqual({
|
||||
isProcessAvailable: false,
|
||||
isEnvAccessBlocked: false,
|
||||
env: {},
|
||||
});
|
||||
} finally {
|
||||
global.process = originalProcess;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createEnvProvider', () => {
|
||||
it('should return true when checking for a property using "has"', () => {
|
||||
const proxy = createEnvProvider(0, 0, createEnvProviderState());
|
||||
expect('someProperty' in proxy).toBe(true);
|
||||
});
|
||||
|
||||
it('should return the value from process.env if access is allowed', () => {
|
||||
process.env.TEST_ENV_VAR = 'test_value';
|
||||
const proxy = createEnvProvider(0, 0, createEnvProviderState());
|
||||
expect(proxy.TEST_ENV_VAR).toBe('test_value');
|
||||
});
|
||||
|
||||
it('should throw ExpressionError when process is unavailable', () => {
|
||||
const originalProcess = global.process;
|
||||
// @ts-expect-error process is read-only
|
||||
global.process = undefined;
|
||||
try {
|
||||
const proxy = createEnvProvider(1, 1, createEnvProviderState());
|
||||
|
||||
expect(() => proxy.someEnvVar).toThrowError(
|
||||
new ExpressionError('not accessible via UI, please run node', {
|
||||
runIndex: 1,
|
||||
itemIndex: 1,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
global.process = originalProcess;
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw ExpressionError when env access is blocked', () => {
|
||||
process.env.N8N_BLOCK_ENV_ACCESS_IN_NODE = 'true';
|
||||
const proxy = createEnvProvider(1, 1, createEnvProviderState());
|
||||
|
||||
expect(() => proxy.someEnvVar).toThrowError(
|
||||
new ExpressionError('access to env vars denied', {
|
||||
causeDetailed:
|
||||
'If you need access please contact the administrator to remove the environment variable ‘N8N_BLOCK_ENV_ACCESS_IN_NODE‘',
|
||||
runIndex: 1,
|
||||
itemIndex: 1,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user