1
1
mirror of https://github.com/n8n-io/n8n.git synced 2024-09-11 13:15:28 +03:00

feat(editor): Block the frontend when trying to access n8n from another host over http (#8906)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2024-03-18 18:34:41 +01:00 committed by GitHub
parent 6955e8991c
commit 669bd830e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 152 additions and 182 deletions

View File

@ -100,6 +100,9 @@ export class FrontendService {
urlBaseEditor: instanceBaseUrl,
binaryDataMode: config.getEnv('binaryDataManager.mode'),
versionCli: '',
authCookie: {
secure: config.getEnv('secure_cookie'),
},
releaseChannel: config.getEnv('generic.releaseChannel'),
oauthCallbackUrls: {
oauth1: `${instanceBaseUrl}/${restEndpoint}/oauth1-credential/callback`,

View File

@ -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 nodeTypesJson from '../../../nodes-base/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[] =
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,
},
};

View File

@ -1,99 +1,7 @@
import type { Server } from 'miragejs';
import { Response } from 'miragejs';
import type { AppSchema } from '../types';
import type { IN8nUISettings } from 'n8n-workflow';
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',
};
import { defaultSettings } from '../../defaults';
export function routesForSettings(server: Server) {
server.get('/rest/settings', (schema: AppSchema) => {

View File

@ -1,5 +1,6 @@
import type { ISettingsState } from '@/Interface';
import { UserManagementAuthenticationMethod } from '@/Interface';
import { defaultSettings } from './defaults';
export const retry = async (
assertion: () => ReturnType<typeof expect>,
@ -25,92 +26,7 @@ export const retry = async (
export const waitAllPromises = async () => await new Promise((resolve) => setTimeout(resolve));
export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = {
settings: {
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,
},
},
settings: defaultSettings,
promptsData: {
message: '',
title: '',
@ -118,10 +34,10 @@ export const SETTINGS_STORE_DEFAULT_STATE: ISettingsState = {
showValueSurvey: false,
},
userManagement: {
enabled: false,
showSetupOnFirstLoad: false,
smtpSetup: false,
authenticationMethod: UserManagementAuthenticationMethod.Email,
quota: defaultSettings.userManagement.quota,
},
templatesEndpointHealthy: false,
api: {

View File

@ -759,3 +759,19 @@ export const ROLE = {
Admin: 'global:admin',
Default: 'default', // default user with no email when setting up instance
} 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">&#x1F6AB;</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>`;

View File

@ -7,6 +7,7 @@ import { useTelemetryStore } from '@/stores/telemetry.store';
import type { IN8nUISettings } from 'n8n-workflow';
import { LOCAL_STORAGE_EXPERIMENT_OVERRIDES } from '@/constants';
import { nextTick } from 'vue';
import { defaultSettings } from '../../__tests__/defaults';
const DEFAULT_POSTHOG_SETTINGS: IN8nUISettings['posthog'] = {
enabled: true,
@ -21,6 +22,7 @@ const CURRENT_INSTANCE_ID = '456';
function setSettings(overrides?: Partial<IN8nUISettings>) {
useSettingsStore().setSettings({
...defaultSettings,
posthog: DEFAULT_POSTHOG_SETTINGS,
instanceId: CURRENT_INSTANCE_ID,
...overrides,

View File

@ -9,7 +9,12 @@ import {
import { getPromptsData, getSettings, submitContactInfo, submitValueSurvey } from '@/api/settings';
import { testHealthEndpoint } from '@/api/templates';
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 {
ILdapConfig,
IN8nPromptResponse,
@ -248,6 +253,15 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
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');
if (!isV1BannerDismissedPermanently && useRootStore().versionCli.startsWith('1.')) {
useUIStore().pushBannerToStack('V1');

View File

@ -2477,6 +2477,9 @@ export interface IN8nUISettings {
urlBaseWebhook: string;
urlBaseEditor: string;
versionCli: string;
authCookie: {
secure: boolean;
};
binaryDataMode: string;
releaseChannel: 'stable' | 'beta' | 'nightly' | 'dev';
n8nMetadata?: {