mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 03:12:15 +00:00
refactor(editor): Extract SAML, OIDC, and LDAP from Settings Store into SSO Store (no-changelog) (#16276)
This commit is contained in:
@@ -2,19 +2,21 @@
|
|||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
import { useI18n } from '@n8n/i18n';
|
import { useI18n } from '@n8n/i18n';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const ssoStore = useSSOStore();
|
const ssoStore = useSSOStore();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const settingsStore = useSettingsStore();
|
const route = useRoute();
|
||||||
|
|
||||||
const onSSOLogin = async () => {
|
const onSSOLogin = async () => {
|
||||||
try {
|
try {
|
||||||
const redirectUrl = ssoStore.isDefaultAuthenticationSaml
|
const redirectUrl = ssoStore.isDefaultAuthenticationSaml
|
||||||
? await ssoStore.getSSORedirectUrl()
|
? await ssoStore.getSSORedirectUrl(
|
||||||
: settingsStore.settings.sso.oidc.loginUrl;
|
typeof route.query?.redirect === 'string' ? route.query.redirect : '',
|
||||||
window.location.href = redirectUrl;
|
)
|
||||||
|
: ssoStore.oidc.loginUrl;
|
||||||
|
window.location.href = redirectUrl ?? '';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.showError(error, 'Error', error.message);
|
toast.showError(error, 'Error', error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,18 @@ import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
|||||||
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
import { useSourceControlStore } from '@/stores/sourceControl.store';
|
||||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||||
import { initializeAuthenticatedFeatures, initializeCore } from '@/init';
|
import { state, initializeAuthenticatedFeatures, initializeCore } from '@/init';
|
||||||
import { createTestingPinia } from '@pinia/testing';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import { setActivePinia } from 'pinia';
|
import { setActivePinia } from 'pinia';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useVersionsStore } from '@/stores/versions.store';
|
import { useVersionsStore } from '@/stores/versions.store';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
|
import merge from 'lodash/merge';
|
||||||
|
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
|
||||||
|
import { STORES } from '@n8n/stores';
|
||||||
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
|
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
|
import { EnterpriseEditionFeature } from '@/constants';
|
||||||
|
|
||||||
const showMessage = vi.fn();
|
const showMessage = vi.fn();
|
||||||
|
|
||||||
@@ -31,9 +37,17 @@ describe('Init', () => {
|
|||||||
let usersStore: ReturnType<typeof useUsersStore>;
|
let usersStore: ReturnType<typeof useUsersStore>;
|
||||||
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
|
let nodeTypesStore: ReturnType<typeof useNodeTypesStore>;
|
||||||
let versionsStore: ReturnType<typeof useVersionsStore>;
|
let versionsStore: ReturnType<typeof useVersionsStore>;
|
||||||
|
let ssoStore: ReturnType<typeof useSSOStore>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createTestingPinia());
|
setActivePinia(
|
||||||
|
createTestingPinia({
|
||||||
|
initialState: {
|
||||||
|
[STORES.SETTINGS]: merge({}, SETTINGS_STORE_DEFAULT_STATE),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
settingsStore = useSettingsStore();
|
settingsStore = useSettingsStore();
|
||||||
cloudPlanStore = useCloudPlanStore();
|
cloudPlanStore = useCloudPlanStore();
|
||||||
sourceControlStore = useSourceControlStore();
|
sourceControlStore = useSourceControlStore();
|
||||||
@@ -41,9 +55,14 @@ describe('Init', () => {
|
|||||||
usersStore = useUsersStore();
|
usersStore = useUsersStore();
|
||||||
versionsStore = useVersionsStore();
|
versionsStore = useVersionsStore();
|
||||||
versionsStore = useVersionsStore();
|
versionsStore = useVersionsStore();
|
||||||
|
ssoStore = useSSOStore();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('initializeCore()', () => {
|
describe('initializeCore()', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
state.initialized = false;
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
@@ -63,6 +82,28 @@ describe('Init', () => {
|
|||||||
|
|
||||||
expect(settingsStoreSpy).toHaveBeenCalledTimes(1);
|
expect(settingsStoreSpy).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should initialize ssoStore with settings SSO configuration', async () => {
|
||||||
|
const saml = { loginEnabled: true, loginLabel: '' };
|
||||||
|
const ldap = { loginEnabled: false, loginLabel: '' };
|
||||||
|
const oidc = { loginEnabled: false, loginUrl: '', callbackUrl: '' };
|
||||||
|
|
||||||
|
settingsStore.userManagement.authenticationMethod = UserManagementAuthenticationMethod.Saml;
|
||||||
|
settingsStore.settings.sso = { saml, ldap, oidc };
|
||||||
|
settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Saml] = true;
|
||||||
|
|
||||||
|
await initializeCore();
|
||||||
|
|
||||||
|
expect(ssoStore.initialize).toHaveBeenCalledWith({
|
||||||
|
authenticationMethod: UserManagementAuthenticationMethod.Saml,
|
||||||
|
config: { saml, ldap, oidc },
|
||||||
|
features: {
|
||||||
|
saml: true,
|
||||||
|
ldap: false,
|
||||||
|
oidc: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('initializeAuthenticatedFeatures()', () => {
|
describe('initializeAuthenticatedFeatures()', () => {
|
||||||
|
|||||||
@@ -13,8 +13,13 @@ import { useInsightsStore } from '@/features/insights/insights.store';
|
|||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useI18n } from '@n8n/i18n';
|
import { useI18n } from '@n8n/i18n';
|
||||||
import SourceControlInitializationErrorMessage from '@/components/SourceControlInitializationErrorMessage.vue';
|
import SourceControlInitializationErrorMessage from '@/components/SourceControlInitializationErrorMessage.vue';
|
||||||
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
|
import { EnterpriseEditionFeature } from '@/constants';
|
||||||
|
import type { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
|
|
||||||
let coreInitialized = false;
|
export const state = {
|
||||||
|
initialized: false,
|
||||||
|
};
|
||||||
let authenticatedFeaturesInitialized = false;
|
let authenticatedFeaturesInitialized = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,16 +27,28 @@ let authenticatedFeaturesInitialized = false;
|
|||||||
* This is called once, when the first route is loaded.
|
* This is called once, when the first route is loaded.
|
||||||
*/
|
*/
|
||||||
export async function initializeCore() {
|
export async function initializeCore() {
|
||||||
if (coreInitialized) {
|
if (state.initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const versionsStore = useVersionsStore();
|
const versionsStore = useVersionsStore();
|
||||||
|
const ssoStore = useSSOStore();
|
||||||
|
|
||||||
await settingsStore.initialize();
|
await settingsStore.initialize();
|
||||||
|
|
||||||
|
ssoStore.initialize({
|
||||||
|
authenticationMethod: settingsStore.userManagement
|
||||||
|
.authenticationMethod as UserManagementAuthenticationMethod,
|
||||||
|
config: settingsStore.settings.sso,
|
||||||
|
features: {
|
||||||
|
saml: settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Saml],
|
||||||
|
ldap: settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Ldap],
|
||||||
|
oidc: settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Oidc],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
void useExternalHooks().run('app.mount');
|
void useExternalHooks().run('app.mount');
|
||||||
|
|
||||||
if (!settingsStore.isPreviewMode) {
|
if (!settingsStore.isPreviewMode) {
|
||||||
@@ -40,7 +57,7 @@ export async function initializeCore() {
|
|||||||
void versionsStore.checkForNewVersions();
|
void versionsStore.checkForNewVersions();
|
||||||
}
|
}
|
||||||
|
|
||||||
coreInitialized = true;
|
state.initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import Bowser from 'bowser';
|
|||||||
import type { IUserManagementSettings, FrontendSettings } from '@n8n/api-types';
|
import type { IUserManagementSettings, FrontendSettings } from '@n8n/api-types';
|
||||||
|
|
||||||
import * as eventsApi from '@n8n/rest-api-client/api/events';
|
import * as eventsApi from '@n8n/rest-api-client/api/events';
|
||||||
import * as ldapApi from '@n8n/rest-api-client/api/ldap';
|
|
||||||
import * as settingsApi from '@n8n/rest-api-client/api/settings';
|
import * as settingsApi from '@n8n/rest-api-client/api/settings';
|
||||||
import * as promptsApi from '@n8n/rest-api-client/api/prompts';
|
import * as promptsApi from '@n8n/rest-api-client/api/prompts';
|
||||||
import { testHealthEndpoint } from '@/api/templates';
|
import { testHealthEndpoint } from '@/api/templates';
|
||||||
import type { LdapConfig } from '@n8n/rest-api-client/api/ldap';
|
|
||||||
import {
|
import {
|
||||||
INSECURE_CONNECTION_WARNING,
|
INSECURE_CONNECTION_WARNING,
|
||||||
LOCAL_STORAGE_EXPERIMENTAL_MIN_ZOOM_NODE_SETTINGS_IN_CANVAS,
|
LOCAL_STORAGE_EXPERIMENTAL_MIN_ZOOM_NODE_SETTINGS_IN_CANVAS,
|
||||||
@@ -44,9 +42,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const ldap = ref({ loginLabel: '', loginEnabled: false });
|
|
||||||
const saml = ref({ loginLabel: '', loginEnabled: false });
|
|
||||||
const oidc = ref({ loginEnabled: false, loginUrl: '', callbackUrl: '' });
|
|
||||||
const mfa = ref({ enabled: false });
|
const mfa = ref({ enabled: false });
|
||||||
const folders = ref({ enabled: false });
|
const folders = ref({ enabled: false });
|
||||||
|
|
||||||
@@ -90,16 +85,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
|
|
||||||
const publicApiPath = computed(() => api.value.path);
|
const publicApiPath = computed(() => api.value.path);
|
||||||
|
|
||||||
const isLdapLoginEnabled = computed(() => ldap.value.loginEnabled);
|
|
||||||
|
|
||||||
const ldapLoginLabel = computed(() => ldap.value.loginLabel);
|
|
||||||
|
|
||||||
const isSamlLoginEnabled = computed(() => saml.value.loginEnabled);
|
|
||||||
|
|
||||||
const isOidcLoginEnabled = computed(() => oidc.value.loginEnabled);
|
|
||||||
|
|
||||||
const oidcCallBackUrl = computed(() => oidc.value.callbackUrl);
|
|
||||||
|
|
||||||
const isAiAssistantEnabled = computed(() => settings.value.aiAssistant?.enabled);
|
const isAiAssistantEnabled = computed(() => settings.value.aiAssistant?.enabled);
|
||||||
|
|
||||||
const isAskAiEnabled = computed(() => settings.value.askAi?.enabled);
|
const isAskAiEnabled = computed(() => settings.value.askAi?.enabled);
|
||||||
@@ -182,14 +167,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
() => settings.value.workflowCallerPolicyDefaultOption,
|
() => settings.value.workflowCallerPolicyDefaultOption,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isDefaultAuthenticationSaml = computed(
|
|
||||||
() => userManagement.value.authenticationMethod === UserManagementAuthenticationMethod.Saml,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isDefaultAuthenticationOidc = computed(
|
|
||||||
() => userManagement.value.authenticationMethod === UserManagementAuthenticationMethod.Oidc,
|
|
||||||
);
|
|
||||||
|
|
||||||
const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []);
|
const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []);
|
||||||
|
|
||||||
const isBelowUserQuota = computed(
|
const isBelowUserQuota = computed(
|
||||||
@@ -210,21 +187,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
!!settings.value.userManagement.showSetupOnFirstLoad;
|
!!settings.value.userManagement.showSetupOnFirstLoad;
|
||||||
}
|
}
|
||||||
api.value = settings.value.publicApi;
|
api.value = settings.value.publicApi;
|
||||||
if (settings.value.sso?.ldap) {
|
|
||||||
ldap.value.loginEnabled = settings.value.sso.ldap.loginEnabled;
|
|
||||||
ldap.value.loginLabel = settings.value.sso.ldap.loginLabel;
|
|
||||||
}
|
|
||||||
if (settings.value.sso?.saml) {
|
|
||||||
saml.value.loginEnabled = settings.value.sso.saml.loginEnabled;
|
|
||||||
saml.value.loginLabel = settings.value.sso.saml.loginLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.value.sso?.oidc) {
|
|
||||||
oidc.value.loginEnabled = settings.value.sso.oidc.loginEnabled;
|
|
||||||
oidc.value.loginUrl = settings.value.sso.oidc.loginUrl || '';
|
|
||||||
oidc.value.callbackUrl = settings.value.sso.oidc.callbackUrl || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
mfa.value.enabled = settings.value.mfa?.enabled;
|
mfa.value.enabled = settings.value.mfa?.enabled;
|
||||||
folders.value.enabled = settings.value.folders?.enabled;
|
folders.value.enabled = settings.value.folders?.enabled;
|
||||||
|
|
||||||
@@ -364,31 +326,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
templatesEndpointHealthy.value = true;
|
templatesEndpointHealthy.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLdapConfig = async () => {
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
return await ldapApi.getLdapConfig(rootStore.restApiContext);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getLdapSynchronizations = async (pagination: { page: number }) => {
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
return await ldapApi.getLdapSynchronizations(rootStore.restApiContext, pagination);
|
|
||||||
};
|
|
||||||
|
|
||||||
const testLdapConnection = async () => {
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
return await ldapApi.testLdapConnection(rootStore.restApiContext);
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateLdapConfig = async (ldapConfig: LdapConfig) => {
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
return await ldapApi.updateLdapConfig(rootStore.restApiContext, ldapConfig);
|
|
||||||
};
|
|
||||||
|
|
||||||
const runLdapSync = async (data: IDataObject) => {
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
return await ldapApi.runLdapSync(rootStore.restApiContext, data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTimezones = async (): Promise<IDataObject> => {
|
const getTimezones = async (): Promise<IDataObject> => {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/options/timezones');
|
return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/options/timezones');
|
||||||
@@ -412,8 +349,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
userManagement,
|
userManagement,
|
||||||
templatesEndpointHealthy,
|
templatesEndpointHealthy,
|
||||||
api,
|
api,
|
||||||
ldap,
|
|
||||||
saml,
|
|
||||||
mfa,
|
mfa,
|
||||||
isDocker,
|
isDocker,
|
||||||
isDevRelease,
|
isDevRelease,
|
||||||
@@ -432,10 +367,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
isPreviewMode,
|
isPreviewMode,
|
||||||
publicApiLatestVersion,
|
publicApiLatestVersion,
|
||||||
publicApiPath,
|
publicApiPath,
|
||||||
isLdapLoginEnabled,
|
|
||||||
ldapLoginLabel,
|
|
||||||
isSamlLoginEnabled,
|
|
||||||
isOidcLoginEnabled,
|
|
||||||
showSetupPage,
|
showSetupPage,
|
||||||
deploymentType,
|
deploymentType,
|
||||||
isCloudDeployment,
|
isCloudDeployment,
|
||||||
@@ -459,8 +390,6 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
isQueueModeEnabled,
|
isQueueModeEnabled,
|
||||||
isMultiMain,
|
isMultiMain,
|
||||||
isWorkerViewAvailable,
|
isWorkerViewAvailable,
|
||||||
isDefaultAuthenticationSaml,
|
|
||||||
isDefaultAuthenticationOidc,
|
|
||||||
workflowCallerPolicyDefaultOption,
|
workflowCallerPolicyDefaultOption,
|
||||||
permanentlyDismissedBanners,
|
permanentlyDismissedBanners,
|
||||||
isBelowUserQuota,
|
isBelowUserQuota,
|
||||||
@@ -474,13 +403,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||||||
aiCreditsQuota,
|
aiCreditsQuota,
|
||||||
experimental__minZoomNodeSettingsInCanvas,
|
experimental__minZoomNodeSettingsInCanvas,
|
||||||
partialExecutionVersion,
|
partialExecutionVersion,
|
||||||
oidcCallBackUrl,
|
|
||||||
reset,
|
reset,
|
||||||
testLdapConnection,
|
|
||||||
getLdapConfig,
|
|
||||||
getLdapSynchronizations,
|
|
||||||
updateLdapConfig,
|
|
||||||
runLdapSync,
|
|
||||||
getTimezones,
|
getTimezones,
|
||||||
testTemplatesEndpoint,
|
testTemplatesEndpoint,
|
||||||
submitContactInfo,
|
submitContactInfo,
|
||||||
|
|||||||
@@ -1,75 +1,19 @@
|
|||||||
import type { OidcConfigDto } from '@n8n/api-types';
|
import type { OidcConfigDto } from '@n8n/api-types';
|
||||||
import { type SamlPreferences } from '@n8n/api-types';
|
import { type SamlPreferences } from '@n8n/api-types';
|
||||||
import { computed, reactive } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { EnterpriseEditionFeature } from '@/constants';
|
|
||||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
import * as ssoApi from '@n8n/rest-api-client/api/sso';
|
import * as ssoApi from '@n8n/rest-api-client/api/sso';
|
||||||
import type { SamlPreferencesExtractedData } from '@n8n/rest-api-client/api/sso';
|
import type { SamlPreferencesExtractedData } from '@n8n/rest-api-client/api/sso';
|
||||||
import { updateCurrentUser } from '@/api/users';
|
import * as ldapApi from '@n8n/rest-api-client/api/ldap';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import type { LdapConfig } from '@n8n/rest-api-client/api/ldap';
|
||||||
|
import type { IDataObject } from 'n8n-workflow';
|
||||||
|
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
|
|
||||||
export const useSSOStore = defineStore('sso', () => {
|
export const useSSOStore = defineStore('sso', () => {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const usersStore = useUsersStore();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const state = reactive({
|
const authenticationMethod = ref<UserManagementAuthenticationMethod | undefined>(undefined);
|
||||||
samlConfig: undefined as (SamlPreferences & SamlPreferencesExtractedData) | undefined,
|
|
||||||
oidcConfig: undefined as OidcConfigDto | undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const samlConfig = computed(() => state.samlConfig);
|
|
||||||
|
|
||||||
const oidcConfig = computed(() => state.oidcConfig);
|
|
||||||
|
|
||||||
const isSamlLoginEnabled = computed({
|
|
||||||
get: () => settingsStore.isSamlLoginEnabled,
|
|
||||||
set: (value: boolean) => {
|
|
||||||
settingsStore.setSettings({
|
|
||||||
...settingsStore.settings,
|
|
||||||
sso: {
|
|
||||||
...settingsStore.settings.sso,
|
|
||||||
saml: {
|
|
||||||
...settingsStore.settings.sso.saml,
|
|
||||||
loginEnabled: value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
void toggleLoginEnabled(value);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const isOidcLoginEnabled = computed({
|
|
||||||
get: () => settingsStore.isOidcLoginEnabled,
|
|
||||||
set: (value: boolean) => {
|
|
||||||
settingsStore.setSettings({
|
|
||||||
...settingsStore.settings,
|
|
||||||
sso: {
|
|
||||||
...settingsStore.settings.sso,
|
|
||||||
oidc: {
|
|
||||||
...settingsStore.settings.sso.oidc,
|
|
||||||
loginEnabled: value,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const isEnterpriseSamlEnabled = computed(
|
|
||||||
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Saml],
|
|
||||||
);
|
|
||||||
|
|
||||||
const isEnterpriseOidcEnabled = computed(
|
|
||||||
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Oidc],
|
|
||||||
);
|
|
||||||
|
|
||||||
const isDefaultAuthenticationSaml = computed(() => settingsStore.isDefaultAuthenticationSaml);
|
|
||||||
|
|
||||||
const isDefaultAuthenticationOidc = computed(() => settingsStore.isDefaultAuthenticationOidc);
|
|
||||||
|
|
||||||
const showSsoLoginButton = computed(
|
const showSsoLoginButton = computed(
|
||||||
() =>
|
() =>
|
||||||
@@ -81,10 +25,70 @@ export const useSSOStore = defineStore('sso', () => {
|
|||||||
isDefaultAuthenticationOidc.value),
|
isDefaultAuthenticationOidc.value),
|
||||||
);
|
);
|
||||||
|
|
||||||
const getSSORedirectUrl = async () =>
|
const getSSORedirectUrl = async (existingRedirect?: string) =>
|
||||||
await ssoApi.initSSO(
|
await ssoApi.initSSO(rootStore.restApiContext, existingRedirect);
|
||||||
rootStore.restApiContext,
|
|
||||||
typeof route.query?.redirect === 'string' ? route.query.redirect : '',
|
const initialize = (options: {
|
||||||
|
authenticationMethod: UserManagementAuthenticationMethod;
|
||||||
|
config: {
|
||||||
|
ldap?: Pick<LdapConfig, 'loginLabel' | 'loginEnabled'>;
|
||||||
|
saml?: Pick<SamlPreferences, 'loginLabel' | 'loginEnabled'>;
|
||||||
|
oidc?: Pick<OidcConfigDto, 'loginEnabled'> & {
|
||||||
|
loginUrl?: string;
|
||||||
|
callbackUrl?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
features: {
|
||||||
|
saml: boolean;
|
||||||
|
ldap: boolean;
|
||||||
|
oidc: boolean;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
|
authenticationMethod.value = options.authenticationMethod;
|
||||||
|
|
||||||
|
isEnterpriseLdapEnabled.value = options.features.ldap;
|
||||||
|
if (options.config.ldap) {
|
||||||
|
ldap.value.loginEnabled = options.config.ldap.loginEnabled;
|
||||||
|
ldap.value.loginLabel = options.config.ldap.loginLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnterpriseSamlEnabled.value = options.features.saml;
|
||||||
|
if (options.config.saml) {
|
||||||
|
saml.value.loginEnabled = options.config.saml.loginEnabled;
|
||||||
|
saml.value.loginLabel = options.config.saml.loginLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
isEnterpriseOidcEnabled.value = options.features.oidc;
|
||||||
|
if (options.config.oidc) {
|
||||||
|
oidc.value.loginEnabled = options.config.oidc.loginEnabled;
|
||||||
|
oidc.value.loginUrl = options.config.oidc.loginUrl || '';
|
||||||
|
oidc.value.callbackUrl = options.config.oidc.callbackUrl || '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SAML
|
||||||
|
*/
|
||||||
|
|
||||||
|
const saml = ref<Pick<SamlPreferences, 'loginLabel' | 'loginEnabled'>>({
|
||||||
|
loginLabel: '',
|
||||||
|
loginEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const samlConfig = ref<SamlPreferences & SamlPreferencesExtractedData>();
|
||||||
|
|
||||||
|
const isSamlLoginEnabled = computed({
|
||||||
|
get: () => saml.value.loginEnabled,
|
||||||
|
set: (value: boolean) => {
|
||||||
|
saml.value.loginEnabled = value;
|
||||||
|
void toggleLoginEnabled(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEnterpriseSamlEnabled = ref(false);
|
||||||
|
|
||||||
|
const isDefaultAuthenticationSaml = computed(
|
||||||
|
() => authenticationMethod.value === UserManagementAuthenticationMethod.Saml,
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleLoginEnabled = async (enabled: boolean) =>
|
const toggleLoginEnabled = async (enabled: boolean) =>
|
||||||
@@ -92,55 +96,130 @@ export const useSSOStore = defineStore('sso', () => {
|
|||||||
|
|
||||||
const getSamlMetadata = async () => await ssoApi.getSamlMetadata(rootStore.restApiContext);
|
const getSamlMetadata = async () => await ssoApi.getSamlMetadata(rootStore.restApiContext);
|
||||||
|
|
||||||
// const getOidcRedirectLUrl = async () => await ssoApi)
|
|
||||||
|
|
||||||
const getSamlConfig = async () => {
|
const getSamlConfig = async () => {
|
||||||
const samlConfig = await ssoApi.getSamlConfig(rootStore.restApiContext);
|
const config = await ssoApi.getSamlConfig(rootStore.restApiContext);
|
||||||
state.samlConfig = samlConfig;
|
samlConfig.value = config;
|
||||||
return samlConfig;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveSamlConfig = async (config: Partial<SamlPreferences>) =>
|
const saveSamlConfig = async (config: Partial<SamlPreferences>) =>
|
||||||
await ssoApi.saveSamlConfig(rootStore.restApiContext, config);
|
await ssoApi.saveSamlConfig(rootStore.restApiContext, config);
|
||||||
|
|
||||||
const testSamlConfig = async () => await ssoApi.testSamlConfig(rootStore.restApiContext);
|
const testSamlConfig = async () => await ssoApi.testSamlConfig(rootStore.restApiContext);
|
||||||
|
|
||||||
const updateUser = async (params: { firstName: string; lastName: string }) =>
|
/**
|
||||||
await updateCurrentUser(rootStore.restApiContext, {
|
* OIDC
|
||||||
email: usersStore.currentUser!.email!,
|
*/
|
||||||
...params,
|
|
||||||
|
const oidc = ref<
|
||||||
|
Pick<OidcConfigDto, 'loginEnabled'> & {
|
||||||
|
loginUrl?: string;
|
||||||
|
callbackUrl?: string;
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
loginUrl: '',
|
||||||
|
loginEnabled: false,
|
||||||
|
callbackUrl: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
const userData = computed(() => usersStore.currentUser);
|
const oidcConfig = ref<OidcConfigDto | undefined>();
|
||||||
|
|
||||||
|
const isEnterpriseOidcEnabled = ref(false);
|
||||||
|
|
||||||
const getOidcConfig = async () => {
|
const getOidcConfig = async () => {
|
||||||
const oidcConfig = await ssoApi.getOidcConfig(rootStore.restApiContext);
|
const config = await ssoApi.getOidcConfig(rootStore.restApiContext);
|
||||||
state.oidcConfig = oidcConfig;
|
oidcConfig.value = config;
|
||||||
return oidcConfig;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveOidcConfig = async (config: OidcConfigDto) => {
|
const saveOidcConfig = async (config: OidcConfigDto) => {
|
||||||
const savedConfig = await ssoApi.saveOidcConfig(rootStore.restApiContext, config);
|
const savedConfig = await ssoApi.saveOidcConfig(rootStore.restApiContext, config);
|
||||||
state.oidcConfig = savedConfig;
|
oidcConfig.value = savedConfig;
|
||||||
return savedConfig;
|
return savedConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isOidcLoginEnabled = computed({
|
||||||
|
get: () => oidc.value.loginEnabled,
|
||||||
|
set: (value: boolean) => {
|
||||||
|
oidc.value.loginEnabled = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDefaultAuthenticationOidc = computed(
|
||||||
|
() => authenticationMethod.value === UserManagementAuthenticationMethod.Oidc,
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LDAP Configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ldap = ref<Pick<LdapConfig, 'loginLabel' | 'loginEnabled'>>({
|
||||||
|
loginLabel: '',
|
||||||
|
loginEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isEnterpriseLdapEnabled = ref(false);
|
||||||
|
|
||||||
|
const isLdapLoginEnabled = computed(() => ldap.value.loginEnabled);
|
||||||
|
|
||||||
|
const ldapLoginLabel = computed(() => ldap.value.loginLabel);
|
||||||
|
|
||||||
|
const getLdapConfig = async () => {
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
return await ldapApi.getLdapConfig(rootStore.restApiContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLdapSynchronizations = async (pagination: { page: number }) => {
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
return await ldapApi.getLdapSynchronizations(rootStore.restApiContext, pagination);
|
||||||
|
};
|
||||||
|
|
||||||
|
const testLdapConnection = async () => {
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
return await ldapApi.testLdapConnection(rootStore.restApiContext);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateLdapConfig = async (ldapConfig: LdapConfig) => {
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
return await ldapApi.updateLdapConfig(rootStore.restApiContext, ldapConfig);
|
||||||
|
};
|
||||||
|
|
||||||
|
const runLdapSync = async (data: IDataObject) => {
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
return await ldapApi.runLdapSync(rootStore.restApiContext, data);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isEnterpriseOidcEnabled,
|
showSsoLoginButton,
|
||||||
|
getSSORedirectUrl,
|
||||||
|
initialize,
|
||||||
|
|
||||||
|
saml,
|
||||||
|
samlConfig,
|
||||||
isSamlLoginEnabled,
|
isSamlLoginEnabled,
|
||||||
isOidcLoginEnabled,
|
|
||||||
isEnterpriseSamlEnabled,
|
isEnterpriseSamlEnabled,
|
||||||
isDefaultAuthenticationSaml,
|
isDefaultAuthenticationSaml,
|
||||||
isDefaultAuthenticationOidc,
|
|
||||||
showSsoLoginButton,
|
|
||||||
samlConfig,
|
|
||||||
oidcConfig,
|
|
||||||
userData,
|
|
||||||
getSSORedirectUrl,
|
|
||||||
getSamlMetadata,
|
getSamlMetadata,
|
||||||
getSamlConfig,
|
getSamlConfig,
|
||||||
saveSamlConfig,
|
saveSamlConfig,
|
||||||
testSamlConfig,
|
testSamlConfig,
|
||||||
updateUser,
|
|
||||||
|
oidc,
|
||||||
|
oidcConfig,
|
||||||
|
isOidcLoginEnabled,
|
||||||
|
isEnterpriseOidcEnabled,
|
||||||
|
isDefaultAuthenticationOidc,
|
||||||
getOidcConfig,
|
getOidcConfig,
|
||||||
saveOidcConfig,
|
saveOidcConfig,
|
||||||
|
|
||||||
|
ldap,
|
||||||
|
isLdapLoginEnabled,
|
||||||
|
isEnterpriseLdapEnabled,
|
||||||
|
ldapLoginLabel,
|
||||||
|
getLdapConfig,
|
||||||
|
getLdapSynchronizations,
|
||||||
|
testLdapConnection,
|
||||||
|
updateLdapConfig,
|
||||||
|
runLdapSync,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,20 +1,13 @@
|
|||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
import merge from 'lodash/merge';
|
import type { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
|
|
||||||
import type { FrontendSettings } from '@n8n/api-types';
|
|
||||||
|
|
||||||
let ssoStore: ReturnType<typeof useSSOStore>;
|
let ssoStore: ReturnType<typeof useSSOStore>;
|
||||||
let settingsStore: ReturnType<typeof useSettingsStore>;
|
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: FrontendSettings = SETTINGS_STORE_DEFAULT_STATE.settings;
|
|
||||||
|
|
||||||
describe('SSO store', () => {
|
describe('SSO store', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createPinia());
|
setActivePinia(createPinia());
|
||||||
ssoStore = useSSOStore();
|
ssoStore = useSSOStore();
|
||||||
settingsStore = useSettingsStore();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
@@ -26,21 +19,19 @@ describe('SSO store', () => {
|
|||||||
])(
|
])(
|
||||||
'should check SSO login button availability when authenticationMethod is %s and enterprise feature is %s and sso login is set to %s',
|
'should check SSO login button availability when authenticationMethod is %s and enterprise feature is %s and sso login is set to %s',
|
||||||
(authenticationMethod, saml, loginEnabled, expectation) => {
|
(authenticationMethod, saml, loginEnabled, expectation) => {
|
||||||
settingsStore.setSettings(
|
ssoStore.initialize({
|
||||||
merge({}, DEFAULT_SETTINGS, {
|
authenticationMethod: authenticationMethod as UserManagementAuthenticationMethod,
|
||||||
userManagement: {
|
config: {
|
||||||
authenticationMethod,
|
|
||||||
},
|
|
||||||
enterprise: {
|
|
||||||
saml,
|
|
||||||
},
|
|
||||||
sso: {
|
|
||||||
saml: {
|
saml: {
|
||||||
loginEnabled,
|
loginEnabled,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
features: {
|
||||||
);
|
saml,
|
||||||
|
ldap: false,
|
||||||
|
oidc: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
expect(ssoStore.showSsoLoginButton).toBe(expectation);
|
expect(ssoStore.showSsoLoginButton).toBe(expectation);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -264,6 +264,18 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
|
|||||||
const updateUser = async (params: UserUpdateRequestDto) => {
|
const updateUser = async (params: UserUpdateRequestDto) => {
|
||||||
const user = await usersApi.updateCurrentUser(rootStore.restApiContext, params);
|
const user = await usersApi.updateCurrentUser(rootStore.restApiContext, params);
|
||||||
addUsers([user]);
|
addUsers([user]);
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUserName = async (params: { firstName: string; lastName: string }) => {
|
||||||
|
if (!currentUser.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await updateUser({
|
||||||
|
email: currentUser.value.email as string,
|
||||||
|
...params,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateUserSettings = async (settings: SettingsUpdateRequestDto) => {
|
const updateUserSettings = async (settings: SettingsUpdateRequestDto) => {
|
||||||
@@ -430,6 +442,7 @@ export const useUsersStore = defineStore(STORES.USERS, () => {
|
|||||||
validatePasswordToken,
|
validatePasswordToken,
|
||||||
changePassword,
|
changePassword,
|
||||||
updateUser,
|
updateUser,
|
||||||
|
updateUserName,
|
||||||
updateUserSettings,
|
updateUserSettings,
|
||||||
updateOtherUserSettings,
|
updateOtherUserSettings,
|
||||||
updateCurrentUserPassword,
|
updateCurrentUserPassword,
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { useRouter } from 'vue-router';
|
|||||||
import { createTestingPinia } from '@pinia/testing';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
import SamlOnboarding from '@/views/SamlOnboarding.vue';
|
import SamlOnboarding from '@/views/SamlOnboarding.vue';
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
|
||||||
import { STORES } from '@n8n/stores';
|
import { STORES } from '@n8n/stores';
|
||||||
import { SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils';
|
import { SETTINGS_STORE_DEFAULT_STATE, waitAllPromises } from '@/__tests__/utils';
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
|
||||||
vi.mock('vue-router', () => {
|
vi.mock('vue-router', () => {
|
||||||
const push = vi.fn();
|
const push = vi.fn();
|
||||||
@@ -20,7 +20,7 @@ vi.mock('vue-router', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let pinia: ReturnType<typeof createTestingPinia>;
|
let pinia: ReturnType<typeof createTestingPinia>;
|
||||||
let ssoStore: ReturnType<typeof useSSOStore>;
|
let usersStore: ReturnType<typeof useUsersStore>;
|
||||||
let router: ReturnType<typeof useRouter>;
|
let router: ReturnType<typeof useRouter>;
|
||||||
|
|
||||||
const renderComponent = createComponentRenderer(SamlOnboarding);
|
const renderComponent = createComponentRenderer(SamlOnboarding);
|
||||||
@@ -34,7 +34,7 @@ describe('SamlOnboarding', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
ssoStore = useSSOStore(pinia);
|
usersStore = useUsersStore(pinia);
|
||||||
router = useRouter();
|
router = useRouter();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ describe('SamlOnboarding', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should submit filled in form only and redirect', async () => {
|
it('should submit filled in form only and redirect', async () => {
|
||||||
vi.spyOn(ssoStore, 'updateUser').mockResolvedValue({
|
vi.spyOn(usersStore, 'updateUserName').mockResolvedValue({
|
||||||
id: '1',
|
id: '1',
|
||||||
isPending: false,
|
isPending: false,
|
||||||
});
|
});
|
||||||
@@ -56,14 +56,14 @@ describe('SamlOnboarding', () => {
|
|||||||
await userEvent.click(submit);
|
await userEvent.click(submit);
|
||||||
await waitAllPromises();
|
await waitAllPromises();
|
||||||
|
|
||||||
expect(ssoStore.updateUser).not.toHaveBeenCalled();
|
expect(usersStore.updateUserName).not.toHaveBeenCalled();
|
||||||
expect(router.push).not.toHaveBeenCalled();
|
expect(router.push).not.toHaveBeenCalled();
|
||||||
|
|
||||||
await userEvent.type(inputs[0], 'test');
|
await userEvent.type(inputs[0], 'test');
|
||||||
await userEvent.type(inputs[1], 'test');
|
await userEvent.type(inputs[1], 'test');
|
||||||
await userEvent.click(submit);
|
await userEvent.click(submit);
|
||||||
|
|
||||||
expect(ssoStore.updateUser).toHaveBeenCalled();
|
expect(usersStore.updateUserName).toHaveBeenCalled();
|
||||||
expect(router.push).toHaveBeenCalled();
|
expect(router.push).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import AuthView from '@/views/AuthView.vue';
|
|||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
import { useI18n } from '@n8n/i18n';
|
import { useI18n } from '@n8n/i18n';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const locale = useI18n();
|
const locale = useI18n();
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const ssoStore = useSSOStore();
|
const usersStore = useUsersStore();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const FORM_CONFIG: IFormBoxConfig = reactive({
|
const FORM_CONFIG: IFormBoxConfig = reactive({
|
||||||
@@ -21,7 +21,7 @@ const FORM_CONFIG: IFormBoxConfig = reactive({
|
|||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: 'firstName',
|
name: 'firstName',
|
||||||
initialValue: ssoStore.userData?.firstName,
|
initialValue: usersStore.currentUser?.firstName,
|
||||||
properties: {
|
properties: {
|
||||||
label: locale.baseText('auth.firstName'),
|
label: locale.baseText('auth.firstName'),
|
||||||
maxlength: 32,
|
maxlength: 32,
|
||||||
@@ -32,7 +32,7 @@ const FORM_CONFIG: IFormBoxConfig = reactive({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'lastName',
|
name: 'lastName',
|
||||||
initialValue: ssoStore.userData?.lastName,
|
initialValue: usersStore.currentUser?.lastName,
|
||||||
properties: {
|
properties: {
|
||||||
label: locale.baseText('auth.lastName'),
|
label: locale.baseText('auth.lastName'),
|
||||||
maxlength: 32,
|
maxlength: 32,
|
||||||
@@ -54,7 +54,7 @@ const onSubmit = async (values: { [key: string]: string }) => {
|
|||||||
if (!isFormWithFirstAndLastName(values)) return;
|
if (!isFormWithFirstAndLastName(values)) return;
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await ssoStore.updateUser(values);
|
await usersStore.updateUserName(values);
|
||||||
await router.push({ name: VIEWS.HOMEPAGE });
|
await router.push({ name: VIEWS.HOMEPAGE });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { createFormEventBus } from '@n8n/design-system/utils';
|
|||||||
import type { TableColumnCtx } from 'element-plus';
|
import type { TableColumnCtx } from 'element-plus';
|
||||||
import { useI18n } from '@n8n/i18n';
|
import { useI18n } from '@n8n/i18n';
|
||||||
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
|
|
||||||
type TableRow = {
|
type TableRow = {
|
||||||
status: string;
|
status: string;
|
||||||
@@ -65,6 +66,7 @@ const documentTitle = useDocumentTitle();
|
|||||||
const pageRedirectionHelper = usePageRedirectionHelper();
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const ssoStore = useSSOStore();
|
||||||
|
|
||||||
const dataTable = ref<LdapSyncTable[]>([]);
|
const dataTable = ref<LdapSyncTable[]>([]);
|
||||||
const tableKey = ref(0);
|
const tableKey = ref(0);
|
||||||
@@ -192,7 +194,7 @@ const onSubmit = async () => {
|
|||||||
hasAnyChanges.value = true;
|
hasAnyChanges.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
adConfig.value = await settingsStore.updateLdapConfig(newConfiguration);
|
adConfig.value = await ssoStore.updateLdapConfig(newConfiguration);
|
||||||
toast.showToast({
|
toast.showToast({
|
||||||
title: i18n.baseText('settings.ldap.updateConfiguration'),
|
title: i18n.baseText('settings.ldap.updateConfiguration'),
|
||||||
message: '',
|
message: '',
|
||||||
@@ -214,7 +216,7 @@ const onSaveClick = () => {
|
|||||||
const onTestConnectionClick = async () => {
|
const onTestConnectionClick = async () => {
|
||||||
loadingTestConnection.value = true;
|
loadingTestConnection.value = true;
|
||||||
try {
|
try {
|
||||||
await settingsStore.testLdapConnection();
|
await ssoStore.testLdapConnection();
|
||||||
toast.showToast({
|
toast.showToast({
|
||||||
title: i18n.baseText('settings.ldap.connectionTest'),
|
title: i18n.baseText('settings.ldap.connectionTest'),
|
||||||
message: i18n.baseText('settings.ldap.toast.connection.success'),
|
message: i18n.baseText('settings.ldap.toast.connection.success'),
|
||||||
@@ -234,7 +236,7 @@ const onTestConnectionClick = async () => {
|
|||||||
const onDryRunClick = async () => {
|
const onDryRunClick = async () => {
|
||||||
loadingDryRun.value = true;
|
loadingDryRun.value = true;
|
||||||
try {
|
try {
|
||||||
await settingsStore.runLdapSync({ type: 'dry' });
|
await ssoStore.runLdapSync({ type: 'dry' });
|
||||||
toast.showToast({
|
toast.showToast({
|
||||||
title: i18n.baseText('settings.ldap.runSync.title'),
|
title: i18n.baseText('settings.ldap.runSync.title'),
|
||||||
message: i18n.baseText('settings.ldap.toast.sync.success'),
|
message: i18n.baseText('settings.ldap.toast.sync.success'),
|
||||||
@@ -251,7 +253,7 @@ const onDryRunClick = async () => {
|
|||||||
const onLiveRunClick = async () => {
|
const onLiveRunClick = async () => {
|
||||||
loadingLiveRun.value = true;
|
loadingLiveRun.value = true;
|
||||||
try {
|
try {
|
||||||
await settingsStore.runLdapSync({ type: 'live' });
|
await ssoStore.runLdapSync({ type: 'live' });
|
||||||
toast.showToast({
|
toast.showToast({
|
||||||
title: i18n.baseText('settings.ldap.runSync.title'),
|
title: i18n.baseText('settings.ldap.runSync.title'),
|
||||||
message: i18n.baseText('settings.ldap.toast.sync.success'),
|
message: i18n.baseText('settings.ldap.toast.sync.success'),
|
||||||
@@ -267,7 +269,7 @@ const onLiveRunClick = async () => {
|
|||||||
|
|
||||||
const getLdapConfig = async () => {
|
const getLdapConfig = async () => {
|
||||||
try {
|
try {
|
||||||
adConfig.value = await settingsStore.getLdapConfig();
|
adConfig.value = await ssoStore.getLdapConfig();
|
||||||
loginEnabled.value = adConfig.value.loginEnabled;
|
loginEnabled.value = adConfig.value.loginEnabled;
|
||||||
syncEnabled.value = adConfig.value.synchronizationEnabled;
|
syncEnabled.value = adConfig.value.synchronizationEnabled;
|
||||||
const whenLoginEnabled: IFormInput['shouldDisplay'] = (values) => values.loginEnabled === true;
|
const whenLoginEnabled: IFormInput['shouldDisplay'] = (values) => values.loginEnabled === true;
|
||||||
@@ -554,7 +556,7 @@ const getLdapConfig = async () => {
|
|||||||
const getLdapSynchronizations = async (state: Parameters<Events['infinite']>[0]) => {
|
const getLdapSynchronizations = async (state: Parameters<Events['infinite']>[0]) => {
|
||||||
try {
|
try {
|
||||||
loadingTable.value = true;
|
loadingTable.value = true;
|
||||||
const data = await settingsStore.getLdapSynchronizations({
|
const data = await ssoStore.getLdapSynchronizations({
|
||||||
page: page.value,
|
page: page.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ import { setupServer } from '@/__tests__/server';
|
|||||||
import { ROLE } from '@n8n/api-types';
|
import { ROLE } from '@n8n/api-types';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
|
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
|
|
||||||
let pinia: ReturnType<typeof createPinia>;
|
let pinia: ReturnType<typeof createPinia>;
|
||||||
let settingsStore: ReturnType<typeof useSettingsStore>;
|
let settingsStore: ReturnType<typeof useSettingsStore>;
|
||||||
|
let ssoStore: ReturnType<typeof useSSOStore>;
|
||||||
let usersStore: ReturnType<typeof useUsersStore>;
|
let usersStore: ReturnType<typeof useUsersStore>;
|
||||||
let uiStore: ReturnType<typeof useUIStore>;
|
let uiStore: ReturnType<typeof useUIStore>;
|
||||||
let cloudPlanStore: ReturnType<typeof useCloudPlanStore>;
|
let cloudPlanStore: ReturnType<typeof useCloudPlanStore>;
|
||||||
@@ -41,6 +44,7 @@ describe('SettingsPersonalView', () => {
|
|||||||
pinia = createPinia();
|
pinia = createPinia();
|
||||||
|
|
||||||
settingsStore = useSettingsStore(pinia);
|
settingsStore = useSettingsStore(pinia);
|
||||||
|
ssoStore = useSSOStore(pinia);
|
||||||
usersStore = useUsersStore(pinia);
|
usersStore = useUsersStore(pinia);
|
||||||
uiStore = useUIStore(pinia);
|
uiStore = useUIStore(pinia);
|
||||||
cloudPlanStore = useCloudPlanStore(pinia);
|
cloudPlanStore = useCloudPlanStore(pinia);
|
||||||
@@ -49,6 +53,15 @@ describe('SettingsPersonalView', () => {
|
|||||||
usersStore.currentUserId = currentUser.id;
|
usersStore.currentUserId = currentUser.id;
|
||||||
|
|
||||||
await settingsStore.getSettings();
|
await settingsStore.getSettings();
|
||||||
|
ssoStore.initialize({
|
||||||
|
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
||||||
|
config: settingsStore.settings.sso,
|
||||||
|
features: {
|
||||||
|
saml: true,
|
||||||
|
ldap: true,
|
||||||
|
oidc: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@@ -96,7 +109,9 @@ describe('SettingsPersonalView', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should commit the theme change after clicking save', async () => {
|
it('should commit the theme change after clicking save', async () => {
|
||||||
vi.spyOn(usersStore, 'updateUser').mockReturnValue(Promise.resolve());
|
vi.spyOn(usersStore, 'updateUser').mockReturnValue(
|
||||||
|
Promise.resolve({ id: '123', isPending: false }),
|
||||||
|
);
|
||||||
const { getByPlaceholderText, findByText, getByTestId } = renderComponent({ pinia });
|
const { getByPlaceholderText, findByText, getByTestId } = renderComponent({ pinia });
|
||||||
await waitAllPromises();
|
await waitAllPromises();
|
||||||
|
|
||||||
@@ -116,8 +131,8 @@ describe('SettingsPersonalView', () => {
|
|||||||
|
|
||||||
describe('when external auth is enabled, email and password change', () => {
|
describe('when external auth is enabled, email and password change', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.spyOn(settingsStore, 'isSamlLoginEnabled', 'get').mockReturnValue(true);
|
vi.spyOn(ssoStore, 'isSamlLoginEnabled', 'get').mockReturnValue(true);
|
||||||
vi.spyOn(settingsStore, 'isDefaultAuthenticationSaml', 'get').mockReturnValue(true);
|
vi.spyOn(ssoStore, 'isDefaultAuthenticationSaml', 'get').mockReturnValue(true);
|
||||||
vi.spyOn(settingsStore, 'isMfaFeatureEnabled', 'get').mockReturnValue(true);
|
vi.spyOn(settingsStore, 'isMfaFeatureEnabled', 'get').mockReturnValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { createFormEventBus } from '@n8n/design-system/utils';
|
|||||||
import type { MfaModalEvents } from '@/event-bus/mfa';
|
import type { MfaModalEvents } from '@/event-bus/mfa';
|
||||||
import { promptMfaCodeBus } from '@/event-bus/mfa';
|
import { promptMfaCodeBus } from '@/event-bus/mfa';
|
||||||
import type { BaseTextKey } from '@n8n/i18n';
|
import type { BaseTextKey } from '@n8n/i18n';
|
||||||
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
|
|
||||||
type UserBasicDetailsForm = {
|
type UserBasicDetailsForm = {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
@@ -62,32 +63,38 @@ const themeOptions = ref<Array<{ name: ThemeOption; label: BaseTextKey }>>([
|
|||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const ssoStore = useSSOStore();
|
||||||
const cloudPlanStore = useCloudPlanStore();
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
|
|
||||||
const currentUser = computed((): IUser | null => {
|
const currentUser = computed((): IUser | null => {
|
||||||
return usersStore.currentUser;
|
return usersStore.currentUser;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isExternalAuthEnabled = computed((): boolean => {
|
const isExternalAuthEnabled = computed((): boolean => {
|
||||||
const isLdapEnabled =
|
const isLdapEnabled =
|
||||||
settingsStore.settings.enterprise.ldap && currentUser.value?.signInType === 'ldap';
|
ssoStore.isEnterpriseLdapEnabled && currentUser.value?.signInType === 'ldap';
|
||||||
const isSamlEnabled =
|
const isSamlEnabled = ssoStore.isSamlLoginEnabled && ssoStore.isDefaultAuthenticationSaml;
|
||||||
settingsStore.isSamlLoginEnabled && settingsStore.isDefaultAuthenticationSaml;
|
|
||||||
const isOidcEnabled =
|
const isOidcEnabled =
|
||||||
settingsStore.settings.enterprise.oidc && currentUser.value?.signInType === 'oidc';
|
ssoStore.isEnterpriseOidcEnabled && currentUser.value?.signInType === 'oidc';
|
||||||
return isLdapEnabled || isSamlEnabled || isOidcEnabled;
|
return isLdapEnabled || isSamlEnabled || isOidcEnabled;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isPersonalSecurityEnabled = computed((): boolean => {
|
const isPersonalSecurityEnabled = computed((): boolean => {
|
||||||
return usersStore.isInstanceOwner || !isExternalAuthEnabled.value;
|
return usersStore.isInstanceOwner || !isExternalAuthEnabled.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
const mfaDisabled = computed((): boolean => {
|
const mfaDisabled = computed((): boolean => {
|
||||||
return !usersStore.mfaEnabled;
|
return !usersStore.mfaEnabled;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isMfaFeatureEnabled = computed((): boolean => {
|
const isMfaFeatureEnabled = computed((): boolean => {
|
||||||
return settingsStore.isMfaFeatureEnabled;
|
return settingsStore.isMfaFeatureEnabled;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasAnyPersonalisationChanges = computed((): boolean => {
|
const hasAnyPersonalisationChanges = computed((): boolean => {
|
||||||
return currentSelectedTheme.value !== uiStore.theme;
|
return currentSelectedTheme.value !== uiStore.theme;
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasAnyChanges = computed(() => {
|
const hasAnyChanges = computed(() => {
|
||||||
return hasAnyBasicInfoChanges.value || hasAnyPersonalisationChanges.value;
|
return hasAnyBasicInfoChanges.value || hasAnyPersonalisationChanges.value;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { useSettingsStore } from '@/stores/settings.store';
|
|||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import { EnterpriseEditionFeature } from '@/constants';
|
|
||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
import type { SamlPreferencesExtractedData } from '@n8n/rest-api-client/api/sso';
|
import type { SamlPreferencesExtractedData } from '@n8n/rest-api-client/api/sso';
|
||||||
@@ -328,7 +327,7 @@ describe('SettingsSso', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render licensed content', async () => {
|
it('should render licensed content', async () => {
|
||||||
settingsStore.settings.enterprise[EnterpriseEditionFeature.Saml] = true;
|
ssoStore.isEnterpriseSamlEnabled = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
const { getByTestId, queryByTestId, getByRole } = renderComponent({
|
const { getByTestId, queryByTestId, getByRole } = renderComponent({
|
||||||
@@ -342,7 +341,7 @@ describe('SettingsSso', () => {
|
|||||||
|
|
||||||
it('should enable activation checkbox and test button if data is already saved', async () => {
|
it('should enable activation checkbox and test button if data is already saved', async () => {
|
||||||
await ssoStore.getSamlConfig();
|
await ssoStore.getSamlConfig();
|
||||||
settingsStore.settings.enterprise[EnterpriseEditionFeature.Saml] = true;
|
ssoStore.isEnterpriseSamlEnabled = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
const { container, getByTestId, getByRole } = renderComponent({
|
const { container, getByTestId, getByRole } = renderComponent({
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { useDocumentTitle } from '@/composables/useDocumentTitle';
|
|||||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||||
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
|
||||||
import { MODAL_CONFIRM } from '@/constants';
|
import { MODAL_CONFIRM } from '@/constants';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
|
|
||||||
type SupportedProtocolType = (typeof SupportedProtocols)[keyof typeof SupportedProtocols];
|
type SupportedProtocolType = (typeof SupportedProtocols)[keyof typeof SupportedProtocols];
|
||||||
|
|
||||||
@@ -29,7 +28,6 @@ const telemetry = useTelemetry();
|
|||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const ssoStore = useSSOStore();
|
const ssoStore = useSSOStore();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const settingsStore = useSettingsStore();
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const documentTitle = useDocumentTitle();
|
const documentTitle = useDocumentTitle();
|
||||||
const pageRedirectionHelper = usePageRedirectionHelper();
|
const pageRedirectionHelper = usePageRedirectionHelper();
|
||||||
@@ -398,7 +396,7 @@ async function onOidcSettingsSave() {
|
|||||||
<div :class="$style.group">
|
<div :class="$style.group">
|
||||||
<label>Redirect URL</label>
|
<label>Redirect URL</label>
|
||||||
<CopyInput
|
<CopyInput
|
||||||
:value="settingsStore.oidcCallBackUrl"
|
:value="ssoStore.oidc.callbackUrl"
|
||||||
:copy-button-text="i18n.baseText('generic.clickToCopy')"
|
:copy-button-text="i18n.baseText('generic.clickToCopy')"
|
||||||
toast-title="Redirect URL copied to clipboard"
|
toast-title="Redirect URL copied to clipboard"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -91,8 +91,7 @@ describe('SettingsUsersView', () => {
|
|||||||
|
|
||||||
it('hides invite button visibility based on user permissions', async () => {
|
it('hides invite button visibility based on user permissions', async () => {
|
||||||
const pinia = createTestingPinia({ initialState: getInitialState() });
|
const pinia = createTestingPinia({ initialState: getInitialState() });
|
||||||
const userStore = useUsersStore(pinia);
|
const userStore = mockedStore(useUsersStore);
|
||||||
// @ts-expect-error: mocked getter
|
|
||||||
userStore.currentUser = createUser({ isDefaultUser: true });
|
userStore.currentUser = createUser({ isDefaultUser: true });
|
||||||
|
|
||||||
const { queryByTestId } = renderView({ pinia });
|
const { queryByTestId } = renderView({ pinia });
|
||||||
@@ -103,8 +102,7 @@ describe('SettingsUsersView', () => {
|
|||||||
describe('Below quota', () => {
|
describe('Below quota', () => {
|
||||||
const pinia = createTestingPinia({ initialState: getInitialState() });
|
const pinia = createTestingPinia({ initialState: getInitialState() });
|
||||||
|
|
||||||
const settingsStore = useSettingsStore(pinia);
|
const settingsStore = mockedStore(useSettingsStore);
|
||||||
// @ts-expect-error: mocked getter
|
|
||||||
settingsStore.isBelowUserQuota = false;
|
settingsStore.isBelowUserQuota = false;
|
||||||
|
|
||||||
it('disables the invite button', async () => {
|
it('disables the invite button', async () => {
|
||||||
@@ -180,8 +178,7 @@ describe('SettingsUsersView', () => {
|
|||||||
|
|
||||||
const pinia = createTestingPinia({ initialState: getInitialState() });
|
const pinia = createTestingPinia({ initialState: getInitialState() });
|
||||||
|
|
||||||
const settingsStore = useSettingsStore(pinia);
|
const settingsStore = mockedStore(useSettingsStore);
|
||||||
// @ts-expect-error: mocked getter
|
|
||||||
settingsStore.isSmtpSetup = true;
|
settingsStore.isSmtpSetup = true;
|
||||||
|
|
||||||
const userStore = useUsersStore();
|
const userStore = useUsersStore();
|
||||||
@@ -236,9 +233,8 @@ describe('SettingsUsersView', () => {
|
|||||||
|
|
||||||
const pinia = createTestingPinia({ initialState: getInitialState() });
|
const pinia = createTestingPinia({ initialState: getInitialState() });
|
||||||
|
|
||||||
const settingsStore = useSettingsStore(pinia);
|
const ssoStore = useSSOStore(pinia);
|
||||||
// @ts-expect-error: mocked getter
|
ssoStore.isSamlLoginEnabled = true;
|
||||||
settingsStore.isSamlLoginEnabled = true;
|
|
||||||
|
|
||||||
const userStore = useUsersStore();
|
const userStore = useUsersStore();
|
||||||
|
|
||||||
@@ -256,9 +252,8 @@ describe('SettingsUsersView', () => {
|
|||||||
|
|
||||||
const pinia = createTestingPinia({ initialState: getInitialState() });
|
const pinia = createTestingPinia({ initialState: getInitialState() });
|
||||||
|
|
||||||
const settingsStore = useSettingsStore(pinia);
|
const ssoStore = useSSOStore(pinia);
|
||||||
// @ts-expect-error: mocked getter
|
ssoStore.isSamlLoginEnabled = true;
|
||||||
settingsStore.isSamlLoginEnabled = true;
|
|
||||||
|
|
||||||
const userStore = useUsersStore();
|
const userStore = useUsersStore();
|
||||||
|
|
||||||
|
|||||||
@@ -71,13 +71,12 @@ const usersListActions = computed((): IUserListAction[] => {
|
|||||||
{
|
{
|
||||||
label: i18n.baseText('settings.users.actions.allowSSOManualLogin'),
|
label: i18n.baseText('settings.users.actions.allowSSOManualLogin'),
|
||||||
value: 'allowSSOManualLogin',
|
value: 'allowSSOManualLogin',
|
||||||
guard: (user) => settingsStore.isSamlLoginEnabled && !user.settings?.allowSSOManualLogin,
|
guard: (user) => !!ssoStore.isSamlLoginEnabled && !user.settings?.allowSSOManualLogin,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: i18n.baseText('settings.users.actions.disallowSSOManualLogin'),
|
label: i18n.baseText('settings.users.actions.disallowSSOManualLogin'),
|
||||||
value: 'disallowSSOManualLogin',
|
value: 'disallowSSOManualLogin',
|
||||||
guard: (user) =>
|
guard: (user) => !!ssoStore.isSamlLoginEnabled && user.settings?.allowSSOManualLogin === true,
|
||||||
settingsStore.isSamlLoginEnabled && user.settings?.allowSSOManualLogin === true,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
|||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
|
|
||||||
import type { IFormBoxConfig } from '@/Interface';
|
import type { IFormBoxConfig } from '@/Interface';
|
||||||
import { MFA_AUTHENTICATION_REQUIRED_ERROR_CODE, VIEWS, MFA_FORM } from '@/constants';
|
import { MFA_AUTHENTICATION_REQUIRED_ERROR_CODE, VIEWS, MFA_FORM } from '@/constants';
|
||||||
@@ -27,6 +28,7 @@ export type MfaCodeOrMfaRecoveryCode = Pick<LoginRequestDto, 'mfaCode' | 'mfaRec
|
|||||||
const usersStore = useUsersStore();
|
const usersStore = useUsersStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const cloudPlanStore = useCloudPlanStore();
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
|
const ssoStore = useSSOStore();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -41,8 +43,8 @@ const emailOrLdapLoginId = ref('');
|
|||||||
const password = ref('');
|
const password = ref('');
|
||||||
const reportError = ref(false);
|
const reportError = ref(false);
|
||||||
|
|
||||||
const ldapLoginLabel = computed(() => settingsStore.ldapLoginLabel);
|
const ldapLoginLabel = computed(() => ssoStore.ldapLoginLabel);
|
||||||
const isLdapLoginEnabled = computed(() => settingsStore.isLdapLoginEnabled);
|
const isLdapLoginEnabled = computed(() => ssoStore.isLdapLoginEnabled);
|
||||||
const emailLabel = computed(() => {
|
const emailLabel = computed(() => {
|
||||||
let label = locale.baseText('auth.email');
|
let label = locale.baseText('auth.email');
|
||||||
if (isLdapLoginEnabled.value && ldapLoginLabel.value) {
|
if (isLdapLoginEnabled.value && ldapLoginLabel.value) {
|
||||||
|
|||||||
Reference in New Issue
Block a user