fix(editor): Reset OIDC form dirty state after saving IdP settings (#28388)

This commit is contained in:
Csaba Tuncsik 2026-04-14 16:21:49 +02:00 committed by GitHub
parent f54608e6e4
commit 1042350f4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 71 additions and 13 deletions

View file

@ -115,10 +115,10 @@ const cannotSaveOidcSettings = computed(() => {
);
});
async function onOidcSettingsSave(provisioningChangesConfirmed: boolean = false) {
async function onOidcSettingsSave(provisioningChangesConfirmed: boolean = false): Promise<boolean> {
if (!provisioningChangesConfirmed && roleAssignmentTransition.value !== 'none') {
showUserRoleProvisioningDialog.value = true;
return;
return false;
}
const isLoginEnabledChanged = ssoStore.oidcConfig?.loginEnabled !== ssoStore.isOidcLoginEnabled;
@ -140,7 +140,7 @@ async function onOidcSettingsSave(provisioningChangesConfirmed: boolean = false)
),
},
);
if (confirmAction !== MODAL_CONFIRM) return;
if (confirmAction !== MODAL_CONFIRM) return false;
}
const acrArray = authenticationContextClassReference.value
@ -176,12 +176,13 @@ async function onOidcSettingsSave(provisioningChangesConfirmed: boolean = false)
title: i18n.baseText('settings.sso.settings.save.success'),
type: 'success',
});
return true;
} catch (error) {
toast.showError(error, i18n.baseText('settings.sso.settings.save.error_oidc'));
return;
return false;
} finally {
savingForm.value = false;
await getOidcConfig();
savingForm.value = false;
}
}

View file

@ -199,7 +199,7 @@ const prompTestSamlConnectionBeforeActivating = async () => {
return promptOpeningTestConnectionPage;
};
const onSave = async (provisioningChangesConfirmed: boolean = false) => {
const onSave = async (provisioningChangesConfirmed: boolean = false): Promise<boolean> => {
try {
savingForm.value = true;
validateSamlInput();
@ -210,13 +210,13 @@ const onSave = async (provisioningChangesConfirmed: boolean = false) => {
if (isDisablingSamlLogin) {
const confirmDisablingSaml = await promptConfirmDisablingSamlLogin();
if (confirmDisablingSaml !== MODAL_CONFIRM) {
return;
return false;
}
}
if (!provisioningChangesConfirmed && roleAssignmentTransition.value !== 'none') {
showUserRoleProvisioningDialog.value = true;
return;
return false;
}
showUserRoleProvisioningDialog.value = false;
@ -233,7 +233,7 @@ const onSave = async (provisioningChangesConfirmed: boolean = false) => {
const confirmTest = await prompTestSamlConnectionBeforeActivating();
if (confirmTest !== MODAL_CONFIRM) {
return;
return false;
}
}
@ -257,9 +257,10 @@ const onSave = async (provisioningChangesConfirmed: boolean = false) => {
title: i18n.baseText('settings.sso.settings.save.success'),
type: 'success',
});
return true;
} catch (error) {
toast.showError(error, i18n.baseText('settings.sso.settings.save.error'));
return;
return false;
} finally {
savingForm.value = false;
}

View file

@ -139,6 +139,7 @@ export const useSSOStore = defineStore('sso', () => {
const getOidcConfig = async () => {
const config = await ssoApi.getOidcConfig(rootStore.restApiContext);
oidcConfig.value = config;
oidc.value.loginEnabled = config.loginEnabled;
return config;
};

View file

@ -1,6 +1,10 @@
import type { OidcConfigDto } from '@n8n/api-types';
import { createPinia, setActivePinia } from 'pinia';
import { useSSOStore, SupportedProtocols } from '@/features/settings/sso/sso.store';
import type { UserManagementAuthenticationMethod } from '@/Interface';
import * as ssoApi from '@n8n/rest-api-client/api/sso';
vi.mock('@n8n/rest-api-client/api/sso');
let ssoStore: ReturnType<typeof useSSOStore>;
@ -150,4 +154,53 @@ describe('SSO store', () => {
expect(ssoStore.selectedAuthProtocol).toBe(SupportedProtocols.SAML);
});
});
describe('getOidcConfig', () => {
it('should sync oidc.loginEnabled when fetching config', async () => {
const oidcConfig: OidcConfigDto = {
clientId: 'test-id',
clientSecret: 'test-secret',
discoveryEndpoint: 'https://example.com/.well-known/openid-configuration',
loginEnabled: true,
prompt: 'select_account',
authenticationContextClassReference: [],
};
vi.mocked(ssoApi.getOidcConfig).mockResolvedValue(oidcConfig);
// loginEnabled starts as false (default)
expect(ssoStore.isOidcLoginEnabled).toBe(false);
await ssoStore.getOidcConfig();
// After fetching config, loginEnabled should be synced
expect(ssoStore.isOidcLoginEnabled).toBe(true);
expect(ssoStore.oidcConfig).toEqual(oidcConfig);
});
it('should reset oidc.loginEnabled to false when server config has it disabled', async () => {
// Start with loginEnabled = true via initialize
ssoStore.initialize({
authenticationMethod: 'oidc' as UserManagementAuthenticationMethod,
config: { oidc: { loginEnabled: true } },
features: { saml: false, ldap: false, oidc: true },
});
expect(ssoStore.isOidcLoginEnabled).toBe(true);
// Server returns config with loginEnabled = false
vi.mocked(ssoApi.getOidcConfig).mockResolvedValue({
clientId: 'test-id',
clientSecret: 'test-secret',
discoveryEndpoint: 'https://example.com/.well-known/openid-configuration',
loginEnabled: false,
prompt: 'select_account',
authenticationContextClassReference: [],
});
await ssoStore.getOidcConfig();
// loginEnabled should now be false, matching server state
expect(ssoStore.isOidcLoginEnabled).toBe(false);
});
});
});

View file

@ -77,9 +77,11 @@ onBeforeRouteLeave((_to, _from, next) => {
async function onSaveAndLeave() {
showUnsavedChangesDialog.value = false;
await activeForm.value?.onSave();
pendingNext.value?.();
pendingNext.value = null;
const saved = await activeForm.value?.onSave();
if (saved) {
pendingNext.value?.();
pendingNext.value = null;
}
}
function onLeaveWithoutSaving() {