mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Block the frontend when trying to access n8n from another host over http (#8906)
This commit is contained in:
parent
6955e8991c
commit
669bd830e9
|
@ -100,6 +100,9 @@ export class FrontendService {
|
||||||
urlBaseEditor: instanceBaseUrl,
|
urlBaseEditor: instanceBaseUrl,
|
||||||
binaryDataMode: config.getEnv('binaryDataManager.mode'),
|
binaryDataMode: config.getEnv('binaryDataManager.mode'),
|
||||||
versionCli: '',
|
versionCli: '',
|
||||||
|
authCookie: {
|
||||||
|
secure: config.getEnv('secure_cookie'),
|
||||||
|
},
|
||||||
releaseChannel: config.getEnv('generic.releaseChannel'),
|
releaseChannel: config.getEnv('generic.releaseChannel'),
|
||||||
oauthCallbackUrls: {
|
oauthCallbackUrls: {
|
||||||
oauth1: `${instanceBaseUrl}/${restEndpoint}/oauth1-credential/callback`,
|
oauth1: `${instanceBaseUrl}/${restEndpoint}/oauth1-credential/callback`,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { INodeTypeData, INodeTypeDescription } from 'n8n-workflow';
|
import type { INodeTypeData, INodeTypeDescription, IN8nUISettings } from 'n8n-workflow';
|
||||||
import { AGENT_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE } from '@/constants';
|
import { AGENT_NODE_TYPE, CHAT_TRIGGER_NODE_TYPE, MANUAL_TRIGGER_NODE_TYPE } from '@/constants';
|
||||||
import nodeTypesJson from '../../../nodes-base/dist/types/nodes.json';
|
import nodeTypesJson from '../../../nodes-base/dist/types/nodes.json';
|
||||||
import aiNodeTypesJson from '../../../@n8n/nodes-langchain/dist/types/nodes.json';
|
import aiNodeTypesJson from '../../../@n8n/nodes-langchain/dist/types/nodes.json';
|
||||||
|
@ -42,3 +42,111 @@ export function mockNodeTypesToArray(nodeTypes: INodeTypeData): INodeTypeDescrip
|
||||||
|
|
||||||
export const defaultMockNodeTypesArray: INodeTypeDescription[] =
|
export const defaultMockNodeTypesArray: INodeTypeDescription[] =
|
||||||
mockNodeTypesToArray(defaultMockNodeTypes);
|
mockNodeTypesToArray(defaultMockNodeTypes);
|
||||||
|
|
||||||
|
export const defaultSettings: IN8nUISettings = {
|
||||||
|
allowedModules: {},
|
||||||
|
communityNodesEnabled: false,
|
||||||
|
defaultLocale: '',
|
||||||
|
endpointForm: '',
|
||||||
|
endpointFormTest: '',
|
||||||
|
endpointFormWaiting: '',
|
||||||
|
endpointWebhook: '',
|
||||||
|
endpointWebhookTest: '',
|
||||||
|
enterprise: {
|
||||||
|
sharing: false,
|
||||||
|
ldap: false,
|
||||||
|
saml: false,
|
||||||
|
logStreaming: false,
|
||||||
|
debugInEditor: false,
|
||||||
|
advancedExecutionFilters: false,
|
||||||
|
variables: true,
|
||||||
|
sourceControl: false,
|
||||||
|
auditLogs: false,
|
||||||
|
showNonProdBanner: false,
|
||||||
|
workflowHistory: false,
|
||||||
|
binaryDataS3: false,
|
||||||
|
externalSecrets: false,
|
||||||
|
workerView: false,
|
||||||
|
advancedPermissions: false,
|
||||||
|
},
|
||||||
|
expressions: {
|
||||||
|
evaluator: 'tournament',
|
||||||
|
},
|
||||||
|
executionMode: 'regular',
|
||||||
|
executionTimeout: 0,
|
||||||
|
hideUsagePage: false,
|
||||||
|
hiringBannerEnabled: false,
|
||||||
|
instanceId: '',
|
||||||
|
isNpmAvailable: false,
|
||||||
|
license: { environment: 'development' },
|
||||||
|
logLevel: 'info',
|
||||||
|
maxExecutionTimeout: 0,
|
||||||
|
oauthCallbackUrls: { oauth1: '', oauth2: '' },
|
||||||
|
onboardingCallPromptEnabled: false,
|
||||||
|
personalizationSurveyEnabled: false,
|
||||||
|
releaseChannel: 'stable',
|
||||||
|
posthog: {
|
||||||
|
apiHost: '',
|
||||||
|
apiKey: '',
|
||||||
|
autocapture: false,
|
||||||
|
debug: false,
|
||||||
|
disableSessionRecording: false,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
publicApi: { enabled: false, latestVersion: 0, path: '', swaggerUi: { enabled: false } },
|
||||||
|
pushBackend: 'websocket',
|
||||||
|
saveDataErrorExecution: 'DEFAULT',
|
||||||
|
saveDataSuccessExecution: 'DEFAULT',
|
||||||
|
saveManualExecutions: false,
|
||||||
|
sso: {
|
||||||
|
ldap: { loginEnabled: false, loginLabel: '' },
|
||||||
|
saml: { loginEnabled: false, loginLabel: '' },
|
||||||
|
},
|
||||||
|
telemetry: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
templates: { enabled: false, host: '' },
|
||||||
|
timezone: '',
|
||||||
|
urlBaseEditor: '',
|
||||||
|
urlBaseWebhook: '',
|
||||||
|
authCookie: {
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
|
userManagement: {
|
||||||
|
showSetupOnFirstLoad: false,
|
||||||
|
smtpSetup: true,
|
||||||
|
authenticationMethod: 'email',
|
||||||
|
quota: 10,
|
||||||
|
},
|
||||||
|
versionCli: '',
|
||||||
|
versionNotifications: {
|
||||||
|
enabled: true,
|
||||||
|
endpoint: '',
|
||||||
|
infoUrl: '',
|
||||||
|
},
|
||||||
|
workflowCallerPolicyDefaultOption: 'any',
|
||||||
|
workflowTagsDisabled: false,
|
||||||
|
variables: {
|
||||||
|
limit: -1,
|
||||||
|
},
|
||||||
|
deployment: {
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
banners: {
|
||||||
|
dismissed: [],
|
||||||
|
},
|
||||||
|
binaryDataMode: 'default',
|
||||||
|
previewMode: false,
|
||||||
|
mfa: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
ai: {
|
||||||
|
enabled: false,
|
||||||
|
provider: '',
|
||||||
|
errorDebugging: false,
|
||||||
|
},
|
||||||
|
workflowHistory: {
|
||||||
|
pruneTime: 0,
|
||||||
|
licensePruneTime: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -1,99 +1,7 @@
|
||||||
import type { Server } from 'miragejs';
|
import type { Server } from 'miragejs';
|
||||||
import { Response } from 'miragejs';
|
import { Response } from 'miragejs';
|
||||||
import type { AppSchema } from '../types';
|
import type { AppSchema } from '../types';
|
||||||
import type { IN8nUISettings } from 'n8n-workflow';
|
import { defaultSettings } from '../../defaults';
|
||||||
|
|
||||||
const defaultSettings: IN8nUISettings = {
|
|
||||||
allowedModules: {},
|
|
||||||
communityNodesEnabled: false,
|
|
||||||
defaultLocale: '',
|
|
||||||
endpointForm: '',
|
|
||||||
endpointFormTest: '',
|
|
||||||
endpointFormWaiting: '',
|
|
||||||
endpointWebhook: '',
|
|
||||||
endpointWebhookTest: '',
|
|
||||||
enterprise: {
|
|
||||||
sharing: false,
|
|
||||||
ldap: false,
|
|
||||||
saml: false,
|
|
||||||
logStreaming: false,
|
|
||||||
debugInEditor: false,
|
|
||||||
advancedExecutionFilters: false,
|
|
||||||
variables: true,
|
|
||||||
sourceControl: false,
|
|
||||||
auditLogs: false,
|
|
||||||
showNonProdBanner: false,
|
|
||||||
workflowHistory: false,
|
|
||||||
debugInEditor: false,
|
|
||||||
binaryDataS3: false,
|
|
||||||
externalSecrets: false,
|
|
||||||
workerView: false,
|
|
||||||
},
|
|
||||||
expressions: {
|
|
||||||
evaluator: 'tournament',
|
|
||||||
},
|
|
||||||
executionMode: 'regular',
|
|
||||||
executionTimeout: 0,
|
|
||||||
hideUsagePage: false,
|
|
||||||
hiringBannerEnabled: false,
|
|
||||||
instanceId: '',
|
|
||||||
isNpmAvailable: false,
|
|
||||||
license: { environment: 'development' },
|
|
||||||
logLevel: 'info',
|
|
||||||
maxExecutionTimeout: 0,
|
|
||||||
oauthCallbackUrls: { oauth1: '', oauth2: '' },
|
|
||||||
onboardingCallPromptEnabled: false,
|
|
||||||
personalizationSurveyEnabled: false,
|
|
||||||
releaseChannel: 'stable',
|
|
||||||
posthog: {
|
|
||||||
apiHost: '',
|
|
||||||
apiKey: '',
|
|
||||||
autocapture: false,
|
|
||||||
debug: false,
|
|
||||||
disableSessionRecording: false,
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
publicApi: { enabled: false, latestVersion: 0, path: '', swaggerUi: { enabled: false } },
|
|
||||||
pushBackend: 'websocket',
|
|
||||||
releaseChannel: 'stable',
|
|
||||||
saveDataErrorExecution: 'DEFAULT',
|
|
||||||
saveDataSuccessExecution: 'DEFAULT',
|
|
||||||
saveManualExecutions: false,
|
|
||||||
sso: {
|
|
||||||
ldap: { loginEnabled: false, loginLabel: '' },
|
|
||||||
saml: { loginEnabled: false, loginLabel: '' },
|
|
||||||
},
|
|
||||||
telemetry: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
templates: { enabled: false, host: '' },
|
|
||||||
timezone: '',
|
|
||||||
urlBaseEditor: '',
|
|
||||||
urlBaseWebhook: '',
|
|
||||||
userManagement: {
|
|
||||||
showSetupOnFirstLoad: false,
|
|
||||||
smtpSetup: true,
|
|
||||||
authenticationMethod: 'email',
|
|
||||||
},
|
|
||||||
versionCli: '',
|
|
||||||
versionNotifications: {
|
|
||||||
enabled: true,
|
|
||||||
endpoint: '',
|
|
||||||
infoUrl: '',
|
|
||||||
},
|
|
||||||
workflowCallerPolicyDefaultOption: 'any',
|
|
||||||
workflowTagsDisabled: false,
|
|
||||||
variables: {
|
|
||||||
limit: -1,
|
|
||||||
},
|
|
||||||
deployment: {
|
|
||||||
type: 'default',
|
|
||||||
},
|
|
||||||
banners: {
|
|
||||||
dismissed: [],
|
|
||||||
},
|
|
||||||
binaryDataMode: 'default',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function routesForSettings(server: Server) {
|
export function routesForSettings(server: Server) {
|
||||||
server.get('/rest/settings', (schema: AppSchema) => {
|
server.get('/rest/settings', (schema: AppSchema) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type { ISettingsState } from '@/Interface';
|
import type { ISettingsState } from '@/Interface';
|
||||||
import { UserManagementAuthenticationMethod } from '@/Interface';
|
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
|
import { defaultSettings } from './defaults';
|
||||||
|
|
||||||
export const retry = async (
|
export const retry = async (
|
||||||
assertion: () => ReturnType<typeof expect>,
|
assertion: () => ReturnType<typeof expect>,
|
||||||
|
@ -25,92 +26,7 @@ export const retry = async (
|
||||||
export const waitAllPromises = async () => await new Promise((resolve) => setTimeout(resolve));
|
export const waitAllPromises = async () => await new Promise((resolve) => setTimeout(resolve));
|
||||||
|
|
||||||
export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = {
|
export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = {
|
||||||
settings: {
|
settings: defaultSettings,
|
||||||
allowedModules: {},
|
|
||||||
communityNodesEnabled: false,
|
|
||||||
defaultLocale: '',
|
|
||||||
endpointForm: '',
|
|
||||||
endpointFormTest: '',
|
|
||||||
endpointFormWaiting: '',
|
|
||||||
endpointWebhook: '',
|
|
||||||
endpointWebhookTest: '',
|
|
||||||
enterprise: {
|
|
||||||
advancedExecutionFilters: false,
|
|
||||||
sharing: false,
|
|
||||||
ldap: false,
|
|
||||||
saml: false,
|
|
||||||
logStreaming: false,
|
|
||||||
variables: false,
|
|
||||||
sourceControl: false,
|
|
||||||
auditLogs: false,
|
|
||||||
},
|
|
||||||
executionMode: 'regular',
|
|
||||||
executionTimeout: 0,
|
|
||||||
hideUsagePage: false,
|
|
||||||
hiringBannerEnabled: false,
|
|
||||||
instanceId: '',
|
|
||||||
isNpmAvailable: false,
|
|
||||||
license: { environment: 'production' },
|
|
||||||
logLevel: 'info',
|
|
||||||
maxExecutionTimeout: 0,
|
|
||||||
oauthCallbackUrls: { oauth1: '', oauth2: '' },
|
|
||||||
onboardingCallPromptEnabled: false,
|
|
||||||
personalizationSurveyEnabled: false,
|
|
||||||
posthog: {
|
|
||||||
apiHost: '',
|
|
||||||
apiKey: '',
|
|
||||||
autocapture: false,
|
|
||||||
debug: false,
|
|
||||||
disableSessionRecording: false,
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
publicApi: { enabled: false, latestVersion: 0, path: '', swaggerUi: { enabled: false } },
|
|
||||||
pushBackend: 'sse',
|
|
||||||
saveDataErrorExecution: 'all',
|
|
||||||
saveDataSuccessExecution: 'all',
|
|
||||||
saveManualExecutions: false,
|
|
||||||
sso: {
|
|
||||||
ldap: { loginEnabled: false, loginLabel: '' },
|
|
||||||
saml: { loginEnabled: false, loginLabel: '' },
|
|
||||||
},
|
|
||||||
telemetry: { enabled: false },
|
|
||||||
templates: { enabled: false, host: '' },
|
|
||||||
timezone: '',
|
|
||||||
urlBaseEditor: '',
|
|
||||||
urlBaseWebhook: '',
|
|
||||||
userManagement: {
|
|
||||||
enabled: false,
|
|
||||||
smtpSetup: false,
|
|
||||||
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
|
||||||
},
|
|
||||||
versionCli: '',
|
|
||||||
versionNotifications: {
|
|
||||||
enabled: false,
|
|
||||||
endpoint: '',
|
|
||||||
infoUrl: '',
|
|
||||||
},
|
|
||||||
workflowCallerPolicyDefaultOption: 'any',
|
|
||||||
workflowTagsDisabled: false,
|
|
||||||
deployment: {
|
|
||||||
type: 'default',
|
|
||||||
},
|
|
||||||
variables: {
|
|
||||||
limit: 100,
|
|
||||||
},
|
|
||||||
expressions: {
|
|
||||||
evaluator: 'tournament',
|
|
||||||
},
|
|
||||||
banners: {
|
|
||||||
dismissed: [],
|
|
||||||
},
|
|
||||||
ai: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
workflowHistory: {
|
|
||||||
pruneTime: -1,
|
|
||||||
licensePruneTime: -1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
promptsData: {
|
promptsData: {
|
||||||
message: '',
|
message: '',
|
||||||
title: '',
|
title: '',
|
||||||
|
@ -118,10 +34,10 @@ export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = {
|
||||||
showValueSurvey: false,
|
showValueSurvey: false,
|
||||||
},
|
},
|
||||||
userManagement: {
|
userManagement: {
|
||||||
enabled: false,
|
|
||||||
showSetupOnFirstLoad: false,
|
showSetupOnFirstLoad: false,
|
||||||
smtpSetup: false,
|
smtpSetup: false,
|
||||||
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
||||||
|
quota: defaultSettings.userManagement.quota,
|
||||||
},
|
},
|
||||||
templatesEndpointHealthy: false,
|
templatesEndpointHealthy: false,
|
||||||
api: {
|
api: {
|
||||||
|
|
|
@ -759,3 +759,19 @@ export const ROLE = {
|
||||||
Admin: 'global:admin',
|
Admin: 'global:admin',
|
||||||
Default: 'default', // default user with no email when setting up instance
|
Default: 'default', // default user with no email when setting up instance
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const INSECURE_CONNECTION_WARNING = `
|
||||||
|
<body style="margin-top: 20px; font-family: 'Open Sans', sans-serif; text-align: center;">
|
||||||
|
<h1 style="font-size: 40px">🚫</h1>
|
||||||
|
<h2>Your n8n server is configured to use a secure cookie, <br/>however you are visiting this via an insecure URL
|
||||||
|
</h2>
|
||||||
|
<br/>
|
||||||
|
<div style="font-size: 18px; max-width: 640px; text-align: left; margin: 10px auto">
|
||||||
|
To fix this, please consider the following options:
|
||||||
|
<ul>
|
||||||
|
<li>Setup TLS/HTTPS (<strong>recommended</strong>), or</li>
|
||||||
|
<li>If you are running this locally, try using <a href="http://localhost:5678">localhost</a> instead</li>
|
||||||
|
<li>If you prefer to disable this security feature (<strong>not recommended</strong>), set the environment variable <code>N8N_SECURE_COOKIE</code> to <code>false</code></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>`;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { useTelemetryStore } from '@/stores/telemetry.store';
|
||||||
import type { IN8nUISettings } from 'n8n-workflow';
|
import type { IN8nUISettings } from 'n8n-workflow';
|
||||||
import { LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants';
|
import { LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants';
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
|
import { defaultSettings } from '../../__tests__/defaults';
|
||||||
|
|
||||||
const DEFAULT_POSTHOG_SETTINGS: IN8nUISettings['posthog'] = {
|
const DEFAULT_POSTHOG_SETTINGS: IN8nUISettings['posthog'] = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
@ -21,6 +22,7 @@ const CURRENT_INSTANCE_ID = '456';
|
||||||
|
|
||||||
function setSettings(overrides?: Partial<IN8nUISettings>) {
|
function setSettings(overrides?: Partial<IN8nUISettings>) {
|
||||||
useSettingsStore().setSettings({
|
useSettingsStore().setSettings({
|
||||||
|
...defaultSettings,
|
||||||
posthog: DEFAULT_POSTHOG_SETTINGS,
|
posthog: DEFAULT_POSTHOG_SETTINGS,
|
||||||
instanceId: CURRENT_INSTANCE_ID,
|
instanceId: CURRENT_INSTANCE_ID,
|
||||||
...overrides,
|
...overrides,
|
||||||
|
|
|
@ -9,7 +9,12 @@ import {
|
||||||
import { getPromptsData, getSettings, submitContactInfo, submitValueSurvey } from '@/api/settings';
|
import { getPromptsData, getSettings, submitContactInfo, submitValueSurvey } from '@/api/settings';
|
||||||
import { testHealthEndpoint } from '@/api/templates';
|
import { testHealthEndpoint } from '@/api/templates';
|
||||||
import type { EnterpriseEditionFeature } from '@/constants';
|
import type { EnterpriseEditionFeature } from '@/constants';
|
||||||
import { CONTACT_PROMPT_MODAL_KEY, STORES, VALUE_SURVEY_MODAL_KEY } from '@/constants';
|
import {
|
||||||
|
CONTACT_PROMPT_MODAL_KEY,
|
||||||
|
STORES,
|
||||||
|
VALUE_SURVEY_MODAL_KEY,
|
||||||
|
INSECURE_CONNECTION_WARNING,
|
||||||
|
} from '@/constants';
|
||||||
import type {
|
import type {
|
||||||
ILdapConfig,
|
ILdapConfig,
|
||||||
IN8nPromptResponse,
|
IN8nPromptResponse,
|
||||||
|
@ -248,6 +253,15 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
||||||
useRootStore().setVersionCli(settings.versionCli);
|
useRootStore().setVersionCli(settings.versionCli);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
settings.authCookie.secure &&
|
||||||
|
location.protocol === 'http:' &&
|
||||||
|
!['localhost', '127.0.0.1'].includes(location.hostname)
|
||||||
|
) {
|
||||||
|
document.write(INSECURE_CONNECTION_WARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const isV1BannerDismissedPermanently = (settings.banners?.dismissed || []).includes('V1');
|
const isV1BannerDismissedPermanently = (settings.banners?.dismissed || []).includes('V1');
|
||||||
if (!isV1BannerDismissedPermanently && useRootStore().versionCli.startsWith('1.')) {
|
if (!isV1BannerDismissedPermanently && useRootStore().versionCli.startsWith('1.')) {
|
||||||
useUIStore().pushBannerToStack('V1');
|
useUIStore().pushBannerToStack('V1');
|
||||||
|
|
|
@ -2477,6 +2477,9 @@ export interface IN8nUISettings {
|
||||||
urlBaseWebhook: string;
|
urlBaseWebhook: string;
|
||||||
urlBaseEditor: string;
|
urlBaseEditor: string;
|
||||||
versionCli: string;
|
versionCli: string;
|
||||||
|
authCookie: {
|
||||||
|
secure: boolean;
|
||||||
|
};
|
||||||
binaryDataMode: string;
|
binaryDataMode: string;
|
||||||
releaseChannel: 'stable' | 'beta' | 'nightly' | 'dev';
|
releaseChannel: 'stable' | 'beta' | 'nightly' | 'dev';
|
||||||
n8nMetadata?: {
|
n8nMetadata?: {
|
||||||
|
|
Loading…
Reference in a new issue