diff --git a/packages/frontend/editor-ui/src/features/core/auth/views/SettingsPersonalView.test.ts b/packages/frontend/editor-ui/src/features/core/auth/views/SettingsPersonalView.test.ts index 1338096cf6d..d1e3bfe1971 100644 --- a/packages/frontend/editor-ui/src/features/core/auth/views/SettingsPersonalView.test.ts +++ b/packages/frontend/editor-ui/src/features/core/auth/views/SettingsPersonalView.test.ts @@ -162,6 +162,29 @@ describe('SettingsPersonalView', () => { }); }); + describe('when signed in via LDAP', () => { + beforeEach(() => { + vi.spyOn(ssoStore, 'isEnterpriseLdapEnabled', 'get').mockReturnValue(true); + vi.spyOn(settingsStore, 'isMfaFeatureEnabled', 'get').mockReturnValue(true); + usersStore.usersById[currentUser.id] = { ...currentUser, signInType: 'ldap' }; + }); + + it('should let a member configure MFA while hiding password change', async () => { + vi.spyOn(usersStore, 'isInstanceOwner', 'get').mockReturnValue(false); + + const { queryByTestId, getAllByRole } = renderComponent({ pinia }); + await waitAllPromises(); + + // LDAP has no native 2FA, so n8n's own MFA stays configurable... + expect(queryByTestId('mfa-section')).toBeInTheDocument(); + // ...but password/email remain managed externally. + expect(queryByTestId('change-password-link')).not.toBeInTheDocument(); + expect( + getAllByRole('textbox').find((el) => el.getAttribute('type') === 'email'), + ).toBeDisabled(); + }); + }); + test.each([ ['Default', ROLE.Default, false, 'Default role for new users'], ['Member', ROLE.Member, false, 'Create and manage own workflows and credentials'], diff --git a/packages/frontend/editor-ui/src/features/core/auth/views/SettingsPersonalView.vue b/packages/frontend/editor-ui/src/features/core/auth/views/SettingsPersonalView.vue index 9bcbd95952d..8d2f686f6d4 100644 --- a/packages/frontend/editor-ui/src/features/core/auth/views/SettingsPersonalView.vue +++ b/packages/frontend/editor-ui/src/features/core/auth/views/SettingsPersonalView.vue @@ -97,15 +97,17 @@ const isManagedByEnv = computed((): boolean => { return currentUser.value?.isManagedByEnv ?? false; }); +const isLdapCurrentAuthMethod = computed((): boolean => { + return ssoStore.isEnterpriseLdapEnabled && currentUser.value?.signInType === 'ldap'; +}); + const isExternalAuthEnabled = computed((): boolean => { - const isLdapEnabled = - ssoStore.isEnterpriseLdapEnabled && currentUser.value?.signInType === 'ldap'; const isSamlEnabled = ssoStore.isSamlLoginEnabled && ssoStore.isDefaultAuthenticationSaml; const isOidcEnabled = ssoStore.isEnterpriseOidcEnabled && ssoStore.isOidcLoginEnabled && currentUser.value?.signInType === 'oidc'; - return isLdapEnabled || isSamlEnabled || isOidcEnabled; + return isLdapCurrentAuthMethod.value || isSamlEnabled || isOidcEnabled; }); const isPersonalSecurityEnabled = computed((): boolean => { @@ -122,6 +124,18 @@ const isMfaFeatureEnabled = computed((): boolean => { return settingsStore.isMfaFeatureEnabled; }); +// Unlike SAML/OIDC, LDAP has no native 2FA, so n8n's own 2FA must stay +// configurable for LDAP users even though password management is external. +const canConfigureMfa = computed((): boolean => { + return ( + isMfaFeatureEnabled.value && (isPersonalSecurityEnabled.value || isLdapCurrentAuthMethod.value) + ); +}); + +const isSecuritySectionVisible = computed((): boolean => { + return !isManagedByEnv.value && (isPersonalSecurityEnabled.value || canConfigureMfa.value); +}); + const hasAnyPersonalisationChanges = computed((): boolean => { return currentSelectedTheme.value !== uiStore.theme; }); @@ -403,18 +417,18 @@ onBeforeUnmount(() => { /> -
+
{{ i18n.baseText('settings.personal.security') }}
-
+
{{ i18n.baseText('auth.changePassword') }}
-
+