mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
feat(editor): SSO login button (#5615)
* feat(editor): SSO login button * feat(editor): SSO login button * feat(editor): SSO login button
This commit is contained in:
parent
e0ea97af8d
commit
6916628a9f
|
@ -137,9 +137,9 @@ export class SamlController {
|
|||
const result = this.samlService.getLoginRequestUrl();
|
||||
if (result?.binding === 'redirect') {
|
||||
// forced client side redirect through the use of a javascript redirect
|
||||
return res.send(getInitSSOPostView(result.context));
|
||||
// TODO:SAML: If we want the frontend to handle the redirect, we will send the redirect URL instead:
|
||||
// return res.status(301).send(result.context.context);
|
||||
// return res.send(getInitSSOPostView(result.context));
|
||||
// Return the redirect URL directly
|
||||
return res.send(result.context.context);
|
||||
} else if (result?.binding === 'post') {
|
||||
return res.send(getInitSSOFormView(result.context as PostBindingContext));
|
||||
} else {
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
{{ redirectText }}
|
||||
</n8n-link>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -623,10 +623,17 @@ export interface IN8nPromptResponse {
|
|||
updated: boolean;
|
||||
}
|
||||
|
||||
export enum UserManagementAuthenticationMethod {
|
||||
Email = 'email',
|
||||
Ldap = 'ldap',
|
||||
Saml = 'saml',
|
||||
}
|
||||
|
||||
export interface IUserManagementConfig {
|
||||
enabled: boolean;
|
||||
showSetupOnFirstLoad?: boolean;
|
||||
smtpSetup: boolean;
|
||||
authenticationMethod: UserManagementAuthenticationMethod;
|
||||
}
|
||||
|
||||
export interface IPermissionGroup {
|
||||
|
|
6
packages/editor-ui/src/api/sso.ts
Normal file
6
packages/editor-ui/src/api/sso.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { makeRestApiRequest } from '@/utils';
|
||||
import { IRestApiContext } from '@/Interface';
|
||||
|
||||
export const initSSO = (context: IRestApiContext): Promise<string> => {
|
||||
return makeRestApiRequest(context, 'GET', '/sso/saml/initsso');
|
||||
};
|
61
packages/editor-ui/src/components/SSOLogin.vue
Normal file
61
packages/editor-ui/src/components/SSOLogin.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<script lang="ts" setup>
|
||||
import { Notification } from 'element-ui';
|
||||
import { useSSOStore } from '@/stores/sso';
|
||||
|
||||
const ssoStore = useSSOStore();
|
||||
|
||||
const onSSOLogin = async () => {
|
||||
try {
|
||||
window.location.href = await ssoStore.getSSORedirectUrl();
|
||||
} catch (error) {
|
||||
Notification.error({
|
||||
title: 'Error',
|
||||
message: error.message,
|
||||
position: 'bottom-right',
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="ssoStore.showSsoLoginButton" :class="$style.ssoLogin">
|
||||
<div :class="$style.divider">
|
||||
<span>{{ $locale.baseText('sso.login.divider') }}</span>
|
||||
</div>
|
||||
<n8n-button
|
||||
@click="onSSOLogin"
|
||||
size="large"
|
||||
type="primary"
|
||||
outline
|
||||
:label="$locale.baseText('sso.login.button')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" module>
|
||||
.ssoLogin {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
text-transform: uppercase;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--color-foreground-base);
|
||||
}
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: var(--spacing-xl) var(--spacing-l);
|
||||
background: var(--color-foreground-xlight);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -448,6 +448,7 @@ export enum EnterpriseEditionFeature {
|
|||
Sharing = 'sharing',
|
||||
Ldap = 'ldap',
|
||||
LogStreaming = 'logStreaming',
|
||||
Saml = 'saml',
|
||||
}
|
||||
export const MAIN_NODE_PANEL_WIDTH = 360;
|
||||
|
||||
|
|
|
@ -1692,5 +1692,8 @@
|
|||
"settings.ldap.form.pageSize.infoText": "Max number of records to return per page during synchronization. 0 for unlimited",
|
||||
"settings.ldap.form.searchTimeout.label": "Search Timeout (Seconds)",
|
||||
"settings.ldap.form.searchTimeout.infoText": "The timeout value for queries to the AD/LDAP server. Increase if you are getting timeout errors caused by a slow AD/LDAP server",
|
||||
"settings.ldap.section.synchronization.title": "Synchronization"
|
||||
"settings.ldap.section.synchronization.title": "Synchronization",
|
||||
|
||||
"sso.login.divider": "or",
|
||||
"sso.login.button": "Continue with SSO"
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import WorkflowsView from '@/views/WorkflowsView.vue';
|
|||
import { IPermissions } from './Interface';
|
||||
import { LOGIN_STATUS, ROLE } from '@/utils';
|
||||
import { RouteConfigSingleView } from 'vue-router/types/router';
|
||||
import { EnterpriseEditionFeature, VIEWS } from './constants';
|
||||
import { VIEWS } from './constants';
|
||||
import { useSettingsStore } from './stores/settings';
|
||||
import { useTemplatesStore } from './stores/templates';
|
||||
import SettingsUsageAndPlanVue from './views/SettingsUsageAndPlan.vue';
|
||||
|
|
|
@ -15,14 +15,15 @@ import {
|
|||
VALUE_SURVEY_MODAL_KEY,
|
||||
} from '@/constants';
|
||||
import {
|
||||
ILdapConfig,
|
||||
ILogLevel,
|
||||
IN8nPromptResponse,
|
||||
IN8nPrompts,
|
||||
IN8nUISettings,
|
||||
IN8nValueSurveyData,
|
||||
ISettingsState,
|
||||
UserManagementAuthenticationMethod,
|
||||
WorkflowCallerPolicyDefaultOption,
|
||||
ILdapConfig,
|
||||
} from '@/Interface';
|
||||
import { IDataObject, ITelemetrySettings } from 'n8n-workflow';
|
||||
import { defineStore } from 'pinia';
|
||||
|
@ -40,6 +41,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
|||
enabled: false,
|
||||
showSetupOnFirstLoad: false,
|
||||
smtpSetup: false,
|
||||
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
||||
},
|
||||
templatesEndpointHealthy: false,
|
||||
api: {
|
||||
|
@ -169,13 +171,15 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
|||
workflowCallerPolicyDefaultOption(): WorkflowCallerPolicyDefaultOption {
|
||||
return this.settings.workflowCallerPolicyDefaultOption;
|
||||
},
|
||||
isDefaultAuthenticationSaml(): boolean {
|
||||
return this.userManagement.authenticationMethod === UserManagementAuthenticationMethod.Saml;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setSettings(settings: IN8nUISettings): void {
|
||||
this.settings = settings;
|
||||
this.userManagement.enabled = settings.userManagement.enabled;
|
||||
this.userManagement = settings.userManagement;
|
||||
this.userManagement.showSetupOnFirstLoad = !!settings.userManagement.showSetupOnFirstLoad;
|
||||
this.userManagement.smtpSetup = settings.userManagement.smtpSetup;
|
||||
this.api = settings.publicApi;
|
||||
this.onboardingCallPromptEnabled = settings.onboardingCallPromptEnabled;
|
||||
this.ldap.loginEnabled = settings.sso.ldap.loginEnabled;
|
||||
|
|
42
packages/editor-ui/src/stores/sso.ts
Normal file
42
packages/editor-ui/src/stores/sso.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { computed, reactive } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { EnterpriseEditionFeature } from '@/constants';
|
||||
import { useRootStore } from '@/stores/n8nRootStore';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { initSSO } from '@/api/sso';
|
||||
|
||||
export const useSSOStore = defineStore('sso', () => {
|
||||
const rootStore = useRootStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
const state = reactive({
|
||||
loading: false,
|
||||
});
|
||||
|
||||
const isLoading = computed(() => state.loading);
|
||||
|
||||
const setLoading = (loading: boolean) => {
|
||||
state.loading = loading;
|
||||
};
|
||||
|
||||
const isSamlLoginEnabled = computed(() => settingsStore.isSamlLoginEnabled);
|
||||
const isEnterpriseSamlEnabled = computed(() =>
|
||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Saml),
|
||||
);
|
||||
const isDefaultAuthenticationSaml = computed(() => settingsStore.isDefaultAuthenticationSaml);
|
||||
const showSsoLoginButton = computed(
|
||||
() =>
|
||||
isSamlLoginEnabled.value &&
|
||||
isEnterpriseSamlEnabled.value &&
|
||||
isDefaultAuthenticationSaml.value,
|
||||
);
|
||||
|
||||
const getSSORedirectUrl = () => initSSO(rootStore.getRestApiContext);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
setLoading,
|
||||
showSsoLoginButton,
|
||||
getSSORedirectUrl,
|
||||
};
|
||||
});
|
|
@ -14,7 +14,9 @@
|
|||
@secondaryClick="onSecondaryClick"
|
||||
@submit="onSubmit"
|
||||
@input="onInput"
|
||||
/>
|
||||
>
|
||||
<SSOLogin v-if="withSso" />
|
||||
</n8n-form-box>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -22,12 +24,14 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
import Logo from '../components/Logo.vue';
|
||||
import Logo from '@/components/Logo.vue';
|
||||
import SSOLogin from '@/components/SSOLogin.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'AuthView',
|
||||
components: {
|
||||
Logo,
|
||||
SSOLogin,
|
||||
},
|
||||
props: {
|
||||
form: {},
|
||||
|
@ -38,6 +42,10 @@ export default Vue.extend({
|
|||
subtitle: {
|
||||
type: String,
|
||||
},
|
||||
withSso: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onInput(e: { name: string; value: string }) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<AuthView
|
||||
:form="FORM_CONFIG"
|
||||
:formLoading="loading"
|
||||
:with-sso="true"
|
||||
data-test-id="signin-form"
|
||||
@submit="onSubmit"
|
||||
/>
|
||||
|
|
Loading…
Reference in a new issue