diff --git a/cypress/constants.ts b/cypress/constants.ts index 28dfc43cf3..5074c41080 100644 --- a/cypress/constants.ts +++ b/cypress/constants.ts @@ -12,6 +12,13 @@ export const INSTANCE_OWNER = { lastName: randLastName(), }; +export const INSTANCE_ADMIN = { + email: 'admin@n8n.io', + password: DEFAULT_USER_PASSWORD, + firstName: randFirstName(), + lastName: randLastName(), +}; + export const INSTANCE_MEMBERS = [ { email: 'rebecca@n8n.io', diff --git a/cypress/e2e/18-user-management.cy.ts b/cypress/e2e/18-user-management.cy.ts index 8c4e89f22c..2f1f530e2e 100644 --- a/cypress/e2e/18-user-management.cy.ts +++ b/cypress/e2e/18-user-management.cy.ts @@ -1,4 +1,4 @@ -import { INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants'; +import { INSTANCE_MEMBERS, INSTANCE_OWNER, INSTANCE_ADMIN } from '../constants'; import { MainSidebar, SettingsSidebar, SettingsUsersPage, WorkflowPage } from '../pages'; import { PersonalSettingsPage } from '../pages/settings-personal'; @@ -46,7 +46,7 @@ describe('User Management', { disableAutoLogin: true }, () => { it('should properly render UM settings page for instance owners', () => { usersSettingsPage.actions.loginAndVisit(INSTANCE_OWNER.email, INSTANCE_OWNER.password, true); // All items in user list should be there - usersSettingsPage.getters.userListItems().should('have.length', 3); + usersSettingsPage.getters.userListItems().should('have.length', 4); // List item for current user should have the `Owner` badge usersSettingsPage.getters .userItem(INSTANCE_OWNER.email) @@ -55,6 +55,7 @@ describe('User Management', { disableAutoLogin: true }, () => { // Other users list items should contain action pop-up list usersSettingsPage.getters.userActionsToggle(INSTANCE_MEMBERS[0].email).should('exist'); usersSettingsPage.getters.userActionsToggle(INSTANCE_MEMBERS[1].email).should('exist'); + usersSettingsPage.getters.userActionsToggle(INSTANCE_ADMIN.email).should('exist'); }); it('should be able to change theme', () => { diff --git a/cypress/e2e/27-two-factor-authentication.cy.ts b/cypress/e2e/27-two-factor-authentication.cy.ts index 5e93909331..91f6ca57a2 100644 --- a/cypress/e2e/27-two-factor-authentication.cy.ts +++ b/cypress/e2e/27-two-factor-authentication.cy.ts @@ -1,5 +1,5 @@ import { MainSidebar } from './../pages/sidebar/main-sidebar'; -import { INSTANCE_OWNER, BACKEND_BASE_URL } from '../constants'; +import { INSTANCE_OWNER, INSTANCE_ADMIN, BACKEND_BASE_URL } from '../constants'; import { SigninPage } from '../pages'; import { PersonalSettingsPage } from '../pages/settings-personal'; import { MfaLoginPage } from '../pages/mfa-login'; @@ -19,6 +19,16 @@ const user = { mfaRecoveryCodes: [RECOVERY_CODE], }; +const admin = { + email: INSTANCE_ADMIN.email, + password: INSTANCE_ADMIN.password, + firstName: 'Admin', + lastName: 'B', + mfaEnabled: false, + mfaSecret: MFA_SECRET, + mfaRecoveryCodes: [RECOVERY_CODE], +}; + const mfaLoginPage = new MfaLoginPage(); const signinPage = new SigninPage(); const personalSettingsPage = new PersonalSettingsPage(); @@ -30,6 +40,7 @@ describe('Two-factor authentication', () => { cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/reset`, { owner: user, members: [], + admin, }); cy.on('uncaught:exception', (err, runnable) => { expect(err.message).to.include('Not logged in'); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index de5a39d549..74a92a111d 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -1,10 +1,11 @@ -import { BACKEND_BASE_URL, INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants'; +import { BACKEND_BASE_URL, INSTANCE_ADMIN, INSTANCE_MEMBERS, INSTANCE_OWNER } from '../constants'; import './commands'; before(() => { cy.request('POST', `${BACKEND_BASE_URL}/rest/e2e/reset`, { owner: INSTANCE_OWNER, members: INSTANCE_MEMBERS, + admin: INSTANCE_ADMIN, }); Cypress.on('uncaught:exception', (err) => { diff --git a/packages/cli/src/controllers/e2e.controller.ts b/packages/cli/src/controllers/e2e.controller.ts index 8a41b81207..8a231b0864 100644 --- a/packages/cli/src/controllers/e2e.controller.ts +++ b/packages/cli/src/controllers/e2e.controller.ts @@ -16,6 +16,7 @@ import type { UserSetupPayload } from '@/requests'; import type { BooleanLicenseFeature, IPushDataType } from '@/Interfaces'; import { MfaService } from '@/Mfa/mfa.service'; import { Push } from '@/push'; +import { CacheService } from '@/services/cache.service'; if (!inE2ETests) { console.error('E2E endpoints only allowed during E2E tests'); @@ -49,6 +50,7 @@ type ResetRequest = Request< { owner: UserSetupPayload; members: UserSetupPayload[]; + admin: UserSetupPayload; } >; @@ -92,6 +94,7 @@ export class E2EController { private userRepo: UserRepository, private workflowRunner: ActiveWorkflowRunner, private mfaService: MfaService, + private cacheService: CacheService, ) { license.isFeatureEnabled = (feature: BooleanLicenseFeature) => this.enabledFeatures[feature] ?? false; @@ -103,7 +106,8 @@ export class E2EController { await this.resetLogStreaming(); await this.removeActiveWorkflows(); await this.truncateAll(); - await this.setupUserManagement(req.body.owner, req.body.members); + await this.resetCache(); + await this.setupUserManagement(req.body.owner, req.body.members, req.body.admin); } @Post('/push') @@ -160,19 +164,25 @@ export class E2EController { } } - private async setupUserManagement(owner: UserSetupPayload, members: UserSetupPayload[]) { + private async setupUserManagement( + owner: UserSetupPayload, + members: UserSetupPayload[], + admin: UserSetupPayload, + ) { const roles: Array<[Role['name'], Role['scope']]> = [ ['owner', 'global'], ['member', 'global'], + ['admin', 'global'], ['owner', 'workflow'], ['owner', 'credential'], ['user', 'credential'], ['editor', 'workflow'], ]; - const [{ id: globalOwnerRoleId }, { id: globalMemberRoleId }] = await this.roleRepo.save( - roles.map(([name, scope], index) => ({ name, scope, id: (index + 1).toString() })), - ); + const [{ id: globalOwnerRoleId }, { id: globalMemberRoleId }, { id: globalAdminRoleId }] = + await this.roleRepo.save( + roles.map(([name, scope], index) => ({ name, scope, id: (index + 1).toString() })), + ); const instanceOwner = { id: uuid(), @@ -188,9 +198,16 @@ export class E2EController { instanceOwner.mfaRecoveryCodes = encryptedRecoveryCodes; } + const adminUser = { + id: uuid(), + ...admin, + password: await hashPassword(admin.password), + globalRoleId: globalAdminRoleId, + }; + const users = []; - users.push(instanceOwner); + users.push(instanceOwner, adminUser); for (const { password, ...payload } of members) { users.push( @@ -212,4 +229,8 @@ export class E2EController { config.set('userManagement.isInstanceOwnerSetUp', true); } + + private async resetCache() { + await this.cacheService.reset(); + } }