Skip to content

Commit c22ca2c

Browse files
authored
refactor(editor): Extract SAML, OIDC, and LDAP from Settings Store into SSO Store (no-changelog) (#16276)
1 parent e93fd1a commit c22ca2c

17 files changed

+330
-247
lines changed

packages/frontend/editor-ui/src/components/SSOLogin.vue

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,21 @@
22
import { useSSOStore } from '@/stores/sso.store';
33
import { useI18n } from '@n8n/i18n';
44
import { useToast } from '@/composables/useToast';
5-
import { useSettingsStore } from '@/stores/settings.store';
5+
import { useRoute } from 'vue-router';
66
77
const i18n = useI18n();
88
const ssoStore = useSSOStore();
99
const toast = useToast();
10-
const settingsStore = useSettingsStore();
10+
const route = useRoute();
1111
1212
const onSSOLogin = async () => {
1313
try {
1414
const redirectUrl = ssoStore.isDefaultAuthenticationSaml
15-
? await ssoStore.getSSORedirectUrl()
16-
: settingsStore.settings.sso.oidc.loginUrl;
17-
window.location.href = redirectUrl;
15+
? await ssoStore.getSSORedirectUrl(
16+
typeof route.query?.redirect === 'string' ? route.query.redirect : '',
17+
)
18+
: ssoStore.oidc.loginUrl;
19+
window.location.href = redirectUrl ?? '';
1820
} catch (error) {
1921
toast.showError(error, 'Error', error.message);
2022
}

packages/frontend/editor-ui/src/init.test.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ import { useCloudPlanStore } from '@/stores/cloudPlan.store';
33
import { useSourceControlStore } from '@/stores/sourceControl.store';
44
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
55
import { useRootStore } from '@n8n/stores/useRootStore';
6-
import { initializeAuthenticatedFeatures, initializeCore } from '@/init';
6+
import { state, initializeAuthenticatedFeatures, initializeCore } from '@/init';
77
import { createTestingPinia } from '@pinia/testing';
88
import { setActivePinia } from 'pinia';
99
import { useSettingsStore } from '@/stores/settings.store';
1010
import { useVersionsStore } from '@/stores/versions.store';
1111
import { AxiosError } from 'axios';
12+
import merge from 'lodash/merge';
13+
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
14+
import { STORES } from '@n8n/stores';
15+
import { useSSOStore } from '@/stores/sso.store';
16+
import { UserManagementAuthenticationMethod } from '@/Interface';
17+
import { EnterpriseEditionFeature } from '@/constants';
1218

1319
const showMessage = vi.fn();
1420

@@ -31,19 +37,32 @@ describe('Init', () => {
3137
let usersStore: ReturnType<typeof useUsersStore>;
3238
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
3339
let versionsStore: ReturnType<typeof useVersionsStore>;
40+
let ssoStore: ReturnType<typeof useSSOStore>;
3441

3542
beforeEach(() => {
36-
setActivePinia(createTestingPinia());
43+
setActivePinia(
44+
createTestingPinia({
45+
initialState: {
46+
[STORES.SETTINGS]: merge({}, SETTINGS_STORE_DEFAULT_STATE),
47+
},
48+
}),
49+
);
50+
3751
settingsStore = useSettingsStore();
3852
cloudPlanStore = useCloudPlanStore();
3953
sourceControlStore = useSourceControlStore();
4054
nodeTypesStore = useNodeTypesStore();
4155
usersStore = useUsersStore();
4256
versionsStore = useVersionsStore();
4357
versionsStore = useVersionsStore();
58+
ssoStore = useSSOStore();
4459
});
4560

4661
describe('initializeCore()', () => {
62+
beforeEach(() => {
63+
state.initialized = false;
64+
});
65+
4766
afterEach(() => {
4867
vi.clearAllMocks();
4968
});
@@ -63,6 +82,28 @@ describe('Init', () => {
6382

6483
expect(settingsStoreSpy).toHaveBeenCalledTimes(1);
6584
});
85+
86+
it('should initialize ssoStore with settings SSO configuration', async () => {
87+
const saml = { loginEnabled: true, loginLabel: '' };
88+
const ldap = { loginEnabled: false, loginLabel: '' };
89+
const oidc = { loginEnabled: false, loginUrl: '', callbackUrl: '' };
90+
91+
settingsStore.userManagement.authenticationMethod = UserManagementAuthenticationMethod.Saml;
92+
settingsStore.settings.sso = { saml, ldap, oidc };
93+
settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Saml] = true;
94+
95+
await initializeCore();
96+
97+
expect(ssoStore.initialize).toHaveBeenCalledWith({
98+
authenticationMethod: UserManagementAuthenticationMethod.Saml,
99+
config: { saml, ldap, oidc },
100+
features: {
101+
saml: true,
102+
ldap: false,
103+
oidc: false,
104+
},
105+
});
106+
});
66107
});
67108

68109
describe('initializeAuthenticatedFeatures()', () => {

packages/frontend/editor-ui/src/init.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,42 @@ import { useInsightsStore } from '@/features/insights/insights.store';
1313
import { useToast } from '@/composables/useToast';
1414
import { useI18n } from '@n8n/i18n';
1515
import SourceControlInitializationErrorMessage from '@/components/SourceControlInitializationErrorMessage.vue';
16+
import { useSSOStore } from '@/stores/sso.store';
17+
import { EnterpriseEditionFeature } from '@/constants';
18+
import type { UserManagementAuthenticationMethod } from '@/Interface';
1619

17-
let coreInitialized = false;
20+
export const state = {
21+
initialized: false,
22+
};
1823
let authenticatedFeaturesInitialized = false;
1924

2025
/**
2126
* Initializes the core application stores and hooks
2227
* This is called once, when the first route is loaded.
2328
*/
2429
export async function initializeCore() {
25-
if (coreInitialized) {
30+
if (state.initialized) {
2631
return;
2732
}
2833

2934
const settingsStore = useSettingsStore();
3035
const usersStore = useUsersStore();
3136
const versionsStore = useVersionsStore();
37+
const ssoStore = useSSOStore();
3238

3339
await settingsStore.initialize();
3440

41+
ssoStore.initialize({
42+
authenticationMethod: settingsStore.userManagement
43+
.authenticationMethod as UserManagementAuthenticationMethod,
44+
config: settingsStore.settings.sso,
45+
features: {
46+
saml: settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Saml],
47+
ldap: settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Ldap],
48+
oidc: settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Oidc],
49+
},
50+
});
51+
3552
void useExternalHooks().run('app.mount');
3653

3754
if (!settingsStore.isPreviewMode) {
@@ -40,7 +57,7 @@ export async function initializeCore() {
4057
void versionsStore.checkForNewVersions();
4158
}
4259

43-
coreInitialized = true;
60+
state.initialized = true;
4461
}
4562

4663
/**

packages/frontend/editor-ui/src/stores/settings.store.ts

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ import Bowser from 'bowser';
33
import type { IUserManagementSettings, FrontendSettings } from '@n8n/api-types';
44

55
import * as eventsApi from '@n8n/rest-api-client/api/events';
6-
import * as ldapApi from '@n8n/rest-api-client/api/ldap';
76
import * as settingsApi from '@n8n/rest-api-client/api/settings';
87
import * as promptsApi from '@n8n/rest-api-client/api/prompts';
98
import { testHealthEndpoint } from '@/api/templates';
10-
import type { LdapConfig } from '@n8n/rest-api-client/api/ldap';
119
import {
1210
INSECURE_CONNECTION_WARNING,
1311
LOCAL_STORAGE_EXPERIMENTAL_MIN_ZOOM_NODE_SETTINGS_IN_CANVAS,
@@ -44,9 +42,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
4442
enabled: false,
4543
},
4644
});
47-
const ldap = ref({ loginLabel: '', loginEnabled: false });
48-
const saml = ref({ loginLabel: '', loginEnabled: false });
49-
const oidc = ref({ loginEnabled: false, loginUrl: '', callbackUrl: '' });
5045
const mfa = ref({ enabled: false });
5146
const folders = ref({ enabled: false });
5247

@@ -90,16 +85,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
9085

9186
const publicApiPath = computed(() => api.value.path);
9287

93-
const isLdapLoginEnabled = computed(() => ldap.value.loginEnabled);
94-
95-
const ldapLoginLabel = computed(() => ldap.value.loginLabel);
96-
97-
const isSamlLoginEnabled = computed(() => saml.value.loginEnabled);
98-
99-
const isOidcLoginEnabled = computed(() => oidc.value.loginEnabled);
100-
101-
const oidcCallBackUrl = computed(() => oidc.value.callbackUrl);
102-
10388
const isAiAssistantEnabled = computed(() => settings.value.aiAssistant?.enabled);
10489

10590
const isAskAiEnabled = computed(() => settings.value.askAi?.enabled);
@@ -182,14 +167,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
182167
() => settings.value.workflowCallerPolicyDefaultOption,
183168
);
184169

185-
const isDefaultAuthenticationSaml = computed(
186-
() => userManagement.value.authenticationMethod === UserManagementAuthenticationMethod.Saml,
187-
);
188-
189-
const isDefaultAuthenticationOidc = computed(
190-
() => userManagement.value.authenticationMethod === UserManagementAuthenticationMethod.Oidc,
191-
);
192-
193170
const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []);
194171

195172
const isBelowUserQuota = computed(
@@ -210,21 +187,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
210187
!!settings.value.userManagement.showSetupOnFirstLoad;
211188
}
212189
api.value = settings.value.publicApi;
213-
if (settings.value.sso?.ldap) {
214-
ldap.value.loginEnabled = settings.value.sso.ldap.loginEnabled;
215-
ldap.value.loginLabel = settings.value.sso.ldap.loginLabel;
216-
}
217-
if (settings.value.sso?.saml) {
218-
saml.value.loginEnabled = settings.value.sso.saml.loginEnabled;
219-
saml.value.loginLabel = settings.value.sso.saml.loginLabel;
220-
}
221-
222-
if (settings.value.sso?.oidc) {
223-
oidc.value.loginEnabled = settings.value.sso.oidc.loginEnabled;
224-
oidc.value.loginUrl = settings.value.sso.oidc.loginUrl || '';
225-
oidc.value.callbackUrl = settings.value.sso.oidc.callbackUrl || '';
226-
}
227-
228190
mfa.value.enabled = settings.value.mfa?.enabled;
229191
folders.value.enabled = settings.value.folders?.enabled;
230192

@@ -364,31 +326,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
364326
templatesEndpointHealthy.value = true;
365327
};
366328

367-
const getLdapConfig = async () => {
368-
const rootStore = useRootStore();
369-
return await ldapApi.getLdapConfig(rootStore.restApiContext);
370-
};
371-
372-
const getLdapSynchronizations = async (pagination: { page: number }) => {
373-
const rootStore = useRootStore();
374-
return await ldapApi.getLdapSynchronizations(rootStore.restApiContext, pagination);
375-
};
376-
377-
const testLdapConnection = async () => {
378-
const rootStore = useRootStore();
379-
return await ldapApi.testLdapConnection(rootStore.restApiContext);
380-
};
381-
382-
const updateLdapConfig = async (ldapConfig: LdapConfig) => {
383-
const rootStore = useRootStore();
384-
return await ldapApi.updateLdapConfig(rootStore.restApiContext, ldapConfig);
385-
};
386-
387-
const runLdapSync = async (data: IDataObject) => {
388-
const rootStore = useRootStore();
389-
return await ldapApi.runLdapSync(rootStore.restApiContext, data);
390-
};
391-
392329
const getTimezones = async (): Promise<IDataObject> => {
393330
const rootStore = useRootStore();
394331
return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/options/timezones');
@@ -412,8 +349,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
412349
userManagement,
413350
templatesEndpointHealthy,
414351
api,
415-
ldap,
416-
saml,
417352
mfa,
418353
isDocker,
419354
isDevRelease,
@@ -432,10 +367,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
432367
isPreviewMode,
433368
publicApiLatestVersion,
434369
publicApiPath,
435-
isLdapLoginEnabled,
436-
ldapLoginLabel,
437-
isSamlLoginEnabled,
438-
isOidcLoginEnabled,
439370
showSetupPage,
440371
deploymentType,
441372
isCloudDeployment,
@@ -459,8 +390,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
459390
isQueueModeEnabled,
460391
isMultiMain,
461392
isWorkerViewAvailable,
462-
isDefaultAuthenticationSaml,
463-
isDefaultAuthenticationOidc,
464393
workflowCallerPolicyDefaultOption,
465394
permanentlyDismissedBanners,
466395
isBelowUserQuota,
@@ -474,13 +403,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
474403
aiCreditsQuota,
475404
experimental__minZoomNodeSettingsInCanvas,
476405
partialExecutionVersion,
477-
oidcCallBackUrl,
478406
reset,
479-
testLdapConnection,
480-
getLdapConfig,
481-
getLdapSynchronizations,
482-
updateLdapConfig,
483-
runLdapSync,
484407
getTimezones,
485408
testTemplatesEndpoint,
486409
submitContactInfo,

0 commit comments

Comments
 (0)