import { useI18n } from '@n8n/i18n';
import { ElDialog } from 'element-plus';
-import { N8nButton, N8nCheckbox, N8nIcon, N8nText } from '@n8n/design-system';
+import { N8nButton, N8nCard, N8nCheckbox, N8nIcon, N8nText } from '@n8n/design-system';
import { ref, watch, computed } from 'vue';
import { useAccessSettingsCsvExport } from '@/features/settings/sso/provisioning/composables/useAccessSettingsCsvExport';
import type { UserRoleProvisioningSetting } from './UserRoleProvisioningDropdown.vue';
@@ -77,22 +77,23 @@ const onConfirmProvisioningSetting = () => {
>
- {{
- locale.baseText('settings.provisioningConfirmDialog.breakingChangeDescription.firstLine')
- }}
+ {{
+ locale.baseText(
+ newProvisioningSetting === 'instance_and_project_roles'
+ ? 'settings.provisioningConfirmDialog.breakingChangeDescription.firstSentence.partOne.withProjectRoles'
+ : 'settings.provisioningConfirmDialog.breakingChangeDescription.firstSentence.partOne',
+ )
+ }}
+
+
+ {{
+ locale.baseText(
+ 'settings.provisioningConfirmDialog.breakingChangeDescription.firstSentence.partTwo',
+ )
+ }}
-
- -
- {{
- locale.baseText('settings.provisioningConfirmDialog.breakingChangeDescription.list.one')
- }}
-
- -
- {{
- locale.baseText('settings.provisioningConfirmDialog.breakingChangeDescription.list.two')
- }}
-
-
- {{
- locale.baseText('settings.provisioningConfirmDialog.breakingChangeRequiredSteps')
- }}
+
-
-
{{
+
+ -
+ {{
locale.baseText('settings.provisioningConfirmDialog.button.downloadInstanceRolesCsv')
- }}
-
-
-
- {{
+ }}
+
+
+
+
+ {{
locale.baseText('settings.provisioningConfirmDialog.button.downloadProjectRolesCsv')
- }}
-
-
+ }}
+
+
+
+
@@ -154,40 +156,6 @@ const onConfirmProvisioningSetting = () => {
locale.baseText('settings.provisioningConfirmDialog.disable.description')
}}
-
- {{
- locale.baseText('settings.provisioningConfirmDialog.disable.whatWillHappen')
- }}
-
-
- -
- {{
- locale.baseText('settings.provisioningConfirmDialog.disable.list.one')
- }}
-
- -
- {{
- locale.baseText('settings.provisioningConfirmDialog.disable.list.two')
- }}
-
-
-
- {{
- locale.baseText('settings.provisioningConfirmDialog.disable.beforeSaving')
- }}
-
-
- -
- {{
- locale.baseText('settings.provisioningConfirmDialog.disable.checklist.one')
- }}
-
- -
- {{
- locale.baseText('settings.provisioningConfirmDialog.disable.checklist.two')
- }}
-
-
-
- {{
- locale.baseText(`settings.provisioningConfirmDialog.${messagingKey}.checkbox`)
- }}
-
+
+
+ {{
+ locale.baseText(`settings.provisioningConfirmDialog.${messagingKey}.checkbox`)
+ }}
+
+
@@ -242,24 +212,35 @@ const onConfirmProvisioningSetting = () => {
diff --git a/packages/frontend/editor-ui/src/features/settings/sso/provisioning/components/UserRoleProvisioningDropdown.vue b/packages/frontend/editor-ui/src/features/settings/sso/provisioning/components/UserRoleProvisioningDropdown.vue
index d8e364d1a87..54733267321 100644
--- a/packages/frontend/editor-ui/src/features/settings/sso/provisioning/components/UserRoleProvisioningDropdown.vue
+++ b/packages/frontend/editor-ui/src/features/settings/sso/provisioning/components/UserRoleProvisioningDropdown.vue
@@ -49,7 +49,6 @@ const getUserRoleProvisioningValueFromConfig = (
type UserRoleProvisioningDescription = {
label: string;
- description: string;
value: UserRoleProvisioningSetting;
};
@@ -57,25 +56,16 @@ const userRoleProvisioningDescriptions: UserRoleProvisioningDescription[] = [
{
label: i18n.baseText('settings.sso.settings.userRoleProvisioning.option.disabled.label'),
value: 'disabled',
- description: i18n.baseText(
- 'settings.sso.settings.userRoleProvisioning.option.disabled.description',
- ),
},
{
label: i18n.baseText('settings.sso.settings.userRoleProvisioning.option.instanceRole.label'),
value: 'instance_role',
- description: i18n.baseText(
- 'settings.sso.settings.userRoleProvisioning.option.instanceRole.description',
- ),
},
{
label: i18n.baseText(
'settings.sso.settings.userRoleProvisioning.option.instanceAndProjectRoles.label',
),
value: 'instance_and_project_roles',
- description: i18n.baseText(
- 'settings.sso.settings.userRoleProvisioning.option.instanceAndProjectRoles.description',
- ),
},
];
@@ -104,12 +94,7 @@ onMounted(async () => {
:label="option.label"
data-test-id="oidc-user-role-provisioning-option"
:value="option.value"
- >
-
-
{{ option.label }}
-
{{ option.description }}
-
-
+ />
{{ i18n.baseText('settings.sso.settings.userRoleProvisioning.help') }}
diff --git a/packages/frontend/editor-ui/src/features/settings/sso/sso.store.ts b/packages/frontend/editor-ui/src/features/settings/sso/sso.store.ts
index 59bbcc9cd75..0635ec72bb7 100644
--- a/packages/frontend/editor-ui/src/features/settings/sso/sso.store.ts
+++ b/packages/frontend/editor-ui/src/features/settings/sso/sso.store.ts
@@ -88,7 +88,6 @@ export const useSSOStore = defineStore('sso', () => {
get: () => saml.value.loginEnabled,
set: (value: boolean) => {
saml.value.loginEnabled = value;
- void toggleLoginEnabled(value);
},
});
@@ -98,14 +97,13 @@ export const useSSOStore = defineStore('sso', () => {
() => authenticationMethod.value === UserManagementAuthenticationMethod.Saml,
);
- const toggleLoginEnabled = async (enabled: boolean) =>
- await ssoApi.toggleSamlConfig(rootStore.restApiContext, { loginEnabled: enabled });
-
const getSamlMetadata = async () => await ssoApi.getSamlMetadata(rootStore.restApiContext);
const getSamlConfig = async () => {
const config = await ssoApi.getSamlConfig(rootStore.restApiContext);
samlConfig.value = config;
+ saml.value.loginEnabled = config.loginEnabled;
+ saml.value.loginLabel = config.loginLabel;
return config;
};
diff --git a/packages/frontend/editor-ui/src/features/settings/sso/styles/sso-form.module.scss b/packages/frontend/editor-ui/src/features/settings/sso/styles/sso-form.module.scss
index f90a1d7c2d3..fceb66e6557 100644
--- a/packages/frontend/editor-ui/src/features/settings/sso/styles/sso-form.module.scss
+++ b/packages/frontend/editor-ui/src/features/settings/sso/styles/sso-form.module.scss
@@ -38,6 +38,11 @@
}
}
+.checkboxGroup label > *:first-child {
+ // center checkbox next to label
+ vertical-align: text-top;
+}
+
.actionBox {
margin: var(--spacing--2xl) 0 0;
}
diff --git a/packages/frontend/editor-ui/src/features/settings/sso/views/SettingsSso.test.ts b/packages/frontend/editor-ui/src/features/settings/sso/views/SettingsSso.test.ts
index d903e8fc1f5..861b429db40 100644
--- a/packages/frontend/editor-ui/src/features/settings/sso/views/SettingsSso.test.ts
+++ b/packages/frontend/editor-ui/src/features/settings/sso/views/SettingsSso.test.ts
@@ -121,31 +121,43 @@ describe('SettingsSso View', () => {
const { getByTestId } = renderView();
const toggle = getByTestId('sso-toggle');
+ const checkbox = toggle.querySelector('input[type="checkbox"]') as HTMLInputElement;
- expect(toggle.textContent).toContain('Deactivated');
+ expect(checkbox).not.toBeChecked();
await userEvent.click(toggle);
- expect(toggle.textContent).toContain('Activated');
+ expect(checkbox).toBeChecked();
await userEvent.click(toggle);
- expect(toggle.textContent).toContain('Deactivated');
+ expect(checkbox).not.toBeChecked();
});
it("allows user to fill Identity Provider's URL", async () => {
- confirmMessage.mockResolvedValueOnce('confirm');
+ // Mock two confirm dialogs: 1) test connection prompt, 2) confirm successful test
+ confirmMessage.mockResolvedValueOnce('confirm').mockResolvedValueOnce('confirm');
const windowOpenSpy = vi.spyOn(window, 'open');
ssoStore.isEnterpriseSamlEnabled = true;
ssoStore.isEnterpriseOidcEnabled = true;
ssoStore.isSamlLoginEnabled = false;
- ssoStore.samlConfig = { ...samlConfig, metadataUrl: undefined, metadata: undefined };
+ ssoStore.samlConfig = {
+ ...samlConfig,
+ metadataUrl: undefined,
+ metadata: undefined,
+ loginEnabled: false,
+ };
ssoStore.getSamlConfig.mockResolvedValue({
...samlConfig,
metadataUrl: undefined,
metadata: undefined,
+ loginEnabled: false,
+ });
+ ssoStore.saveSamlConfig.mockResolvedValue({
+ ...samlConfig,
+ metadata: undefined,
+ loginEnabled: true,
});
- ssoStore.saveSamlConfig.mockResolvedValue({ ...samlConfig, metadata: undefined });
ssoStore.testSamlConfig.mockResolvedValue('https://test-url.com');
const { getByTestId } = renderView();
@@ -158,11 +170,18 @@ describe('SettingsSso View', () => {
expect(urlInput).toBeVisible();
await userEvent.type(urlInput, samlConfig.metadataUrl as string);
+ // Enable SSO toggle
+ const toggle = getByTestId('sso-toggle');
+ await userEvent.click(toggle);
+
expect(saveButton).not.toBeDisabled();
await userEvent.click(saveButton);
expect(ssoStore.saveSamlConfig).toHaveBeenCalledWith(
- expect.objectContaining({ metadataUrl: samlConfig.metadataUrl }),
+ expect.objectContaining({
+ metadataUrl: samlConfig.metadataUrl,
+ loginEnabled: true,
+ }),
);
expect(ssoStore.testSamlConfig).toHaveBeenCalled();
@@ -177,7 +196,8 @@ describe('SettingsSso View', () => {
});
it("allows user to fill Identity Provider's XML", async () => {
- confirmMessage.mockResolvedValueOnce('confirm');
+ // Mock two confirm dialogs: 1) test connection prompt, 2) confirm successful test
+ confirmMessage.mockResolvedValueOnce('confirm').mockResolvedValueOnce('confirm');
const windowOpenSpy = vi.spyOn(window, 'open');
@@ -185,8 +205,18 @@ describe('SettingsSso View', () => {
ssoStore.isEnterpriseOidcEnabled = true;
ssoStore.isSamlLoginEnabled = false;
ssoStore.samlConfig = { ...samlConfig, metadataUrl: undefined, metadata: undefined };
+ ssoStore.getSamlConfig.mockResolvedValue({
+ ...samlConfig,
+ metadataUrl: undefined,
+ metadata: undefined,
+ loginEnabled: false,
+ });
// Mock should return config with metadata but WITHOUT metadataUrl (since user filled XML)
- ssoStore.saveSamlConfig.mockResolvedValue({ ...samlConfig, metadataUrl: undefined });
+ ssoStore.saveSamlConfig.mockResolvedValue({
+ ...samlConfig,
+ metadataUrl: undefined,
+ loginEnabled: true,
+ });
ssoStore.testSamlConfig.mockResolvedValue('https://test-url.com');
const { getByTestId } = renderView();
@@ -201,11 +231,18 @@ describe('SettingsSso View', () => {
expect(xmlInput).toBeVisible();
await userEvent.type(xmlInput, samlConfig.metadata!);
+ // Enable SSO toggle
+ const toggle = getByTestId('sso-toggle');
+ await userEvent.click(toggle);
+
expect(saveButton).not.toBeDisabled();
await userEvent.click(saveButton);
expect(ssoStore.saveSamlConfig).toHaveBeenCalledWith(
- expect.objectContaining({ metadata: samlConfig.metadata }),
+ expect.objectContaining({
+ metadata: samlConfig.metadata,
+ loginEnabled: true,
+ }),
);
expect(ssoStore.testSamlConfig).toHaveBeenCalled();
@@ -281,19 +318,21 @@ describe('SettingsSso View', () => {
ssoStore.isEnterpriseOidcEnabled = true;
ssoStore.isOidcLoginEnabled = false;
- const error = new Error('Request failed with status code 404');
- ssoStore.getSamlConfig.mockRejectedValue(error);
+ ssoStore.getSamlConfig.mockResolvedValue({
+ ...samlConfig,
+ loginEnabled: true,
+ });
const { getByTestId } = renderView();
expect(ssoStore.getSamlConfig).toHaveBeenCalledTimes(1);
await waitFor(async () => {
- expect(showError).toHaveBeenCalledWith(error, 'error');
const toggle = getByTestId('sso-toggle');
- expect(toggle.textContent).toContain('Activated');
+ const checkbox = toggle.querySelector('input[type="checkbox"]') as HTMLInputElement;
+ expect(checkbox).toBeChecked();
await userEvent.click(toggle);
- expect(toggle.textContent).toContain('Deactivated');
+ expect(checkbox).not.toBeChecked();
});
});
@@ -312,7 +351,7 @@ describe('SettingsSso View', () => {
expect(container.querySelector('textarea[name="metadata"]')).toHaveValue(samlConfig.metadata);
- expect(getByRole('switch')).toBeEnabled();
+ expect(getByRole('checkbox')).toBeEnabled();
expect(getByTestId('sso-test')).toBeEnabled();
});
});