refactor(core)!: Remove basic-auth, external-jwt-auth, and no-auth options (#6362)

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
Iván Ovejero 2023-06-07 16:53:53 +02:00 committed by कारतोफ्फेलस्क्रिप्ट™
parent a45a2c8c41
commit 8c008f5d22
85 changed files with 297 additions and 831 deletions

View file

@ -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);
}); });

View file

@ -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();
}); });

View file

@ -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');

View file

@ -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();
}); });

View file

@ -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();
}); });

View file

@ -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();
}); });

View file

@ -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(

View file

@ -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) => {

View file

@ -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();
}); });

View file

@ -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) => {

View file

@ -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();
}); });

View file

@ -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();
}); });

View file

@ -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);
}); });

View file

@ -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();

View file

@ -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();
}); });

View file

@ -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();

View file

@ -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(

View file

@ -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();
}); });

View file

@ -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);
});
});

View file

@ -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();
}); });

View file

@ -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();

View file

@ -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');

View file

@ -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();
}); });

View file

@ -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', () => {

View file

@ -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');

View file

@ -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();

View file

@ -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;

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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
@ -193,19 +177,19 @@ A basic step by step example setup of n8n with docker-compose and Lets Encrypt i
1. Pull the latest version from the registry 1. Pull the latest version from the registry
`docker pull docker.n8n.io/n8nio/n8n` `docker pull docker.n8n.io/n8nio/n8n`
2. Stop the current setup 2. Stop the current setup
`sudo docker-compose stop` `sudo docker-compose stop`
3. Delete it (will only delete the docker-containers, data is stored separately) 3. Delete it (will only delete the docker-containers, data is stored separately)
`sudo docker-compose rm` `sudo docker-compose rm`
4. Then start it again 4. Then start it again
`sudo docker-compose up -d` `sudo docker-compose up -d`
## Setting Timezone ## Setting Timezone

View file

@ -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();

View file

@ -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;

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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(

View file

@ -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']> {

View file

@ -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);

View file

@ -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 = {

View file

@ -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);

View file

@ -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.');
} }

View file

@ -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',

View file

@ -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;
}; };
// ----------------------------------- // -----------------------------------

View file

@ -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';

View file

@ -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 };
}
} }

View file

@ -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(

View file

@ -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';`,
);
}
}

View file

@ -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,
]; ];

View file

@ -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';`,
);
}
}

View file

@ -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,
]; ];

View file

@ -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';`,
);
}
}

View file

@ -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 };

View file

@ -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);
}); });

View file

@ -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!');
});
};

View file

@ -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();
}
});
});
};

View file

@ -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' });
}
};

View file

@ -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,20 +79,19 @@ 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] ?? '';
await resolveJwt(authCookie); await resolveJwt(authCookie);
} catch (error) { } catch (error) {
if (ws) { if (ws) {
ws.send(`Unauthorized: ${(error as Error).message}`); ws.send(`Unauthorized: ${(error as Error).message}`);
ws.close(401); ws.close(401);
} else { } else {
res.status(401).send('Unauthorized'); res.status(401).send('Unauthorized');
}
return;
} }
return;
} }
next(); next();

View file

@ -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();

View file

@ -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 },

View file

@ -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();

View file

@ -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', '');
}); });

View file

@ -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');
});
});

View file

@ -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);
}); });

View file

@ -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);
}); });

View file

@ -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 = {

View file

@ -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);

View file

@ -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);
});
});
}); });

View file

@ -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 });
});
});

View file

@ -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 });
});
});

View file

@ -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[]>,

View file

@ -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;
} }

View file

@ -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 },

View file

@ -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;

View file

@ -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",

View file

@ -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],

View file

@ -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;
}, },

View file

@ -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) {}
},
}, },
}); });

View file

@ -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;
}, },

View file

@ -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],
}, },
}, },
}, },

View file

@ -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')"

View file

@ -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>

View file

@ -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';
} }
}, },

View file

@ -1 +1 @@
export type AuthenticationMethod = 'none' | 'email' | 'ldap' | 'saml'; export type AuthenticationMethod = 'email' | 'ldap' | 'saml';

View file

@ -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;