import { authenticator } from 'otplib'; import { INSTANCE_OWNER_CREDENTIALS } from '../../../../config/test-users'; import { test, expect } from '../../../../fixtures/base'; test.use({ capability: { env: { TEST_ISOLATION: 'two-factor-auth' } } }); const TEST_DATA = { NEW_EMAIL: 'newemail@test.com', NEW_FIRST_NAME: 'newFirstName', NEW_LAST_NAME: 'newLastName', }; const NOTIFICATIONS = { PERSONAL_DETAILS_UPDATED: 'Personal details updated', }; const { email, password, mfaSecret, mfaRecoveryCodes } = INSTANCE_OWNER_CREDENTIALS; const RECOVERY_CODE = mfaRecoveryCodes![0]; test.describe( 'Two-factor authentication @auth:none @db:reset', { annotation: [{ type: 'owner', description: 'Identity & Access' }], }, () => { test.describe.configure({ mode: 'serial' }); test('Should be able to login with MFA code', async ({ n8n }) => { await n8n.mfaComposer.enableMfa(email, password, mfaSecret!); await n8n.sideBar.signOutFromWorkflows(); await n8n.mfaComposer.loginWithMfaCode(email, password, mfaSecret!); await expect(n8n.page).toHaveURL(/workflows/); }); test('Should be able to login with MFA recovery code', async ({ n8n }) => { await n8n.mfaComposer.enableMfa(email, password, mfaSecret!); await n8n.sideBar.signOutFromWorkflows(); await n8n.mfaComposer.loginWithMfaRecoveryCode(email, password, RECOVERY_CODE); await expect(n8n.page).toHaveURL(/workflows/); }); test('Should be able to disable MFA in account with MFA code', async ({ n8n }) => { await n8n.mfaComposer.enableMfa(email, password, mfaSecret!); await n8n.sideBar.signOutFromWorkflows(); await n8n.mfaComposer.loginWithMfaCode(email, password, mfaSecret!); const disableToken = authenticator.generate(mfaSecret!); await n8n.settingsPersonal.triggerDisableMfa(); await n8n.settingsPersonal.fillMfaCodeAndSave(disableToken); await expect(n8n.settingsPersonal.getEnableMfaButton()).toBeVisible(); }); test('Should prompt for MFA code when email changes', async ({ n8n }) => { await n8n.mfaComposer.enableMfa(email, password, mfaSecret!); await n8n.settingsPersonal.goto(); await n8n.settingsPersonal.fillEmail(TEST_DATA.NEW_EMAIL); await n8n.settingsPersonal.pressEnterOnEmail(); const mfaCode = authenticator.generate(mfaSecret!); await n8n.settingsPersonal.fillMfaCodeAndSave(mfaCode); await expect( n8n.notifications.getNotificationByTitleOrContent(NOTIFICATIONS.PERSONAL_DETAILS_UPDATED), ).toBeVisible(); }); test('Should prompt for MFA recovery code when email changes', async ({ n8n }) => { await n8n.mfaComposer.enableMfa(email, password, mfaSecret!); await n8n.settingsPersonal.goto(); await n8n.settingsPersonal.fillEmail(TEST_DATA.NEW_EMAIL); await n8n.settingsPersonal.pressEnterOnEmail(); await expect(n8n.settingsPersonal.getMfaCodeOrRecoveryCodeInput()).toBeVisible(); }); test('Should not prompt for MFA code or recovery code when first name or last name changes', async ({ n8n, }) => { await n8n.mfaComposer.enableMfa(email, password, mfaSecret!); await n8n.settingsPersonal.updateFirstAndLastName( TEST_DATA.NEW_FIRST_NAME, TEST_DATA.NEW_LAST_NAME, ); await expect( n8n.notifications.getNotificationByTitleOrContent(NOTIFICATIONS.PERSONAL_DETAILS_UPDATED), ).toBeVisible(); }); test('Should be able to disable MFA in account with recovery code', async ({ n8n }) => { await n8n.mfaComposer.enableMfa(email, password, mfaSecret!); await n8n.sideBar.signOutFromWorkflows(); await n8n.mfaComposer.loginWithMfaCode(email, password, mfaSecret!); await n8n.settingsPersonal.triggerDisableMfa(); await n8n.settingsPersonal.fillMfaCodeAndSave(RECOVERY_CODE); await expect(n8n.settingsPersonal.getEnableMfaButton()).toBeVisible(); }); }, );