Skip to content
/ qp-n8n Public
forked from n8n-io/n8n

Commit

Permalink
feat(editor): SSO login button (n8n-io#5615)
Browse files Browse the repository at this point in the history
* feat(editor): SSO login button

* feat(editor): SSO login button

* feat(editor): SSO login button
  • Loading branch information
cstuncsik authored and sunilrr committed Apr 24, 2023
1 parent fce5967 commit 816a62a
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 10 deletions.
6 changes: 3 additions & 3 deletions packages/cli/src/sso/saml/routes/saml.controller.ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
{{ redirectText }}
</n8n-link>
</div>
<slot></slot>
</div>
</template>

Expand Down
7 changes: 7 additions & 0 deletions packages/editor-ui/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions packages/editor-ui/src/api/sso.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 61 additions & 0 deletions packages/editor-ui/src/components/SSOLogin.vue
Original file line number Diff line number Diff line change
@@ -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>
1 change: 1 addition & 0 deletions packages/editor-ui/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ export enum EnterpriseEditionFeature {
Sharing = 'sharing',
Ldap = 'ldap',
LogStreaming = 'logStreaming',
Saml = 'saml',
}
export const MAIN_NODE_PANEL_WIDTH = 360;

Expand Down
5 changes: 4 additions & 1 deletion packages/editor-ui/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1693,5 +1693,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"
}
2 changes: 1 addition & 1 deletion packages/editor-ui/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
10 changes: 7 additions & 3 deletions packages/editor-ui/src/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -40,6 +41,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
enabled: false,
showSetupOnFirstLoad: false,
smtpSetup: false,
authenticationMethod: UserManagementAuthenticationMethod.Email,
},
templatesEndpointHealthy: false,
api: {
Expand Down Expand Up @@ -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;
Expand Down
42 changes: 42 additions & 0 deletions packages/editor-ui/src/stores/sso.ts
Original file line number Diff line number Diff line change
@@ -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,
};
});
12 changes: 10 additions & 2 deletions packages/editor-ui/src/views/AuthView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,24 @@
@secondaryClick="onSecondaryClick"
@submit="onSubmit"
@input="onInput"
/>
>
<SSOLogin v-if="withSso" />
</n8n-form-box>
</div>
</div>
</template>

<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: {},
Expand All @@ -38,6 +42,10 @@ export default Vue.extend({
subtitle: {
type: String,
},
withSso: {
type: Boolean,
default: false,
},
},
methods: {
onInput(e: { name: string; value: string }) {
Expand Down
1 change: 1 addition & 0 deletions packages/editor-ui/src/views/SigninView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<AuthView
:form="FORM_CONFIG"
:formLoading="loading"
:with-sso="true"
data-test-id="signin-form"
@submit="onSubmit"
/>
Expand Down

0 comments on commit 816a62a

Please sign in to comment.