mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -08:00
refactor(core)!: Remove basic-auth, external-jwt-auth, and no-auth options (#6362)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
parent
a45a2c8c41
commit
8c008f5d22
|
@ -1,18 +1,26 @@
|
||||||
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
import { WorkflowsPage as WorkflowsPageClass } from '../pages/workflows';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
|
|
||||||
const WorkflowsPage = new WorkflowsPageClass();
|
const WorkflowsPage = new WorkflowsPageClass();
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
const multipleWorkflowsCount = 5;
|
const multipleWorkflowsCount = 5;
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Workflows', () => {
|
describe('Workflows', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
cy.visit(WorkflowsPage.url);
|
cy.visit(WorkflowsPage.url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { CODE_NODE_NAME, SET_NODE_NAME } from './../constants';
|
import { CODE_NODE_NAME, DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD, SET_NODE_NAME } from './../constants';
|
||||||
import { SCHEDULE_TRIGGER_NODE_NAME } from '../constants';
|
import { SCHEDULE_TRIGGER_NODE_NAME } from '../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
|
||||||
// Suite-specific constants
|
// Suite-specific constants
|
||||||
const CODE_NODE_NEW_NAME = 'Something else';
|
const CODE_NODE_NEW_NAME = 'Something else';
|
||||||
|
@ -9,12 +10,18 @@ const CODE_NODE_NEW_NAME = 'Something else';
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Undo/Redo', () => {
|
describe('Undo/Redo', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
describe('Inline expression editor', () => {
|
describe('Inline expression editor', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||||
|
|
|
@ -6,16 +6,25 @@ import {
|
||||||
SET_NODE_NAME,
|
SET_NODE_NAME,
|
||||||
IF_NODE_NAME,
|
IF_NODE_NAME,
|
||||||
HTTP_REQUEST_NODE_NAME,
|
HTTP_REQUEST_NODE_NAME,
|
||||||
|
DEFAULT_USER_EMAIL,
|
||||||
|
DEFAULT_USER_PASSWORD,
|
||||||
} from './../constants';
|
} from './../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
describe('Canvas Actions', () => {
|
describe('Canvas Actions', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,11 @@ import {
|
||||||
IF_NODE_NAME,
|
IF_NODE_NAME,
|
||||||
MERGE_NODE_NAME,
|
MERGE_NODE_NAME,
|
||||||
HTTP_REQUEST_NODE_NAME,
|
HTTP_REQUEST_NODE_NAME,
|
||||||
|
DEFAULT_USER_EMAIL,
|
||||||
|
DEFAULT_USER_PASSWORD,
|
||||||
} from './../constants';
|
} from './../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
|
@ -20,12 +23,18 @@ const ZOOM_OUT_X1_FACTOR = 0.8;
|
||||||
const ZOOM_OUT_X2_FACTOR = 0.64;
|
const ZOOM_OUT_X2_FACTOR = 0.64;
|
||||||
const RENAME_NODE_NAME = 'Something else';
|
const RENAME_NODE_NAME = 'Something else';
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Canvas Node Manipulation and Navigation', () => {
|
describe('Canvas Node Manipulation and Navigation', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
import {
|
import {
|
||||||
|
DEFAULT_USER_EMAIL,
|
||||||
|
DEFAULT_USER_PASSWORD,
|
||||||
HTTP_REQUEST_NODE_NAME,
|
HTTP_REQUEST_NODE_NAME,
|
||||||
MANUAL_TRIGGER_NODE_NAME,
|
MANUAL_TRIGGER_NODE_NAME,
|
||||||
PIPEDRIVE_NODE_NAME,
|
PIPEDRIVE_NODE_NAME,
|
||||||
|
@ -9,12 +12,18 @@ import { WorkflowPage, NDV } from '../pages';
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Data pinning', () => {
|
describe('Data pinning', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
|
|
||||||
const wf = new WorkflowPage();
|
const wf = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Data transformation expressions', () => {
|
describe('Data transformation expressions', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
wf.actions.visit();
|
wf.actions.visit();
|
||||||
|
|
||||||
cy.window().then(
|
cy.window().then(
|
||||||
|
|
|
@ -2,18 +2,27 @@ import {
|
||||||
MANUAL_TRIGGER_NODE_NAME,
|
MANUAL_TRIGGER_NODE_NAME,
|
||||||
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
|
||||||
SCHEDULE_TRIGGER_NODE_NAME,
|
SCHEDULE_TRIGGER_NODE_NAME,
|
||||||
|
DEFAULT_USER_EMAIL,
|
||||||
|
DEFAULT_USER_PASSWORD,
|
||||||
} from './../constants';
|
} from './../constants';
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Data mapping', () => {
|
describe('Data mapping', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
|
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage, WorkflowsPage, NDV } from '../pages';
|
import { WorkflowPage, WorkflowsPage, NDV } from '../pages';
|
||||||
import { BACKEND_BASE_URL } from '../constants';
|
import { BACKEND_BASE_URL } from '../constants';
|
||||||
|
|
||||||
|
@ -5,12 +7,18 @@ const workflowsPage = new WorkflowsPage();
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Schedule Trigger node', async () => {
|
describe('Schedule Trigger node', async () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,13 @@ import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { cowBase64 } from '../support/binaryTestFiles';
|
import { cowBase64 } from '../support/binaryTestFiles';
|
||||||
import { BACKEND_BASE_URL } from '../constants';
|
import { BACKEND_BASE_URL } from '../constants';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
@ -93,10 +100,11 @@ const simpleWebhookCall = (options: SimpleWebhookCallOptions) => {
|
||||||
|
|
||||||
describe('Webhook Trigger node', async () => {
|
describe('Webhook Trigger node', async () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
|
|
||||||
cy.window().then((win) => {
|
cy.window().then((win) => {
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage } from '../pages';
|
import { WorkflowPage } from '../pages';
|
||||||
|
|
||||||
const wf = new WorkflowPage();
|
const wf = new WorkflowPage();
|
||||||
|
|
||||||
const TEST_TAGS = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5'];
|
const TEST_TAGS = ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4', 'Tag 5'];
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Workflow tags', () => {
|
describe('Workflow tags', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
wf.actions.visit();
|
wf.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { NDV, WorkflowPage as WorkflowPageClass, WorkflowsPage } from '../pages';
|
import { NDV, WorkflowPage as WorkflowPageClass, WorkflowsPage } from '../pages';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
|
|
||||||
const workflowsPage = new WorkflowsPage();
|
const workflowsPage = new WorkflowsPage();
|
||||||
const workflowPage = new WorkflowPageClass();
|
const workflowPage = new WorkflowPageClass();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Execution', () => {
|
describe('Execution', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,11 @@ const NEW_CREDENTIAL_NAME = 'Something else';
|
||||||
|
|
||||||
describe('Credentials', () => {
|
describe('Credentials', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
cy.visit(credentialsPage.url);
|
cy.visit(credentialsPage.url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage } from '../pages';
|
import { WorkflowPage } from '../pages';
|
||||||
import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab';
|
import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const executionsTab = new WorkflowExecutionsTab();
|
const executionsTab = new WorkflowExecutionsTab();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
// Test suite for executions tab
|
// Test suite for executions tab
|
||||||
describe('Current Workflow Executions', () => {
|
describe('Current Workflow Executions', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', `My test workflow`);
|
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', `My test workflow`);
|
||||||
createMockExecutions();
|
createMockExecutions();
|
||||||
|
|
|
@ -4,17 +4,24 @@ import { CredentialsModal, WorkflowPage } from '../pages';
|
||||||
import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json';
|
import CustomNodeWithN8nCredentialFixture from '../fixtures/Custom_node_n8n_credential.json';
|
||||||
import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json';
|
import CustomNodeWithCustomCredentialFixture from '../fixtures/Custom_node_custom_credential.json';
|
||||||
import CustomCredential from '../fixtures/Custom_credential.json';
|
import CustomCredential from '../fixtures/Custom_credential.json';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
|
|
||||||
const credentialsModal = new CredentialsModal();
|
const credentialsModal = new CredentialsModal();
|
||||||
const nodeCreatorFeature = new NodeCreator();
|
const nodeCreatorFeature = new NodeCreator();
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
// We separate-out the custom nodes because they require injecting nodes and credentials
|
// We separate-out the custom nodes because they require injecting nodes and credentials
|
||||||
// so the /nodes and /credentials endpoints are intercepted and non-cached.
|
// so the /nodes and /credentials endpoints are intercepted and non-cached.
|
||||||
// We want to keep the other tests as fast as possible so we don't want to break the cache in those.
|
// We want to keep the other tests as fast as possible so we don't want to break the cache in those.
|
||||||
describe('Community Nodes', () => {
|
describe('Community Nodes', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
})
|
})
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('/types/nodes.json', { middleware: true }, (req) => {
|
cy.intercept('/types/nodes.json', { middleware: true }, (req) => {
|
||||||
|
@ -36,6 +43,7 @@ describe('Community Nodes', () => {
|
||||||
credentials.push(CustomCredential);
|
credentials.push(CustomCredential);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('NDV', () => {
|
describe('NDV', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
workflowPage.actions.renameWorkflow(uuid());
|
workflowPage.actions.renameWorkflow(uuid());
|
||||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPageClass();
|
const workflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
function checkStickiesStyle( top: number, left: number, height: number, width: number, zIndex?: number) {
|
function checkStickiesStyle( top: number, left: number, height: number, width: number, zIndex?: number) {
|
||||||
workflowPage.getters.stickies().should(($el) => {
|
workflowPage.getters.stickies().should(($el) => {
|
||||||
expect($el).to.have.css('top', `${top}px`);
|
expect($el).to.have.css('top', `${top}px`);
|
||||||
|
@ -16,10 +23,11 @@ function checkStickiesStyle( top: number, left: number, height: number, width: n
|
||||||
|
|
||||||
describe('Canvas Actions', () => {
|
describe('Canvas Actions', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
|
|
||||||
cy.window().then(
|
cy.window().then(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
import { WorkflowPage, NDV, CredentialsModal } from '../pages';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
|
@ -7,12 +9,18 @@ const credentialsModal = new CredentialsModal();
|
||||||
const NO_CREDENTIALS_MESSAGE = 'Please add your credential';
|
const NO_CREDENTIALS_MESSAGE = 'Please add your credential';
|
||||||
const INVALID_CREDENTIALS_MESSAGE = 'Please check your credential';
|
const INVALID_CREDENTIALS_MESSAGE = 'Please check your credential';
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Resource Locator', () => {
|
describe('Resource Locator', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
import { randFirstName, randLastName } from '@ngneat/falso';
|
|
||||||
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
|
||||||
import {
|
|
||||||
SettingsUsersPage,
|
|
||||||
SignupPage,
|
|
||||||
WorkflowsPage,
|
|
||||||
WorkflowPage,
|
|
||||||
CredentialsPage,
|
|
||||||
CredentialsModal,
|
|
||||||
MessageBox,
|
|
||||||
} from '../pages';
|
|
||||||
import { SettingsUsagePage } from '../pages/settings-usage';
|
|
||||||
|
|
||||||
import { MainSidebar, SettingsSidebar } from '../pages/sidebar';
|
|
||||||
|
|
||||||
const mainSidebar = new MainSidebar();
|
|
||||||
const settingsSidebar = new SettingsSidebar();
|
|
||||||
|
|
||||||
const workflowsPage = new WorkflowsPage();
|
|
||||||
const signupPage = new SignupPage();
|
|
||||||
const workflowPage = new WorkflowPage();
|
|
||||||
|
|
||||||
const credentialsPage = new CredentialsPage();
|
|
||||||
const credentialsModal = new CredentialsModal();
|
|
||||||
|
|
||||||
const settingsUsersPage = new SettingsUsersPage();
|
|
||||||
const settingsUsagePage = new SettingsUsagePage();
|
|
||||||
|
|
||||||
const messageBox = new MessageBox();
|
|
||||||
|
|
||||||
const email = DEFAULT_USER_EMAIL;
|
|
||||||
const password = DEFAULT_USER_PASSWORD;
|
|
||||||
const firstName = randFirstName();
|
|
||||||
const lastName = randLastName();
|
|
||||||
|
|
||||||
describe('Default owner', () => {
|
|
||||||
it('should be able to create workflows', () => {
|
|
||||||
cy.skipSetup();
|
|
||||||
cy.createFixtureWorkflow('Test_workflow_1.json', `Test workflow`);
|
|
||||||
|
|
||||||
// reload page, ensure owner still has access
|
|
||||||
cy.reload();
|
|
||||||
cy.waitForLoad();
|
|
||||||
workflowPage.getters.workflowNameInput().should('contain.value', 'Test workflow');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to add new credentials', () => {
|
|
||||||
cy.visit(credentialsPage.url);
|
|
||||||
|
|
||||||
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
|
||||||
|
|
||||||
credentialsModal.getters.newCredentialModal().should('be.visible');
|
|
||||||
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
|
|
||||||
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
|
||||||
|
|
||||||
credentialsModal.getters.newCredentialTypeButton().click();
|
|
||||||
|
|
||||||
credentialsModal.getters.connectionParameter('API Key').type('1234567890');
|
|
||||||
|
|
||||||
credentialsModal.actions.setName('My awesome Notion account');
|
|
||||||
credentialsModal.actions.save();
|
|
||||||
|
|
||||||
credentialsModal.actions.close();
|
|
||||||
|
|
||||||
credentialsModal.getters.newCredentialModal().should('not.exist');
|
|
||||||
credentialsModal.getters.editCredentialModal().should('not.exist');
|
|
||||||
|
|
||||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to setup UM from settings', () => {
|
|
||||||
cy.visit('/');
|
|
||||||
mainSidebar.getters.settings().should('be.visible');
|
|
||||||
mainSidebar.actions.goToSettings();
|
|
||||||
cy.url().should('include', settingsUsagePage.url);
|
|
||||||
|
|
||||||
settingsSidebar.actions.goToUsers();
|
|
||||||
cy.url().should('include', settingsUsersPage.url);
|
|
||||||
|
|
||||||
settingsUsersPage.actions.goToOwnerSetup();
|
|
||||||
|
|
||||||
cy.url().should('include', signupPage.url);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to setup instance and migrate workflows and credentials', () => {
|
|
||||||
cy.setup({ email, firstName, lastName, password }, true);
|
|
||||||
|
|
||||||
messageBox.getters.content().should('contain.text', '1 existing workflow and 1 credential');
|
|
||||||
|
|
||||||
messageBox.actions.confirm();
|
|
||||||
cy.wait('@setupRequest');
|
|
||||||
cy.url().should('include', settingsUsersPage.url);
|
|
||||||
settingsSidebar.actions.back();
|
|
||||||
|
|
||||||
cy.url().should('include', workflowsPage.url);
|
|
||||||
|
|
||||||
workflowsPage.getters.workflowCards().should('have.length', 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can click back to main menu and have migrated credential after setup', () => {
|
|
||||||
cy.signin({ email, password });
|
|
||||||
cy.visit(workflowsPage.url);
|
|
||||||
|
|
||||||
mainSidebar.actions.goToCredentials();
|
|
||||||
|
|
||||||
cy.url().should('include', credentialsPage.url);
|
|
||||||
|
|
||||||
credentialsPage.getters.credentialCards().should('have.length', 1);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,17 +1,25 @@
|
||||||
import { NodeCreator } from '../pages/features/node-creator';
|
import { NodeCreator } from '../pages/features/node-creator';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
|
|
||||||
const nodeCreatorFeature = new NodeCreator();
|
const nodeCreatorFeature = new NodeCreator();
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
const NDVModal = new NDV();
|
const NDVModal = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Node Creator', () => {
|
describe('Node Creator', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('NDV', () => {
|
describe('NDV', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
workflowPage.actions.visit();
|
workflowPage.actions.visit();
|
||||||
workflowPage.actions.renameWorkflow(uuid());
|
workflowPage.actions.renameWorkflow(uuid());
|
||||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||||
|
|
|
@ -1,12 +1,24 @@
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
import { NDV } from '../pages/ndv';
|
import { NDV } from '../pages/ndv';
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Code node', () => {
|
describe('Code node', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should execute the placeholder in all-items mode successfully', () => {
|
it('should execute the placeholder in all-items mode successfully', () => {
|
||||||
|
@ -20,7 +32,6 @@ describe('Code node', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should execute the placeholder in each-item mode successfully', () => {
|
it('should execute the placeholder in each-item mode successfully', () => {
|
||||||
WorkflowPage.actions.visit();
|
|
||||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||||
WorkflowPage.actions.addNodeToCanvas('Code');
|
WorkflowPage.actions.addNodeToCanvas('Code');
|
||||||
WorkflowPage.actions.openNode('Code');
|
WorkflowPage.actions.openNode('Code');
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
import {
|
import {
|
||||||
CODE_NODE_NAME,
|
CODE_NODE_NAME,
|
||||||
|
DEFAULT_USER_EMAIL,
|
||||||
|
DEFAULT_USER_PASSWORD,
|
||||||
MANUAL_TRIGGER_NODE_NAME,
|
MANUAL_TRIGGER_NODE_NAME,
|
||||||
META_KEY,
|
META_KEY,
|
||||||
SCHEDULE_TRIGGER_NODE_NAME,
|
SCHEDULE_TRIGGER_NODE_NAME,
|
||||||
|
@ -11,14 +14,20 @@ const IMPORT_WORKFLOW_URL = 'https://gist.githubusercontent.com/OlegIvaniv/010bd
|
||||||
const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow';
|
const DUPLICATE_WORKFLOW_NAME = 'Duplicated workflow';
|
||||||
const DUPLICATE_WORKFLOW_TAG = 'Duplicate';
|
const DUPLICATE_WORKFLOW_TAG = 'Duplicate';
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
describe('Workflow Actions', () => {
|
describe('Workflow Actions', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage, NDV } from '../pages';
|
import { WorkflowPage, NDV } from '../pages';
|
||||||
|
|
||||||
const workflowPage = new WorkflowPage();
|
const workflowPage = new WorkflowPage();
|
||||||
const ndv = new NDV();
|
const ndv = new NDV();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('HTTP Request node', () => {
|
describe('HTTP Request node', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should make a request with a URL and receive a response', () => {
|
it('should make a request with a URL and receive a response', () => {
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
|
import { randFirstName, randLastName } from '@ngneat/falso';
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from '../constants';
|
||||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||||
|
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
|
const email = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Expression editor modal', () => {
|
describe('Expression editor modal', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
cy.skipSetup();
|
cy.setup({ email, firstName, lastName, password });
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
cy.signin({ email, password });
|
||||||
WorkflowPage.actions.visit();
|
WorkflowPage.actions.visit();
|
||||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||||
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
WorkflowPage.actions.addNodeToCanvas('Hacker News');
|
||||||
|
|
|
@ -173,34 +173,6 @@ Cypress.Commands.add('inviteUsers', ({ instanceOwner, users }) => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('skipSetup', () => {
|
|
||||||
const signupPage = new SignupPage();
|
|
||||||
const workflowPage = new WorkflowPage();
|
|
||||||
const Confirmation = new MessageBox();
|
|
||||||
|
|
||||||
cy.intercept('GET', signupPage.url).as('setupPage');
|
|
||||||
cy.visit(signupPage.url);
|
|
||||||
cy.wait('@setupPage');
|
|
||||||
|
|
||||||
signupPage.getters.form().within(() => {
|
|
||||||
cy.url().then((url) => {
|
|
||||||
if (url.endsWith(signupPage.url)) {
|
|
||||||
signupPage.getters.skip().click();
|
|
||||||
|
|
||||||
Confirmation.getters.header().should('contain.text', 'Skip owner account setup?');
|
|
||||||
Confirmation.actions.confirm();
|
|
||||||
|
|
||||||
// we should be redirected to empty canvas
|
|
||||||
cy.intercept('GET', '/rest/workflows/new').as('loading');
|
|
||||||
cy.url().should('include', workflowPage.url);
|
|
||||||
cy.wait('@loading');
|
|
||||||
} else {
|
|
||||||
cy.log('User already signed up');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('resetAll', () => {
|
Cypress.Commands.add('resetAll', () => {
|
||||||
cy.task('reset');
|
cy.task('reset');
|
||||||
Cypress.session.clearAllSavedSessions();
|
Cypress.session.clearAllSavedSessions();
|
||||||
|
|
|
@ -40,7 +40,6 @@ declare global {
|
||||||
setupOwner(payload: SetupPayload): void;
|
setupOwner(payload: SetupPayload): void;
|
||||||
inviteUsers(payload: InviteUsersPayload): void;
|
inviteUsers(payload: InviteUsersPayload): void;
|
||||||
interceptREST(method: string, url: string): Chainable<Interception>;
|
interceptREST(method: string, url: string): Chainable<Interception>;
|
||||||
skipSetup(): void;
|
|
||||||
resetAll(): void;
|
resetAll(): void;
|
||||||
enableFeature(feature: string): void;
|
enableFeature(feature: string): void;
|
||||||
disableFeature(feature: string): void;
|
disableFeature(feature: string): void;
|
||||||
|
|
|
@ -41,9 +41,6 @@ services:
|
||||||
- traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
|
- traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
|
||||||
- traefik.http.middlewares.n8n.headers.STSPreload=true
|
- traefik.http.middlewares.n8n.headers.STSPreload=true
|
||||||
environment:
|
environment:
|
||||||
- N8N_BASIC_AUTH_ACTIVE=true
|
|
||||||
- N8N_BASIC_AUTH_USER
|
|
||||||
- N8N_BASIC_AUTH_PASSWORD
|
|
||||||
- N8N_HOST=${DOMAIN_NAME}
|
- N8N_HOST=${DOMAIN_NAME}
|
||||||
- N8N_PORT=5678
|
- N8N_PORT=5678
|
||||||
- N8N_PROTOCOL=https
|
- N8N_PROTOCOL=https
|
||||||
|
|
|
@ -33,9 +33,6 @@ services:
|
||||||
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
|
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
|
||||||
- DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER}
|
- DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER}
|
||||||
- DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD}
|
- DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD}
|
||||||
- N8N_BASIC_AUTH_ACTIVE=true
|
|
||||||
- N8N_BASIC_AUTH_USER
|
|
||||||
- N8N_BASIC_AUTH_PASSWORD
|
|
||||||
ports:
|
ports:
|
||||||
- 5678:5678
|
- 5678:5678
|
||||||
links:
|
links:
|
||||||
|
|
|
@ -17,9 +17,6 @@ x-shared: &shared
|
||||||
- EXECUTIONS_MODE=queue
|
- EXECUTIONS_MODE=queue
|
||||||
- QUEUE_BULL_REDIS_HOST=redis
|
- QUEUE_BULL_REDIS_HOST=redis
|
||||||
- QUEUE_HEALTH_CHECK_ACTIVE=true
|
- QUEUE_HEALTH_CHECK_ACTIVE=true
|
||||||
- N8N_BASIC_AUTH_ACTIVE=true
|
|
||||||
- N8N_BASIC_AUTH_USER
|
|
||||||
- N8N_BASIC_AUTH_PASSWORD
|
|
||||||
links:
|
links:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
|
|
@ -71,20 +71,6 @@ docker run -it --rm \
|
||||||
n8n start --tunnel
|
n8n start --tunnel
|
||||||
```
|
```
|
||||||
|
|
||||||
## Securing n8n
|
|
||||||
|
|
||||||
By default n8n can be accessed by everybody. This is OK if you have it only running
|
|
||||||
locally but if you deploy it on a server which is accessible from the web you have
|
|
||||||
to make sure that n8n is protected!
|
|
||||||
Right now we have very basic protection via basic-auth in place. It can be activated
|
|
||||||
by setting the following environment variables:
|
|
||||||
|
|
||||||
```text
|
|
||||||
N8N_BASIC_AUTH_ACTIVE=true
|
|
||||||
N8N_BASIC_AUTH_USER=<USER>
|
|
||||||
N8N_BASIC_AUTH_PASSWORD=<PASSWORD>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Persist data
|
## Persist data
|
||||||
|
|
||||||
The workflow data gets by default saved in an SQLite database in the user
|
The workflow data gets by default saved in an SQLite database in the user
|
||||||
|
@ -181,8 +167,6 @@ The following environment variables support file input:
|
||||||
- DB_POSTGRESDB_PORT_FILE
|
- DB_POSTGRESDB_PORT_FILE
|
||||||
- DB_POSTGRESDB_USER_FILE
|
- DB_POSTGRESDB_USER_FILE
|
||||||
- DB_POSTGRESDB_SCHEMA_FILE
|
- DB_POSTGRESDB_SCHEMA_FILE
|
||||||
- N8N_BASIC_AUTH_PASSWORD_FILE
|
|
||||||
- N8N_BASIC_AUTH_USER_FILE
|
|
||||||
|
|
||||||
## Example Setup with Lets Encrypt
|
## Example Setup with Lets Encrypt
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,9 @@ const ROOT_DIR = path.resolve(__dirname, '..');
|
||||||
const SPEC_FILENAME = 'openapi.yml';
|
const SPEC_FILENAME = 'openapi.yml';
|
||||||
const SPEC_THEME_FILENAME = 'swaggerTheme.css';
|
const SPEC_THEME_FILENAME = 'swaggerTheme.css';
|
||||||
|
|
||||||
const userManagementEnabled = process.env.N8N_USER_MANAGEMENT_DISABLED !== 'true';
|
|
||||||
const publicApiEnabled = process.env.N8N_PUBLIC_API_DISABLED !== 'true';
|
const publicApiEnabled = process.env.N8N_PUBLIC_API_DISABLED !== 'true';
|
||||||
|
|
||||||
if (userManagementEnabled) {
|
copyUserManagementEmailTemplates();
|
||||||
copyUserManagementEmailTemplates();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (publicApiEnabled) {
|
if (publicApiEnabled) {
|
||||||
copySwaggerTheme();
|
copySwaggerTheme();
|
||||||
|
|
|
@ -306,7 +306,6 @@ export interface IDiagnosticInfo {
|
||||||
databaseType: DatabaseType;
|
databaseType: DatabaseType;
|
||||||
notificationsEnabled: boolean;
|
notificationsEnabled: boolean;
|
||||||
disableProductionWebhooksOnMainProcess: boolean;
|
disableProductionWebhooksOnMainProcess: boolean;
|
||||||
basicAuthActive: boolean;
|
|
||||||
systemInfo: {
|
systemInfo: {
|
||||||
os: {
|
os: {
|
||||||
type?: string;
|
type?: string;
|
||||||
|
@ -324,7 +323,6 @@ export interface IDiagnosticInfo {
|
||||||
};
|
};
|
||||||
deploymentType: string;
|
deploymentType: string;
|
||||||
binaryDataMode: string;
|
binaryDataMode: string;
|
||||||
n8n_multi_user_allowed: boolean;
|
|
||||||
smtp_set_up: boolean;
|
smtp_set_up: boolean;
|
||||||
ldap_allowed: boolean;
|
ldap_allowed: boolean;
|
||||||
saml_enabled: boolean;
|
saml_enabled: boolean;
|
||||||
|
|
|
@ -75,12 +75,10 @@ export class InternalHooks implements IInternalHooksClass {
|
||||||
db_type: diagnosticInfo.databaseType,
|
db_type: diagnosticInfo.databaseType,
|
||||||
n8n_version_notifications_enabled: diagnosticInfo.notificationsEnabled,
|
n8n_version_notifications_enabled: diagnosticInfo.notificationsEnabled,
|
||||||
n8n_disable_production_main_process: diagnosticInfo.disableProductionWebhooksOnMainProcess,
|
n8n_disable_production_main_process: diagnosticInfo.disableProductionWebhooksOnMainProcess,
|
||||||
n8n_basic_auth_active: diagnosticInfo.basicAuthActive,
|
|
||||||
system_info: diagnosticInfo.systemInfo,
|
system_info: diagnosticInfo.systemInfo,
|
||||||
execution_variables: diagnosticInfo.executionVariables,
|
execution_variables: diagnosticInfo.executionVariables,
|
||||||
n8n_deployment_type: diagnosticInfo.deploymentType,
|
n8n_deployment_type: diagnosticInfo.deploymentType,
|
||||||
n8n_binary_data_mode: diagnosticInfo.binaryDataMode,
|
n8n_binary_data_mode: diagnosticInfo.binaryDataMode,
|
||||||
n8n_multi_user_allowed: diagnosticInfo.n8n_multi_user_allowed,
|
|
||||||
smtp_set_up: diagnosticInfo.smtp_set_up,
|
smtp_set_up: diagnosticInfo.smtp_set_up,
|
||||||
ldap_allowed: diagnosticInfo.ldap_allowed,
|
ldap_allowed: diagnosticInfo.ldap_allowed,
|
||||||
saml_enabled: diagnosticInfo.saml_enabled,
|
saml_enabled: diagnosticInfo.saml_enabled,
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { User } from '@db/entities/User';
|
||||||
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||||
import { RoleRepository } from '@db/repositories';
|
import { RoleRepository } from '@db/repositories';
|
||||||
import type { AuthProviderSyncHistory } from '@db/entities/AuthProviderSyncHistory';
|
import type { AuthProviderSyncHistory } from '@db/entities/AuthProviderSyncHistory';
|
||||||
import { isUserManagementEnabled } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import { LdapManager } from './LdapManager.ee';
|
import { LdapManager } from './LdapManager.ee';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -37,10 +36,7 @@ import { InternalServerError } from '../ResponseHelper';
|
||||||
/**
|
/**
|
||||||
* Check whether the LDAP feature is disabled in the instance
|
* Check whether the LDAP feature is disabled in the instance
|
||||||
*/
|
*/
|
||||||
export const isLdapEnabled = (): boolean => {
|
export const isLdapEnabled = () => Container.get(License).isLdapEnabled();
|
||||||
const license = Container.get(License);
|
|
||||||
return isUserManagementEnabled() && license.isLdapEnabled();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether the LDAP feature is enabled in the instance
|
* Check whether the LDAP feature is enabled in the instance
|
||||||
|
|
|
@ -87,17 +87,6 @@ export class ServiceUnavailableError extends ResponseError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function basicAuthAuthorizationError(resp: Response, realm: string, message?: string) {
|
|
||||||
resp.statusCode = 401;
|
|
||||||
resp.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
|
|
||||||
resp.json({ code: resp.statusCode, message });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function jwtAuthAuthorizationError(resp: Response, message?: string) {
|
|
||||||
resp.statusCode = 403;
|
|
||||||
resp.json({ code: resp.statusCode, message });
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendSuccessResponse(
|
export function sendSuccessResponse(
|
||||||
res: Response,
|
res: Response,
|
||||||
data: any,
|
data: any,
|
||||||
|
|
|
@ -105,7 +105,6 @@ import {
|
||||||
getInstanceBaseUrl,
|
getInstanceBaseUrl,
|
||||||
isEmailSetUp,
|
isEmailSetUp,
|
||||||
isSharingEnabled,
|
isSharingEnabled,
|
||||||
isUserManagementEnabled,
|
|
||||||
whereClause,
|
whereClause,
|
||||||
} from '@/UserManagement/UserManagementHelper';
|
} from '@/UserManagement/UserManagementHelper';
|
||||||
import { UserManagementMailer } from '@/UserManagement/email';
|
import { UserManagementMailer } from '@/UserManagement/email';
|
||||||
|
@ -145,8 +144,6 @@ import {
|
||||||
} from './Ldap/helpers';
|
} from './Ldap/helpers';
|
||||||
import { AbstractServer } from './AbstractServer';
|
import { AbstractServer } from './AbstractServer';
|
||||||
import { configureMetrics } from './metrics';
|
import { configureMetrics } from './metrics';
|
||||||
import { setupBasicAuth } from './middlewares/basicAuth';
|
|
||||||
import { setupExternalJWTAuth } from './middlewares/externalJWTAuth';
|
|
||||||
import { PostHogClient } from './posthog';
|
import { PostHogClient } from './posthog';
|
||||||
import { eventBus } from './eventbus';
|
import { eventBus } from './eventbus';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
|
@ -261,11 +258,7 @@ export class Server extends AbstractServer {
|
||||||
config.getEnv('personalization.enabled') && config.getEnv('diagnostics.enabled'),
|
config.getEnv('personalization.enabled') && config.getEnv('diagnostics.enabled'),
|
||||||
defaultLocale: config.getEnv('defaultLocale'),
|
defaultLocale: config.getEnv('defaultLocale'),
|
||||||
userManagement: {
|
userManagement: {
|
||||||
enabled: isUserManagementEnabled(),
|
showSetupOnFirstLoad: config.getEnv('userManagement.isInstanceOwnerSetUp') === false,
|
||||||
showSetupOnFirstLoad:
|
|
||||||
config.getEnv('userManagement.disabled') === false &&
|
|
||||||
config.getEnv('userManagement.isInstanceOwnerSetUp') === false &&
|
|
||||||
config.getEnv('userManagement.skipInstanceOwnerSetup') === false,
|
|
||||||
smtpSetup: isEmailSetUp(),
|
smtpSetup: isEmailSetUp(),
|
||||||
authenticationMethod: getCurrentAuthenticationMethod(),
|
authenticationMethod: getCurrentAuthenticationMethod(),
|
||||||
},
|
},
|
||||||
|
@ -349,7 +342,6 @@ export class Server extends AbstractServer {
|
||||||
const cpus = os.cpus();
|
const cpus = os.cpus();
|
||||||
const binaryDataConfig = config.getEnv('binaryDataManager');
|
const binaryDataConfig = config.getEnv('binaryDataManager');
|
||||||
const diagnosticInfo: IDiagnosticInfo = {
|
const diagnosticInfo: IDiagnosticInfo = {
|
||||||
basicAuthActive: config.getEnv('security.basicAuth.active'),
|
|
||||||
databaseType: config.getEnv('database.type'),
|
databaseType: config.getEnv('database.type'),
|
||||||
disableProductionWebhooksOnMainProcess: config.getEnv(
|
disableProductionWebhooksOnMainProcess: config.getEnv(
|
||||||
'endpoints.disableProductionWebhooksOnMainProcess',
|
'endpoints.disableProductionWebhooksOnMainProcess',
|
||||||
|
@ -385,7 +377,6 @@ export class Server extends AbstractServer {
|
||||||
},
|
},
|
||||||
deploymentType: config.getEnv('deployment.type'),
|
deploymentType: config.getEnv('deployment.type'),
|
||||||
binaryDataMode: binaryDataConfig.mode,
|
binaryDataMode: binaryDataConfig.mode,
|
||||||
n8n_multi_user_allowed: isUserManagementEnabled(),
|
|
||||||
smtp_set_up: config.getEnv('userManagement.emails.mode') === 'smtp',
|
smtp_set_up: config.getEnv('userManagement.emails.mode') === 'smtp',
|
||||||
ldap_allowed: isLdapCurrentAuthenticationMethod(),
|
ldap_allowed: isLdapCurrentAuthenticationMethod(),
|
||||||
saml_enabled: isSamlCurrentAuthenticationMethod(),
|
saml_enabled: isSamlCurrentAuthenticationMethod(),
|
||||||
|
@ -414,12 +405,9 @@ export class Server extends AbstractServer {
|
||||||
getSettingsForFrontend(): IN8nUISettings {
|
getSettingsForFrontend(): IN8nUISettings {
|
||||||
// refresh user management status
|
// refresh user management status
|
||||||
Object.assign(this.frontendSettings.userManagement, {
|
Object.assign(this.frontendSettings.userManagement, {
|
||||||
enabled: isUserManagementEnabled(),
|
|
||||||
authenticationMethod: getCurrentAuthenticationMethod(),
|
authenticationMethod: getCurrentAuthenticationMethod(),
|
||||||
showSetupOnFirstLoad:
|
showSetupOnFirstLoad:
|
||||||
config.getEnv('userManagement.disabled') === false &&
|
|
||||||
config.getEnv('userManagement.isInstanceOwnerSetUp') === false &&
|
config.getEnv('userManagement.isInstanceOwnerSetUp') === false &&
|
||||||
config.getEnv('userManagement.skipInstanceOwnerSetup') === false &&
|
|
||||||
config.getEnv('deployment.type').startsWith('desktop_') === false,
|
config.getEnv('deployment.type').startsWith('desktop_') === false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -461,7 +449,7 @@ export class Server extends AbstractServer {
|
||||||
private registerControllers(ignoredEndpoints: Readonly<string[]>) {
|
private registerControllers(ignoredEndpoints: Readonly<string[]>) {
|
||||||
const { app, externalHooks, activeWorkflowRunner, nodeTypes } = this;
|
const { app, externalHooks, activeWorkflowRunner, nodeTypes } = this;
|
||||||
const repositories = Db.collections;
|
const repositories = Db.collections;
|
||||||
setupAuthMiddlewares(app, ignoredEndpoints, this.restEndpoint, repositories.User);
|
setupAuthMiddlewares(app, ignoredEndpoints, this.restEndpoint);
|
||||||
|
|
||||||
const logger = LoggerProxy;
|
const logger = LoggerProxy;
|
||||||
const internalHooks = Container.get(InternalHooks);
|
const internalHooks = Container.get(InternalHooks);
|
||||||
|
@ -552,19 +540,6 @@ export class Server extends AbstractServer {
|
||||||
`REST endpoint cannot be set to any of these values: ${ignoredEndpoints.join()} `,
|
`REST endpoint cannot be set to any of these values: ${ignoredEndpoints.join()} `,
|
||||||
);
|
);
|
||||||
|
|
||||||
// eslint-disable-next-line no-useless-escape
|
|
||||||
const authIgnoreRegex = new RegExp(`^\/(${ignoredEndpoints.join('|')})\/?.*$`);
|
|
||||||
|
|
||||||
// Check for basic auth credentials if activated
|
|
||||||
if (config.getEnv('security.basicAuth.active')) {
|
|
||||||
setupBasicAuth(this.app, config, authIgnoreRegex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for and validate JWT if configured
|
|
||||||
if (config.getEnv('security.jwtAuth.active')) {
|
|
||||||
setupExternalJWTAuth(this.app, config, authIgnoreRegex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Public API
|
// Public API
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
@ -578,7 +553,7 @@ export class Server extends AbstractServer {
|
||||||
this.app.use(cookieParser());
|
this.app.use(cookieParser());
|
||||||
|
|
||||||
const { restEndpoint, app } = this;
|
const { restEndpoint, app } = this;
|
||||||
setupPushHandler(restEndpoint, app, isUserManagementEnabled());
|
setupPushHandler(restEndpoint, app);
|
||||||
|
|
||||||
// Make sure that Vue history mode works properly
|
// Make sure that Vue history mode works properly
|
||||||
this.app.use(
|
this.app.use(
|
||||||
|
|
|
@ -36,26 +36,8 @@ export function isEmailSetUp(): boolean {
|
||||||
return smtp && host && user && pass;
|
return smtp && host && user && pass;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isUserManagementEnabled(): boolean {
|
|
||||||
// This can be simplified but readability is more important here
|
|
||||||
|
|
||||||
if (config.getEnv('userManagement.isInstanceOwnerSetUp')) {
|
|
||||||
// Short circuit - if owner is set up, UM cannot be disabled.
|
|
||||||
// Users must reset their instance in order to do so.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UM is disabled for desktop by default
|
|
||||||
if (config.getEnv('deployment.type').startsWith('desktop_')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return config.getEnv('userManagement.disabled') ? false : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSharingEnabled(): boolean {
|
export function isSharingEnabled(): boolean {
|
||||||
const license = Container.get(License);
|
return Container.get(License).isSharingEnabled();
|
||||||
return isUserManagementEnabled() && license.isSharingEnabled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise<Role['id']> {
|
export async function getRoleId(scope: Role['scope'], name: Role['name']): Promise<Role['id']> {
|
||||||
|
|
|
@ -95,7 +95,7 @@ const setupUserManagement = async () => {
|
||||||
`INSERT INTO user (id, globalRoleId) values ("${uuid()}", ${instanceOwnerRole[0].insertId})`,
|
`INSERT INTO user (id, globalRoleId) values ("${uuid()}", ${instanceOwnerRole[0].insertId})`,
|
||||||
);
|
);
|
||||||
await connection.query(
|
await connection.query(
|
||||||
"INSERT INTO \"settings\" (key, value, loadOnStartup) values ('userManagement.isInstanceOwnerSetUp', 'false', true), ('userManagement.skipInstanceOwnerSetup', 'false', true)",
|
"INSERT INTO \"settings\" (key, value, loadOnStartup) values ('userManagement.isInstanceOwnerSetUp', 'false', true)",
|
||||||
);
|
);
|
||||||
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
config.set('userManagement.isInstanceOwnerSetUp', false);
|
||||||
|
|
|
@ -18,31 +18,17 @@ import { isApiEnabled } from '@/PublicApi';
|
||||||
function getSecuritySettings() {
|
function getSecuritySettings() {
|
||||||
if (config.getEnv('deployment.type') === 'cloud') return null;
|
if (config.getEnv('deployment.type') === 'cloud') return null;
|
||||||
|
|
||||||
const userManagementEnabled = !config.getEnv('userManagement.disabled');
|
|
||||||
const basicAuthActive = config.getEnv('security.basicAuth.active');
|
|
||||||
const jwtAuthActive = config.getEnv('security.jwtAuth.active');
|
|
||||||
|
|
||||||
const isInstancePubliclyAccessible = !userManagementEnabled && !basicAuthActive && !jwtAuthActive;
|
|
||||||
|
|
||||||
const settings: Record<string, unknown> = {};
|
const settings: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (isInstancePubliclyAccessible) {
|
|
||||||
settings.publiclyAccessibleInstance =
|
|
||||||
'Important! Your n8n instance is publicly accessible. Any third party who knows your instance URL can access your data.'.toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.features = {
|
settings.features = {
|
||||||
communityPackagesEnabled: config.getEnv('nodes.communityPackages.enabled'),
|
communityPackagesEnabled: config.getEnv('nodes.communityPackages.enabled'),
|
||||||
versionNotificationsEnabled: config.getEnv('versionNotifications.enabled'),
|
versionNotificationsEnabled: config.getEnv('versionNotifications.enabled'),
|
||||||
templatesEnabled: config.getEnv('templates.enabled'),
|
templatesEnabled: config.getEnv('templates.enabled'),
|
||||||
publicApiEnabled: isApiEnabled(),
|
publicApiEnabled: isApiEnabled(),
|
||||||
userManagementEnabled,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
settings.auth = {
|
settings.auth = {
|
||||||
authExcludeEndpoints: config.getEnv('security.excludeEndpoints') || 'none',
|
authExcludeEndpoints: config.getEnv('security.excludeEndpoints') || 'none',
|
||||||
basicAuthActive,
|
|
||||||
jwtAuthActive,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
settings.nodes = {
|
settings.nodes = {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { getLogger } from '@/Logger';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import * as CrashJournal from '@/CrashJournal';
|
import * as CrashJournal from '@/CrashJournal';
|
||||||
import { USER_MANAGEMENT_DOCS_URL, inTest } from '@/constants';
|
import { inTest } from '@/constants';
|
||||||
import { CredentialTypes } from '@/CredentialTypes';
|
import { CredentialTypes } from '@/CredentialTypes';
|
||||||
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
||||||
import { initErrorHandling } from '@/ErrorReporting';
|
import { initErrorHandling } from '@/ErrorReporting';
|
||||||
|
@ -78,24 +78,6 @@ export abstract class BaseCommand extends Command {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.N8N_BASIC_AUTH_ACTIVE === 'true') {
|
|
||||||
LoggerProxy.warn(
|
|
||||||
`Basic auth has been deprecated and will be removed in a future version of n8n. For authentication, please consider User Management. To learn more: ${USER_MANAGEMENT_DOCS_URL}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.N8N_JWT_AUTH_ACTIVE === 'true') {
|
|
||||||
LoggerProxy.warn(
|
|
||||||
`JWT auth has been deprecated and will be removed in a future version of n8n. For authentication, please consider User Management. To learn more: ${USER_MANAGEMENT_DOCS_URL}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.N8N_USER_MANAGEMENT_DISABLED === 'true') {
|
|
||||||
LoggerProxy.warn(
|
|
||||||
`User Management will be mandatory in a future version of n8n. Please set up the instance owner. To learn more: ${USER_MANAGEMENT_DOCS_URL}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.instanceId = this.userSettings.instanceId ?? '';
|
this.instanceId = this.userSettings.instanceId ?? '';
|
||||||
await Container.get(PostHogClient).init(this.instanceId);
|
await Container.get(PostHogClient).init(this.instanceId);
|
||||||
await Container.get(InternalHooks).init(this.instanceId);
|
await Container.get(InternalHooks).init(this.instanceId);
|
||||||
|
|
|
@ -56,10 +56,6 @@ export class Reset extends BaseCommand {
|
||||||
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
{ key: 'userManagement.isInstanceOwnerSetUp' },
|
||||||
{ value: 'false' },
|
{ value: 'false' },
|
||||||
);
|
);
|
||||||
await Db.collections.Settings.update(
|
|
||||||
{ key: 'userManagement.skipInstanceOwnerSetup' },
|
|
||||||
{ value: 'false' },
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger.info('Successfully reset the database to default user state.');
|
this.logger.info('Successfully reset the database to default user state.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -492,82 +492,6 @@ export const schema = {
|
||||||
default: '',
|
default: '',
|
||||||
env: 'N8N_AUTH_EXCLUDE_ENDPOINTS',
|
env: 'N8N_AUTH_EXCLUDE_ENDPOINTS',
|
||||||
},
|
},
|
||||||
basicAuth: {
|
|
||||||
active: {
|
|
||||||
format: 'Boolean',
|
|
||||||
default: false,
|
|
||||||
env: 'N8N_BASIC_AUTH_ACTIVE',
|
|
||||||
doc: '[DEPRECATED] If basic auth should be activated for editor and REST-API',
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_BASIC_AUTH_USER',
|
|
||||||
doc: '[DEPRECATED] The name of the basic auth user',
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_BASIC_AUTH_PASSWORD',
|
|
||||||
doc: '[DEPRECATED] The password of the basic auth user',
|
|
||||||
},
|
|
||||||
hash: {
|
|
||||||
format: 'Boolean',
|
|
||||||
default: false,
|
|
||||||
env: 'N8N_BASIC_AUTH_HASH',
|
|
||||||
doc: '[DEPRECATED] If password for basic auth is hashed',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
jwtAuth: {
|
|
||||||
active: {
|
|
||||||
format: 'Boolean',
|
|
||||||
default: false,
|
|
||||||
env: 'N8N_JWT_AUTH_ACTIVE',
|
|
||||||
doc: '[DEPRECATED] If JWT auth should be activated for editor and REST-API',
|
|
||||||
},
|
|
||||||
jwtHeader: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_JWT_AUTH_HEADER',
|
|
||||||
doc: '[DEPRECATED] The request header containing a signed JWT',
|
|
||||||
},
|
|
||||||
jwtHeaderValuePrefix: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_JWT_AUTH_HEADER_VALUE_PREFIX',
|
|
||||||
doc: '[DEPRECATED] The request header value prefix to strip (optional)',
|
|
||||||
},
|
|
||||||
jwksUri: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_JWKS_URI',
|
|
||||||
doc: '[DEPRECATED] The URI to fetch JWK Set for JWT authentication',
|
|
||||||
},
|
|
||||||
jwtIssuer: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_JWT_ISSUER',
|
|
||||||
doc: '[DEPRECATED] JWT issuer to expect (optional)',
|
|
||||||
},
|
|
||||||
jwtNamespace: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_JWT_NAMESPACE',
|
|
||||||
doc: '[DEPRECATED] JWT namespace to expect (optional)',
|
|
||||||
},
|
|
||||||
jwtAllowedTenantKey: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_JWT_ALLOWED_TENANT_KEY',
|
|
||||||
doc: '[DEPRECATED] JWT tenant key name to inspect within JWT namespace (optional)',
|
|
||||||
},
|
|
||||||
jwtAllowedTenant: {
|
|
||||||
format: String,
|
|
||||||
default: '',
|
|
||||||
env: 'N8N_JWT_ALLOWED_TENANT',
|
|
||||||
doc: '[DEPRECATED] JWT tenant to allow (optional)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
endpoints: {
|
endpoints: {
|
||||||
|
@ -726,12 +650,6 @@ export const schema = {
|
||||||
},
|
},
|
||||||
|
|
||||||
userManagement: {
|
userManagement: {
|
||||||
disabled: {
|
|
||||||
doc: '[DEPRECATED] Disable user management and hide it completely.',
|
|
||||||
format: Boolean,
|
|
||||||
default: false,
|
|
||||||
env: 'N8N_USER_MANAGEMENT_DISABLED',
|
|
||||||
},
|
|
||||||
jwtSecret: {
|
jwtSecret: {
|
||||||
doc: 'Set a specific JWT secret (optional - n8n can generate one)', // Generated @ start.ts
|
doc: 'Set a specific JWT secret (optional - n8n can generate one)', // Generated @ start.ts
|
||||||
format: String,
|
format: String,
|
||||||
|
@ -744,12 +662,6 @@ export const schema = {
|
||||||
format: Boolean,
|
format: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
skipInstanceOwnerSetup: {
|
|
||||||
// n8n loads this setting from DB on startup
|
|
||||||
doc: 'Whether to hide the prompt the first time n8n starts with UM enabled',
|
|
||||||
format: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
emails: {
|
emails: {
|
||||||
mode: {
|
mode: {
|
||||||
doc: 'How to send emails',
|
doc: 'How to send emails',
|
||||||
|
|
1
packages/cli/src/config/types.d.ts
vendored
1
packages/cli/src/config/types.d.ts
vendored
|
@ -80,7 +80,6 @@ type ExceptionPaths = {
|
||||||
'nodes.exclude': string[] | undefined;
|
'nodes.exclude': string[] | undefined;
|
||||||
'nodes.include': string[] | undefined;
|
'nodes.include': string[] | undefined;
|
||||||
'userManagement.isInstanceOwnerSetUp': boolean;
|
'userManagement.isInstanceOwnerSetUp': boolean;
|
||||||
'userManagement.skipInstanceOwnerSetup': boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
|
|
|
@ -86,6 +86,3 @@ export const enum LICENSE_QUOTAS {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CREDENTIAL_BLANKING_VALUE = '__n8n_BLANK_VALUE_e5362baf-c777-4d57-a609-6eaf1f9e87f6';
|
export const CREDENTIAL_BLANKING_VALUE = '__n8n_BLANK_VALUE_e5362baf-c777-4d57-a609-6eaf1f9e87f6';
|
||||||
|
|
||||||
export const USER_MANAGEMENT_DOCS_URL =
|
|
||||||
'https://docs.n8n.io/hosting/authentication/user-management-self-hosted';
|
|
||||||
|
|
|
@ -148,19 +148,4 @@ export class OwnerController {
|
||||||
|
|
||||||
return sanitizeUser(owner);
|
return sanitizeUser(owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Persist that the instance owner setup has been skipped
|
|
||||||
*/
|
|
||||||
@Post('/skip-setup')
|
|
||||||
async skipSetup() {
|
|
||||||
await this.settingsRepository.update(
|
|
||||||
{ key: 'userManagement.skipInstanceOwnerSetup' },
|
|
||||||
{ value: JSON.stringify(true) },
|
|
||||||
);
|
|
||||||
|
|
||||||
this.config.set('userManagement.skipInstanceOwnerSetup', true);
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import type {
|
||||||
import type { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
import type { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
|
||||||
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
import { AuthIdentity } from '@db/entities/AuthIdentity';
|
||||||
import type { PostHogClient } from '@/posthog';
|
import type { PostHogClient } from '@/posthog';
|
||||||
import { userManagementEnabledMiddleware } from '../middlewares/userManagementEnabled';
|
|
||||||
import { isSamlLicensedAndEnabled } from '../sso/saml/samlHelpers';
|
import { isSamlLicensedAndEnabled } from '../sso/saml/samlHelpers';
|
||||||
import type {
|
import type {
|
||||||
RoleRepository,
|
RoleRepository,
|
||||||
|
@ -106,7 +105,7 @@ export class UsersController {
|
||||||
/**
|
/**
|
||||||
* Send email invite(s) to one or multiple users and create user shell(s).
|
* Send email invite(s) to one or multiple users and create user shell(s).
|
||||||
*/
|
*/
|
||||||
@Post('/', { middlewares: [userManagementEnabledMiddleware] })
|
@Post('/')
|
||||||
async sendEmailInvites(req: UserRequest.Invite) {
|
async sendEmailInvites(req: UserRequest.Invite) {
|
||||||
if (isSamlLicensedAndEnabled()) {
|
if (isSamlLicensedAndEnabled()) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import type { IrreversibleMigration, MigrationContext } from '@db/types';
|
||||||
|
|
||||||
|
export class RemoveSkipOwnerSetup1681134145997 implements IrreversibleMigration {
|
||||||
|
async up({ queryRunner, tablePrefix }: MigrationContext) {
|
||||||
|
await queryRunner.query(
|
||||||
|
`DELETE FROM ${tablePrefix}settings WHERE \`key\` = 'userManagement.skipInstanceOwnerSetup';`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import { CreateVariables1677501636753 } from './1677501636753-CreateVariables';
|
||||||
import { AddUserActivatedProperty1681134145996 } from './1681134145996-AddUserActivatedProperty';
|
import { AddUserActivatedProperty1681134145996 } from './1681134145996-AddUserActivatedProperty';
|
||||||
import { MigrateIntegerKeysToString1690000000001 } from './1690000000001-MigrateIntegerKeysToString';
|
import { MigrateIntegerKeysToString1690000000001 } from './1690000000001-MigrateIntegerKeysToString';
|
||||||
import { SeparateExecutionData1690000000030 } from './1690000000030-SeparateExecutionData';
|
import { SeparateExecutionData1690000000030 } from './1690000000030-SeparateExecutionData';
|
||||||
|
import { RemoveSkipOwnerSetup1681134145997 } from './1681134145997-RemoveSkipOwnerSetup';
|
||||||
|
|
||||||
export const mysqlMigrations: Migration[] = [
|
export const mysqlMigrations: Migration[] = [
|
||||||
InitialMigration1588157391238,
|
InitialMigration1588157391238,
|
||||||
|
@ -83,4 +84,5 @@ export const mysqlMigrations: Migration[] = [
|
||||||
AddUserActivatedProperty1681134145996,
|
AddUserActivatedProperty1681134145996,
|
||||||
MigrateIntegerKeysToString1690000000001,
|
MigrateIntegerKeysToString1690000000001,
|
||||||
SeparateExecutionData1690000000030,
|
SeparateExecutionData1690000000030,
|
||||||
|
RemoveSkipOwnerSetup1681134145997,
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import type { IrreversibleMigration, MigrationContext } from '@db/types';
|
||||||
|
|
||||||
|
export class RemoveSkipOwnerSetup1681134145997 implements IrreversibleMigration {
|
||||||
|
async up({ queryRunner, tablePrefix }: MigrationContext) {
|
||||||
|
await queryRunner.query(
|
||||||
|
`DELETE FROM ${tablePrefix}settings WHERE key = 'userManagement.skipInstanceOwnerSetup';`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ import { CreateVariables1677501636754 } from './1677501636754-CreateVariables';
|
||||||
import { AddUserActivatedProperty1681134145996 } from './1681134145996-AddUserActivatedProperty';
|
import { AddUserActivatedProperty1681134145996 } from './1681134145996-AddUserActivatedProperty';
|
||||||
import { MigrateIntegerKeysToString1690000000000 } from './1690000000000-MigrateIntegerKeysToString';
|
import { MigrateIntegerKeysToString1690000000000 } from './1690000000000-MigrateIntegerKeysToString';
|
||||||
import { SeparateExecutionData1690000000020 } from './1690000000020-SeparateExecutionData';
|
import { SeparateExecutionData1690000000020 } from './1690000000020-SeparateExecutionData';
|
||||||
|
import { RemoveSkipOwnerSetup1681134145997 } from './1681134145997-RemoveSkipOwnerSetup';
|
||||||
|
|
||||||
export const postgresMigrations: Migration[] = [
|
export const postgresMigrations: Migration[] = [
|
||||||
InitialMigration1587669153312,
|
InitialMigration1587669153312,
|
||||||
|
@ -79,4 +80,5 @@ export const postgresMigrations: Migration[] = [
|
||||||
AddUserActivatedProperty1681134145996,
|
AddUserActivatedProperty1681134145996,
|
||||||
MigrateIntegerKeysToString1690000000000,
|
MigrateIntegerKeysToString1690000000000,
|
||||||
SeparateExecutionData1690000000020,
|
SeparateExecutionData1690000000020,
|
||||||
|
RemoveSkipOwnerSetup1681134145997,
|
||||||
];
|
];
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import type { IrreversibleMigration, MigrationContext } from '@db/types';
|
||||||
|
|
||||||
|
export class RemoveSkipOwnerSetup1681134145997 implements IrreversibleMigration {
|
||||||
|
async up({ queryRunner, tablePrefix }: MigrationContext) {
|
||||||
|
await queryRunner.query(
|
||||||
|
`DELETE FROM "${tablePrefix}settings" WHERE key = 'userManagement.skipInstanceOwnerSetup';`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ import { CreateVariables1677501636752 } from './1677501636752-CreateVariables';
|
||||||
import { AddUserActivatedProperty1681134145996 } from './1681134145996-AddUserActivatedProperty';
|
import { AddUserActivatedProperty1681134145996 } from './1681134145996-AddUserActivatedProperty';
|
||||||
import { MigrateIntegerKeysToString1690000000002 } from './1690000000002-MigrateIntegerKeysToString';
|
import { MigrateIntegerKeysToString1690000000002 } from './1690000000002-MigrateIntegerKeysToString';
|
||||||
import { SeparateExecutionData1690000000010 } from './1690000000010-SeparateExecutionData';
|
import { SeparateExecutionData1690000000010 } from './1690000000010-SeparateExecutionData';
|
||||||
|
import { RemoveSkipOwnerSetup1681134145997 } from './1681134145997-RemoveSkipOwnerSetup';
|
||||||
|
|
||||||
const sqliteMigrations: Migration[] = [
|
const sqliteMigrations: Migration[] = [
|
||||||
InitialMigration1588102412422,
|
InitialMigration1588102412422,
|
||||||
|
@ -77,6 +78,7 @@ const sqliteMigrations: Migration[] = [
|
||||||
AddUserActivatedProperty1681134145996,
|
AddUserActivatedProperty1681134145996,
|
||||||
MigrateIntegerKeysToString1690000000002,
|
MigrateIntegerKeysToString1690000000002,
|
||||||
SeparateExecutionData1690000000010,
|
SeparateExecutionData1690000000010,
|
||||||
|
RemoveSkipOwnerSetup1681134145997,
|
||||||
];
|
];
|
||||||
|
|
||||||
export { sqliteMigrations };
|
export { sqliteMigrations };
|
||||||
|
|
|
@ -10,7 +10,6 @@ import type { AuthenticatedRequest } from '@/requests';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { AUTH_COOKIE_NAME, EDITOR_UI_DIST_DIR } from '@/constants';
|
import { AUTH_COOKIE_NAME, EDITOR_UI_DIST_DIR } from '@/constants';
|
||||||
import { issueCookie, resolveJwtContent } from '@/auth/jwt';
|
import { issueCookie, resolveJwtContent } from '@/auth/jwt';
|
||||||
import { isUserManagementEnabled } from '@/UserManagement/UserManagementHelper';
|
|
||||||
import type { UserRepository } from '@db/repositories';
|
import type { UserRepository } from '@db/repositories';
|
||||||
import { canSkipAuth } from '@/decorators/registerController';
|
import { canSkipAuth } from '@/decorators/registerController';
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ const jwtFromRequest = (req: Request) => {
|
||||||
return (req.cookies?.[AUTH_COOKIE_NAME] as string | undefined) ?? null;
|
return (req.cookies?.[AUTH_COOKIE_NAME] as string | undefined) ?? null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const jwtAuth = (): RequestHandler => {
|
const userManagementJwtAuth = (): RequestHandler => {
|
||||||
const jwtStrategy = new Strategy(
|
const jwtStrategy = new Strategy(
|
||||||
{
|
{
|
||||||
jwtFromRequest,
|
jwtFromRequest,
|
||||||
|
@ -79,11 +78,10 @@ export const setupAuthMiddlewares = (
|
||||||
app: Application,
|
app: Application,
|
||||||
ignoredEndpoints: Readonly<string[]>,
|
ignoredEndpoints: Readonly<string[]>,
|
||||||
restEndpoint: string,
|
restEndpoint: string,
|
||||||
userRepository: UserRepository,
|
|
||||||
) => {
|
) => {
|
||||||
// needed for testing; not adding overhead since it directly returns if req.cookies exists
|
// needed for testing; not adding overhead since it directly returns if req.cookies exists
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(jwtAuth());
|
app.use(userManagementJwtAuth());
|
||||||
|
|
||||||
app.use(async (req: Request, res: Response, next: NextFunction) => {
|
app.use(async (req: Request, res: Response, next: NextFunction) => {
|
||||||
if (
|
if (
|
||||||
|
@ -101,15 +99,6 @@ export const setupAuthMiddlewares = (
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip authentication if user management is disabled
|
|
||||||
if (!isUserManagementEnabled()) {
|
|
||||||
req.user = await userRepository.findOneOrFail({
|
|
||||||
relations: ['globalRole'],
|
|
||||||
where: {},
|
|
||||||
});
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
return passportMiddleware(req, res, next);
|
return passportMiddleware(req, res, next);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
import type { Application } from 'express';
|
|
||||||
import basicAuth from 'basic-auth';
|
|
||||||
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
|
||||||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
|
||||||
import { compare } from 'bcryptjs';
|
|
||||||
import type { Config } from '@/config';
|
|
||||||
import { basicAuthAuthorizationError } from '@/ResponseHelper';
|
|
||||||
|
|
||||||
export const setupBasicAuth = (app: Application, config: Config, authIgnoreRegex: RegExp) => {
|
|
||||||
const basicAuthUser = config.getEnv('security.basicAuth.user');
|
|
||||||
if (basicAuthUser === '') {
|
|
||||||
throw new Error('Basic auth is activated but no user got defined. Please set one!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const basicAuthPassword = config.getEnv('security.basicAuth.password');
|
|
||||||
if (basicAuthPassword === '') {
|
|
||||||
throw new Error('Basic auth is activated but no password got defined. Please set one!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const basicAuthHashEnabled = config.getEnv('security.basicAuth.hash');
|
|
||||||
|
|
||||||
let validPassword: null | string = null;
|
|
||||||
|
|
||||||
app.use(async (req, res, next) => {
|
|
||||||
// Skip basic auth for a few listed endpoints or when instance owner has been setup
|
|
||||||
if (authIgnoreRegex.exec(req.url) || config.getEnv('userManagement.isInstanceOwnerSetUp')) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
const realm = 'n8n - Editor UI';
|
|
||||||
const basicAuthData = basicAuth(req);
|
|
||||||
|
|
||||||
if (basicAuthData === undefined) {
|
|
||||||
// Authorization data is missing
|
|
||||||
return basicAuthAuthorizationError(res, realm, 'Authorization is required!');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (basicAuthData.name === basicAuthUser) {
|
|
||||||
if (basicAuthHashEnabled) {
|
|
||||||
if (validPassword === null && (await compare(basicAuthData.pass, basicAuthPassword))) {
|
|
||||||
// Password is valid so save for future requests
|
|
||||||
validPassword = basicAuthData.pass;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validPassword === basicAuthData.pass && validPassword !== null) {
|
|
||||||
// Provided hash is correct
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
} else if (basicAuthData.pass === basicAuthPassword) {
|
|
||||||
// Provided password is correct
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Provided authentication data is wrong
|
|
||||||
return basicAuthAuthorizationError(res, realm, 'Authorization data is wrong!');
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,85 +0,0 @@
|
||||||
import type { Application } from 'express';
|
|
||||||
import jwt from 'jsonwebtoken';
|
|
||||||
import jwks from 'jwks-rsa';
|
|
||||||
import type { Config } from '@/config';
|
|
||||||
import { jwtAuthAuthorizationError } from '@/ResponseHelper';
|
|
||||||
|
|
||||||
export const setupExternalJWTAuth = (app: Application, config: Config, authIgnoreRegex: RegExp) => {
|
|
||||||
const jwtAuthHeader = config.getEnv('security.jwtAuth.jwtHeader');
|
|
||||||
if (jwtAuthHeader === '') {
|
|
||||||
throw new Error('JWT auth is activated but no request header was defined. Please set one!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const jwksUri = config.getEnv('security.jwtAuth.jwksUri');
|
|
||||||
if (jwksUri === '') {
|
|
||||||
throw new Error('JWT auth is activated but no JWK Set URI was defined. Please set one!');
|
|
||||||
}
|
|
||||||
|
|
||||||
const jwtHeaderValuePrefix = config.getEnv('security.jwtAuth.jwtHeaderValuePrefix');
|
|
||||||
const jwtIssuer = config.getEnv('security.jwtAuth.jwtIssuer');
|
|
||||||
const jwtNamespace = config.getEnv('security.jwtAuth.jwtNamespace');
|
|
||||||
const jwtAllowedTenantKey = config.getEnv('security.jwtAuth.jwtAllowedTenantKey');
|
|
||||||
const jwtAllowedTenant = config.getEnv('security.jwtAuth.jwtAllowedTenant');
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-inner-declarations
|
|
||||||
function isTenantAllowed(decodedToken: object): boolean {
|
|
||||||
if (jwtNamespace === '' || jwtAllowedTenantKey === '' || jwtAllowedTenant === '') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [k, v] of Object.entries(decodedToken)) {
|
|
||||||
if (k === jwtNamespace) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
||||||
for (const [kn, kv] of Object.entries(v)) {
|
|
||||||
if (kn === jwtAllowedTenantKey && kv === jwtAllowedTenant) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
if (authIgnoreRegex.exec(req.url)) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
let token = req.header(jwtAuthHeader) as string;
|
|
||||||
if (token === undefined || token === '') {
|
|
||||||
return jwtAuthAuthorizationError(res, 'Missing token');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jwtHeaderValuePrefix !== '' && token.startsWith(jwtHeaderValuePrefix)) {
|
|
||||||
token = token.replace(`${jwtHeaderValuePrefix} `, '').trimStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
const jwkClient = jwks({ cache: true, jwksUri });
|
|
||||||
const getKey: jwt.GetPublicKeyOrSecret = (header, callbackFn) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
||||||
if (!header.kid) throw jwtAuthAuthorizationError(res, 'No JWT key found');
|
|
||||||
jwkClient.getSigningKey(header.kid, (error, key) => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
||||||
if (error) throw jwtAuthAuthorizationError(res, error.message);
|
|
||||||
callbackFn(null, key?.getPublicKey());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const jwtVerifyOptions: jwt.VerifyOptions = {
|
|
||||||
issuer: jwtIssuer !== '' ? jwtIssuer : undefined,
|
|
||||||
ignoreExpiration: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
jwt.verify(token, getKey, jwtVerifyOptions, (error: jwt.VerifyErrors, decoded: object) => {
|
|
||||||
if (error) {
|
|
||||||
jwtAuthAuthorizationError(res, 'Invalid token');
|
|
||||||
} else if (!isTenantAllowed(decoded)) {
|
|
||||||
jwtAuthAuthorizationError(res, 'Tenant not allowed');
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -1,12 +0,0 @@
|
||||||
import type { RequestHandler } from 'express';
|
|
||||||
import { LoggerProxy } from 'n8n-workflow';
|
|
||||||
import { isUserManagementEnabled } from '../UserManagement/UserManagementHelper';
|
|
||||||
|
|
||||||
export const userManagementEnabledMiddleware: RequestHandler = (req, res, next) => {
|
|
||||||
if (isUserManagementEnabled()) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
LoggerProxy.debug('Request failed because user management is disabled');
|
|
||||||
res.status(400).json({ status: 'error', message: 'User management is disabled' });
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -57,11 +57,7 @@ export const setupPushServer = (restEndpoint: string, server: Server, app: Appli
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setupPushHandler = (
|
export const setupPushHandler = (restEndpoint: string, app: Application) => {
|
||||||
restEndpoint: string,
|
|
||||||
app: Application,
|
|
||||||
isUserManagementEnabled: boolean,
|
|
||||||
) => {
|
|
||||||
const endpoint = `/${restEndpoint}/push`;
|
const endpoint = `/${restEndpoint}/push`;
|
||||||
|
|
||||||
const pushValidationMiddleware: RequestHandler = async (
|
const pushValidationMiddleware: RequestHandler = async (
|
||||||
|
@ -83,7 +79,7 @@ export const setupPushHandler = (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle authentication
|
// Handle authentication
|
||||||
if (isUserManagementEnabled) {
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||||
const authCookie: string = req.cookies?.[AUTH_COOKIE_NAME] ?? '';
|
const authCookie: string = req.cookies?.[AUTH_COOKIE_NAME] ?? '';
|
||||||
|
@ -97,7 +93,6 @@ export const setupPushHandler = (
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { User } from '@db/entities/User';
|
||||||
import { RoleRepository } from '@db/repositories';
|
import { RoleRepository } from '@db/repositories';
|
||||||
import { License } from '@/License';
|
import { License } from '@/License';
|
||||||
import { AuthError, InternalServerError } from '@/ResponseHelper';
|
import { AuthError, InternalServerError } from '@/ResponseHelper';
|
||||||
import { hashPassword, isUserManagementEnabled } from '@/UserManagement/UserManagementHelper';
|
import { hashPassword } from '@/UserManagement/UserManagementHelper';
|
||||||
import type { SamlPreferences } from './types/samlPreferences';
|
import type { SamlPreferences } from './types/samlPreferences';
|
||||||
import type { SamlUserAttributes } from './types/samlUserAttributes';
|
import type { SamlUserAttributes } from './types/samlUserAttributes';
|
||||||
import type { FlowResult } from 'samlify/types/src/flow';
|
import type { FlowResult } from 'samlify/types/src/flow';
|
||||||
|
@ -52,10 +52,7 @@ export function setSamlLoginLabel(label: string): void {
|
||||||
config.set(SAML_LOGIN_LABEL, label);
|
config.set(SAML_LOGIN_LABEL, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSamlLicensed(): boolean {
|
export const isSamlLicensed = () => Container.get(License).isSamlEnabled();
|
||||||
const license = Container.get(License);
|
|
||||||
return isUserManagementEnabled() && license.isSamlEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSamlLicensedAndEnabled(): boolean {
|
export function isSamlLicensedAndEnabled(): boolean {
|
||||||
return isSamlLoginEnabled() && isSamlLicensed() && isSamlCurrentAuthenticationMethod();
|
return isSamlLoginEnabled() && isSamlLicensed() && isSamlCurrentAuthenticationMethod();
|
||||||
|
|
|
@ -244,12 +244,9 @@ test('should report security settings', async () => {
|
||||||
versionNotificationsEnabled: true,
|
versionNotificationsEnabled: true,
|
||||||
templatesEnabled: true,
|
templatesEnabled: true,
|
||||||
publicApiEnabled: false,
|
publicApiEnabled: false,
|
||||||
userManagementEnabled: true,
|
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
authExcludeEndpoints: 'none',
|
authExcludeEndpoints: 'none',
|
||||||
basicAuthActive: false,
|
|
||||||
jwtAuthActive: false,
|
|
||||||
},
|
},
|
||||||
nodes: { nodesExclude: 'none', nodesInclude: 'none' },
|
nodes: { nodesExclude: 'none', nodesInclude: 'none' },
|
||||||
telemetry: { diagnosticsEnabled: true },
|
telemetry: { diagnosticsEnabled: true },
|
||||||
|
|
|
@ -106,7 +106,6 @@ beforeAll(async () => {
|
||||||
await utils.initConfigFile();
|
await utils.initConfigFile();
|
||||||
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
|
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
|
||||||
config.set('eventBus.logWriter.keepLogCount', 1);
|
config.set('eventBus.logWriter.keepLogCount', 1);
|
||||||
config.set('userManagement.disabled', false);
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
|
|
||||||
await eventBus.initialize();
|
await eventBus.initialize();
|
||||||
|
|
|
@ -77,7 +77,6 @@ beforeEach(async () => {
|
||||||
|
|
||||||
jest.mock('@/telemetry');
|
jest.mock('@/telemetry');
|
||||||
|
|
||||||
config.set('userManagement.disabled', false);
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
config.set('userManagement.emails.mode', '');
|
config.set('userManagement.emails.mode', '');
|
||||||
});
|
});
|
||||||
|
|
|
@ -169,19 +169,3 @@ describe('POST /owner/setup', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('POST /owner/skip-setup', () => {
|
|
||||||
test('should persist skipping setup to the DB', async () => {
|
|
||||||
const response = await authOwnerShellAgent.post('/owner/skip-setup').send();
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
|
||||||
|
|
||||||
const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup');
|
|
||||||
expect(skipConfig).toBe(true);
|
|
||||||
|
|
||||||
const { value } = await Db.collections.Settings.findOneByOrFail({
|
|
||||||
key: 'userManagement.skipInstanceOwnerSetup',
|
|
||||||
});
|
|
||||||
expect(value).toBe('true');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -67,7 +67,6 @@ beforeEach(async () => {
|
||||||
version: 1,
|
version: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
config.set('userManagement.disabled', false);
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,6 @@ beforeEach(async () => {
|
||||||
version: 1,
|
version: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
config.set('userManagement.disabled', false);
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,6 @@ export const ROUTES_REQUIRING_AUTHORIZATION: Readonly<string[]> = [
|
||||||
'POST /users/123/reinvite',
|
'POST /users/123/reinvite',
|
||||||
'GET /owner/pre-setup',
|
'GET /owner/pre-setup',
|
||||||
'POST /owner/setup',
|
'POST /owner/setup',
|
||||||
'POST /owner/skip-setup',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const COMMUNITY_PACKAGE_VERSION = {
|
export const COMMUNITY_PACKAGE_VERSION = {
|
||||||
|
|
|
@ -60,7 +60,6 @@ beforeEach(async () => {
|
||||||
|
|
||||||
jest.mock('@/config');
|
jest.mock('@/config');
|
||||||
|
|
||||||
config.set('userManagement.disabled', false);
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||||
config.set('userManagement.emails.mode', 'smtp');
|
config.set('userManagement.emails.mode', 'smtp');
|
||||||
config.set('userManagement.emails.smtp.host', '');
|
config.set('userManagement.emails.smtp.host', '');
|
||||||
|
@ -379,15 +378,6 @@ describe('POST /users', () => {
|
||||||
expect(response.body.data[0].user.inviteAcceptUrl).toBeDefined();
|
expect(response.body.data[0].user.inviteAcceptUrl).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should fail if user management is disabled', async () => {
|
|
||||||
config.set('userManagement.disabled', true);
|
|
||||||
config.set('userManagement.isInstanceOwnerSetUp', false);
|
|
||||||
|
|
||||||
const response = await authOwnerAgent.post('/users').send([{ email: randomEmail() }]);
|
|
||||||
|
|
||||||
expect(response.statusCode).toBe(400);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should email invites and create user shells but ignore existing', async () => {
|
test('should email invites and create user shells but ignore existing', async () => {
|
||||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||||
|
|
|
@ -123,15 +123,4 @@ describe('OwnerController', () => {
|
||||||
expect(cookieOptions.value.sameSite).toBe('lax');
|
expect(cookieOptions.value.sameSite).toBe('lax');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('skipSetup', () => {
|
|
||||||
it('should skip setting up the instance owner', async () => {
|
|
||||||
await controller.skipSetup();
|
|
||||||
expect(settingsRepository.update).toHaveBeenCalledWith(
|
|
||||||
{ key: 'userManagement.skipInstanceOwnerSetup' },
|
|
||||||
{ value: JSON.stringify(true) },
|
|
||||||
);
|
|
||||||
expect(config.set).toHaveBeenCalledWith('userManagement.skipInstanceOwnerSetup', true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
import express from 'express';
|
|
||||||
import request from 'supertest';
|
|
||||||
import config from '@/config';
|
|
||||||
import { setupBasicAuth } from '@/middlewares/basicAuth';
|
|
||||||
|
|
||||||
describe('Basic Auth Middleware', () => {
|
|
||||||
let app: express.Application;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
app = express();
|
|
||||||
config.set('security.basicAuth', { user: 'jim', password: 'n8n', hash: false, active: true });
|
|
||||||
setupBasicAuth(app, config, new RegExp('^/skip-auth'));
|
|
||||||
app.get('/test', (req, res) => res.send({ auth: true }));
|
|
||||||
app.get('/skip-auth', (req, res) => res.send({ auth: false }));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not block calls to /skip-auth', async () => {
|
|
||||||
const response = await request(app).get('/skip-auth');
|
|
||||||
expect(response.statusCode).toEqual(200);
|
|
||||||
expect(response.headers).not.toHaveProperty('www-authenticate');
|
|
||||||
expect(response.body).toEqual({ auth: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should block calls to /test if auth is absent', async () => {
|
|
||||||
const response = await request(app).get('/test');
|
|
||||||
expect(response.statusCode).toEqual(401);
|
|
||||||
expect(response.headers).toHaveProperty('www-authenticate');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should block calls to /test if auth is invalid', async () => {
|
|
||||||
const response = await request(app).get('/test').auth('user', 'invalid');
|
|
||||||
expect(response.statusCode).toEqual(401);
|
|
||||||
expect(response.headers).toHaveProperty('www-authenticate');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow access to /test if basic auth header is valid', async () => {
|
|
||||||
const response = await request(app).get('/test').auth('jim', 'n8n');
|
|
||||||
expect(response.statusCode).toEqual(200);
|
|
||||||
expect(response.headers).not.toHaveProperty('www-authenticate');
|
|
||||||
expect(response.body).toEqual({ auth: true });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,47 +0,0 @@
|
||||||
import express from 'express';
|
|
||||||
import request from 'supertest';
|
|
||||||
import createJWKSMock from 'mock-jwks';
|
|
||||||
import config from '@/config';
|
|
||||||
import { setupExternalJWTAuth } from '@/middlewares/externalJWTAuth';
|
|
||||||
|
|
||||||
const testJWKUri = 'https://n8n.test/';
|
|
||||||
const jwksMock = createJWKSMock(testJWKUri);
|
|
||||||
|
|
||||||
describe('External JWT Auth Middleware', () => {
|
|
||||||
let app: express.Application;
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
app = express();
|
|
||||||
config.set('security.jwtAuth.jwtHeader', 'Authorization');
|
|
||||||
config.set('security.jwtAuth.jwtHeaderValuePrefix', 'Bearer');
|
|
||||||
config.set('security.jwtAuth.jwtIssuer', 'n8n');
|
|
||||||
config.set('security.jwtAuth.jwksUri', `${testJWKUri}.well-known/jwks.json`);
|
|
||||||
setupExternalJWTAuth(app, config, new RegExp('^/skip-auth'));
|
|
||||||
app.get('/test', (req, res) => res.send({ auth: true }));
|
|
||||||
app.get('/skip-auth', (req, res) => res.send({ auth: false }));
|
|
||||||
|
|
||||||
jwksMock.start();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not block calls to /skip-auth', async () => {
|
|
||||||
const response = await request(app).get('/skip-auth');
|
|
||||||
expect(response.statusCode).toEqual(200);
|
|
||||||
expect(response.body).toEqual({ auth: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should block calls to /test if auth is absent', async () =>
|
|
||||||
request(app).get('/test').expect(403));
|
|
||||||
|
|
||||||
it('should block calls to /test if auth is invalid', async () => {
|
|
||||||
const token = jwksMock.token({ iss: 'invalid' });
|
|
||||||
const response = await request(app).get('/test').set('Authorization', `Bearer ${token}`);
|
|
||||||
expect(response.statusCode).toEqual(403);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow access to /test if JWT auth header is valid', async () => {
|
|
||||||
const token = jwksMock.token({ iss: 'n8n' });
|
|
||||||
const response = await request(app).get('/test').set('Authorization', `Bearer ${token}`);
|
|
||||||
expect(response.statusCode).toEqual(200);
|
|
||||||
expect(response.body).toEqual({ auth: true });
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -18,7 +18,7 @@
|
||||||
:disabled="item.disabled"
|
:disabled="item.disabled"
|
||||||
:divided="item.divided"
|
:divided="item.divided"
|
||||||
>
|
>
|
||||||
<div :class="getItemClasses(item)" :data-test-id="`workflow-menu-item-${item.id}`">
|
<div :class="getItemClasses(item)" :data-test-id="`${testIdPrefix}-item-${item.id}`">
|
||||||
<span v-if="item.icon" :class="$style.icon">
|
<span v-if="item.icon" :class="$style.icon">
|
||||||
<n8n-icon :icon="item.icon" :size="iconSize" />
|
<n8n-icon :icon="item.icon" :size="iconSize" />
|
||||||
</span>
|
</span>
|
||||||
|
@ -66,6 +66,10 @@ export default defineComponent({
|
||||||
ElDropdownItem,
|
ElDropdownItem,
|
||||||
N8nIcon,
|
N8nIcon,
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
const testIdPrefix = this.$attrs['data-test-id'];
|
||||||
|
return { testIdPrefix };
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
items: {
|
items: {
|
||||||
type: Array as PropType<IActionDropdownItem[]>,
|
type: Array as PropType<IActionDropdownItem[]>,
|
||||||
|
|
|
@ -142,7 +142,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
authenticate() {
|
authenticate() {
|
||||||
// redirect to setup page. user should be redirected to this only once
|
// redirect to setup page. user should be redirected to this only once
|
||||||
if (this.settingsStore.isUserManagementEnabled && this.settingsStore.showSetupPage) {
|
if (this.settingsStore.showSetupPage) {
|
||||||
if (this.$route.name === VIEWS.SETUP) {
|
if (this.$route.name === VIEWS.SETUP) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,6 @@ export async function setupOwner(
|
||||||
return makeRestApiRequest(context, 'POST', '/owner/setup', params as unknown as IDataObject);
|
return makeRestApiRequest(context, 'POST', '/owner/setup', params as unknown as IDataObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function skipOwnerSetup(context: IRestApiContext): Promise<void> {
|
|
||||||
return makeRestApiRequest(context, 'POST', '/owner/skip-setup');
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function validateSignupToken(
|
export async function validateSignupToken(
|
||||||
context: IRestApiContext,
|
context: IRestApiContext,
|
||||||
params: { inviterId: string; inviteeId: string },
|
params: { inviterId: string; inviteeId: string },
|
||||||
|
|
|
@ -89,6 +89,7 @@
|
||||||
<n8n-action-dropdown
|
<n8n-action-dropdown
|
||||||
:items="userMenuItems"
|
:items="userMenuItems"
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
|
data-test-id="user-menu"
|
||||||
@select="onUserActionToggle"
|
@select="onUserActionToggle"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -171,11 +172,7 @@ export default defineComponent({
|
||||||
return accessibleRoute !== null;
|
return accessibleRoute !== null;
|
||||||
},
|
},
|
||||||
showUserArea(): boolean {
|
showUserArea(): boolean {
|
||||||
return (
|
return this.usersStore.canUserAccessSidebarUserInfo && this.usersStore.currentUser !== null;
|
||||||
this.settingsStore.isUserManagementEnabled &&
|
|
||||||
this.usersStore.canUserAccessSidebarUserInfo &&
|
|
||||||
this.usersStore.currentUser !== null
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
workflowExecution(): IExecutionResponse | null {
|
workflowExecution(): IExecutionResponse | null {
|
||||||
return this.workflowsStore.getWorkflowExecution;
|
return this.workflowsStore.getWorkflowExecution;
|
||||||
|
|
|
@ -95,15 +95,11 @@
|
||||||
"auth.setup.createAccount": "Create account",
|
"auth.setup.createAccount": "Create account",
|
||||||
"auth.setup.goBack": "Go back",
|
"auth.setup.goBack": "Go back",
|
||||||
"auth.setup.next": "Next",
|
"auth.setup.next": "Next",
|
||||||
"auth.setup.ownerAccountBenefits": "Setting up an owner account allows you to invite others, and prevents people using n8n without an account",
|
|
||||||
"auth.setup.settingUpOwnerError": "Problem setting up owner",
|
"auth.setup.settingUpOwnerError": "Problem setting up owner",
|
||||||
"auth.setup.setupConfirmation.concatEntities": "{workflows} and {credentials}",
|
"auth.setup.setupConfirmation.concatEntities": "{workflows} and {credentials}",
|
||||||
"auth.setup.setupConfirmation.credentials": "{count} credential | {count} credentials",
|
"auth.setup.setupConfirmation.credentials": "{count} credential | {count} credentials",
|
||||||
"auth.setup.setupConfirmation.existingWorkflows": "{count} existing workflow | {count} existing workflows",
|
"auth.setup.setupConfirmation.existingWorkflows": "{count} existing workflow | {count} existing workflows",
|
||||||
"auth.setup.setupOwner": "Set up owner account",
|
"auth.setup.setupOwner": "Set up owner account",
|
||||||
"auth.setup.skipOwnerSetupQuestion": "Skip owner account setup?",
|
|
||||||
"auth.setup.skipSetup": "Skip setup",
|
|
||||||
"auth.setup.skipSetupTemporarily": "Skip setup for now",
|
|
||||||
"auth.signin": "Sign in",
|
"auth.signin": "Sign in",
|
||||||
"auth.signin.error": "Problem logging in",
|
"auth.signin.error": "Problem logging in",
|
||||||
"auth.signout": "Sign out",
|
"auth.signout": "Sign out",
|
||||||
|
|
|
@ -416,12 +416,6 @@ export const routes = [
|
||||||
allow: {
|
allow: {
|
||||||
role: [ROLE.Default],
|
role: [ROLE.Default],
|
||||||
},
|
},
|
||||||
deny: {
|
|
||||||
shouldDeny: () => {
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
return settingsStore.isUserManagementEnabled === false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -537,17 +531,7 @@ export const routes = [
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: {
|
allow: {
|
||||||
role: [ROLE.Default, ROLE.Owner],
|
role: [ROLE.Owner],
|
||||||
},
|
|
||||||
deny: {
|
|
||||||
shouldDeny: () => {
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
|
|
||||||
return (
|
|
||||||
settingsStore.isUserManagementEnabled === false &&
|
|
||||||
!(settingsStore.isCloudDeployment || settingsStore.isDesktopDeployment)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -645,7 +629,7 @@ export const routes = [
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: {
|
allow: {
|
||||||
role: [ROLE.Default, ROLE.Owner],
|
role: [ROLE.Owner],
|
||||||
},
|
},
|
||||||
deny: {
|
deny: {
|
||||||
role: [ROLE.Member],
|
role: [ROLE.Member],
|
||||||
|
@ -665,7 +649,7 @@ export const routes = [
|
||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: {
|
allow: {
|
||||||
role: [ROLE.Default, ROLE.Owner],
|
role: [ROLE.Owner],
|
||||||
},
|
},
|
||||||
deny: {
|
deny: {
|
||||||
shouldDeny: () => {
|
shouldDeny: () => {
|
||||||
|
@ -707,7 +691,7 @@ export const routes = [
|
||||||
meta: {
|
meta: {
|
||||||
permissions: {
|
permissions: {
|
||||||
allow: {
|
allow: {
|
||||||
role: [ROLE.Default, ROLE.Owner],
|
role: [ROLE.Owner],
|
||||||
},
|
},
|
||||||
deny: {
|
deny: {
|
||||||
role: [ROLE.Member],
|
role: [ROLE.Member],
|
||||||
|
|
|
@ -37,7 +37,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
||||||
settings: {} as IN8nUISettings,
|
settings: {} as IN8nUISettings,
|
||||||
promptsData: {} as IN8nPrompts,
|
promptsData: {} as IN8nPrompts,
|
||||||
userManagement: {
|
userManagement: {
|
||||||
enabled: false,
|
|
||||||
showSetupOnFirstLoad: false,
|
showSetupOnFirstLoad: false,
|
||||||
smtpSetup: false,
|
smtpSetup: false,
|
||||||
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
||||||
|
@ -71,9 +70,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
||||||
versionCli(): string {
|
versionCli(): string {
|
||||||
return this.settings.versionCli;
|
return this.settings.versionCli;
|
||||||
},
|
},
|
||||||
isUserManagementEnabled(): boolean {
|
|
||||||
return this.userManagement.enabled;
|
|
||||||
},
|
|
||||||
isPublicApiEnabled(): boolean {
|
isPublicApiEnabled(): boolean {
|
||||||
return this.api.enabled;
|
return this.api.enabled;
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
sendForgotPasswordEmail,
|
sendForgotPasswordEmail,
|
||||||
setupOwner,
|
setupOwner,
|
||||||
signup,
|
signup,
|
||||||
skipOwnerSetup,
|
|
||||||
submitPersonalizationSurvey,
|
submitPersonalizationSurvey,
|
||||||
updateCurrentUser,
|
updateCurrentUser,
|
||||||
updateCurrentUserPassword,
|
updateCurrentUserPassword,
|
||||||
|
@ -332,13 +331,5 @@ export const useUsersStore = defineStore(STORES.USERS, {
|
||||||
uiStore.openModal(PERSONALIZATION_MODAL_KEY);
|
uiStore.openModal(PERSONALIZATION_MODAL_KEY);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async skipOwnerSetup(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
settingsStore.stopShowingSetupPage();
|
|
||||||
await skipOwnerSetup(rootStore.getRestApiContext);
|
|
||||||
} catch (error) {}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,6 @@ import type { IWorkflowSettings } from 'n8n-workflow';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useRootStore } from './n8nRoot.store';
|
import { useRootStore } from './n8nRoot.store';
|
||||||
import { useNDVStore } from './ndv.store';
|
import { useNDVStore } from './ndv.store';
|
||||||
import { useSettingsStore } from './settings.store';
|
|
||||||
import { useUIStore } from './ui.store';
|
import { useUIStore } from './ui.store';
|
||||||
import { useUsersStore } from './users.store';
|
import { useUsersStore } from './users.store';
|
||||||
import { useWorkflowsStore } from './workflows.store';
|
import { useWorkflowsStore } from './workflows.store';
|
||||||
|
@ -21,9 +20,6 @@ export const useWebhooksStore = defineStore(STORES.WEBHOOKS, {
|
||||||
getFakeDoorFeatures() {
|
getFakeDoorFeatures() {
|
||||||
return useUIStore().fakeDoorFeatures;
|
return useUIStore().fakeDoorFeatures;
|
||||||
},
|
},
|
||||||
isUserManagementEnabled() {
|
|
||||||
return useSettingsStore().isUserManagementEnabled;
|
|
||||||
},
|
|
||||||
getFakeDoorItems(): IFakeDoor[] {
|
getFakeDoorItems(): IFakeDoor[] {
|
||||||
return useUIStore().fakeDoorFeatures;
|
return useUIStore().fakeDoorFeatures;
|
||||||
},
|
},
|
||||||
|
|
|
@ -101,7 +101,7 @@ export const PERMISSIONS: IUserPermissions = {
|
||||||
TAGS: {
|
TAGS: {
|
||||||
CAN_DELETE_TAGS: {
|
CAN_DELETE_TAGS: {
|
||||||
allow: {
|
allow: {
|
||||||
role: [ROLE.Owner, ROLE.Default],
|
role: [ROLE.Owner],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -125,7 +125,7 @@ export const PERMISSIONS: IUserPermissions = {
|
||||||
USAGE: {
|
USAGE: {
|
||||||
CAN_ACTIVATE_LICENSE: {
|
CAN_ACTIVATE_LICENSE: {
|
||||||
allow: {
|
allow: {
|
||||||
role: [ROLE.Owner, ROLE.Default],
|
role: [ROLE.Owner],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,23 +19,7 @@
|
||||||
</n8n-tooltip>
|
</n8n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!settingsStore.isUserManagementEnabled" :class="$style.setupInfoContainer">
|
<div v-if="usersStore.showUMSetupWarning" :class="$style.setupInfoContainer">
|
||||||
<n8n-action-box
|
|
||||||
:heading="
|
|
||||||
$locale.baseText(uiStore.contextBasedTranslationKeys.users.settings.unavailable.title)
|
|
||||||
"
|
|
||||||
:description="
|
|
||||||
$locale.baseText(
|
|
||||||
uiStore.contextBasedTranslationKeys.users.settings.unavailable.description,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
:buttonText="
|
|
||||||
$locale.baseText(uiStore.contextBasedTranslationKeys.users.settings.unavailable.button)
|
|
||||||
"
|
|
||||||
@click="goToUpgrade"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="usersStore.showUMSetupWarning" :class="$style.setupInfoContainer">
|
|
||||||
<n8n-action-box
|
<n8n-action-box
|
||||||
:heading="$locale.baseText('settings.users.setupToInviteUsers')"
|
:heading="$locale.baseText('settings.users.setupToInviteUsers')"
|
||||||
:buttonText="$locale.baseText('settings.users.setupMyAccount')"
|
:buttonText="$locale.baseText('settings.users.setupMyAccount')"
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
:formLoading="loading"
|
:formLoading="loading"
|
||||||
data-test-id="setup-form"
|
data-test-id="setup-form"
|
||||||
@submit="onSubmit"
|
@submit="onSubmit"
|
||||||
@secondaryClick="showSkipConfirmation"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -41,7 +40,6 @@ export default defineComponent({
|
||||||
const FORM_CONFIG: IFormBoxConfig = {
|
const FORM_CONFIG: IFormBoxConfig = {
|
||||||
title: this.$locale.baseText('auth.setup.setupOwner'),
|
title: this.$locale.baseText('auth.setup.setupOwner'),
|
||||||
buttonText: this.$locale.baseText('auth.setup.next'),
|
buttonText: this.$locale.baseText('auth.setup.next'),
|
||||||
secondaryButtonText: this.$locale.baseText('auth.setup.skipSetupTemporarily'),
|
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: 'email',
|
name: 'email',
|
||||||
|
@ -177,25 +175,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
async showSkipConfirmation() {
|
|
||||||
const skip = await this.confirm(
|
|
||||||
this.$locale.baseText('auth.setup.ownerAccountBenefits'),
|
|
||||||
this.$locale.baseText('auth.setup.skipOwnerSetupQuestion'),
|
|
||||||
{
|
|
||||||
confirmButtonText: this.$locale.baseText('auth.setup.skipSetup'),
|
|
||||||
cancelButtonText: this.$locale.baseText('auth.setup.goBack'),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (skip === MODAL_CONFIRM) {
|
|
||||||
this.onSkip();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSkip() {
|
|
||||||
void this.usersStore.skipOwnerSetup();
|
|
||||||
void this.$router.push({
|
|
||||||
name: VIEWS.NEW_WORKFLOW,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -78,7 +78,7 @@ export default defineComponent({
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.settingsStore.isDesktopDeployment || this.settingsStore.isUserManagementEnabled) {
|
if (!this.settingsStore.isDesktopDeployment) {
|
||||||
this.FORM_CONFIG.redirectLink = '/forgot-password';
|
this.FORM_CONFIG.redirectLink = '/forgot-password';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
export type AuthenticationMethod = 'none' | 'email' | 'ldap' | 'saml';
|
export type AuthenticationMethod = 'email' | 'ldap' | 'saml';
|
||||||
|
|
|
@ -2041,7 +2041,6 @@ export interface IVersionNotificationSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserManagementSettings {
|
export interface IUserManagementSettings {
|
||||||
enabled: boolean;
|
|
||||||
showSetupOnFirstLoad?: boolean;
|
showSetupOnFirstLoad?: boolean;
|
||||||
smtpSetup: boolean;
|
smtpSetup: boolean;
|
||||||
authenticationMethod: AuthenticationMethod;
|
authenticationMethod: AuthenticationMethod;
|
||||||
|
|
Loading…
Reference in a new issue