fix(editor): Allow LDAP users to configure n8n 2FA in personal settings (#34655)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Guillaume Jacquart
2026-07-22 09:33:13 +00:00
committed by GitHub
co-authored by Claude Opus 4.8
parent cd1e48e5f5
commit 2b1e4b8910
2 changed files with 43 additions and 6 deletions
@@ -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'],
@@ -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(() => {
/>
</div>
</div>
<div v-if="isPersonalSecurityEnabled && !isManagedByEnv">
<div v-if="isSecuritySectionVisible">
<div class="mb-s">
<N8nHeading size="large">{{ i18n.baseText('settings.personal.security') }}</N8nHeading>
</div>
<div class="mb-s">
<div v-if="isPersonalSecurityEnabled" class="mb-s">
<N8nInputLabel :label="i18n.baseText('auth.password')">
<N8nLink data-test-id="change-password-link" @click="openPasswordModal">{{
i18n.baseText('auth.changePassword')
}}</N8nLink>
</N8nInputLabel>
</div>
<div v-if="isMfaFeatureEnabled" data-test-id="mfa-section">
<div v-if="canConfigureMfa" data-test-id="mfa-section">
<div class="mb-xs">
<N8nInputLabel :label="i18n.baseText('settings.personal.mfa.section.title')" />
<N8nText :bold="false" :class="$style.infoText">