feat(core): Add LDAP support (#3835)

This commit is contained in:
Ricardo Espinoza
2023-01-24 20:18:39 -05:00
committed by GitHub
parent 259296c5c9
commit 0c70a40317
77 changed files with 3686 additions and 192 deletions

View File

@@ -46,6 +46,7 @@
"fast-json-stable-stringify": "^2.1.0",
"file-saver": "^2.0.2",
"flatted": "^3.2.4",
"humanize-duration": "^3.27.2",
"jquery": "^3.4.1",
"jsonpath": "^1.1.1",
"jsplumb": "2.15.4",
@@ -69,6 +70,7 @@
"vue-agile": "^2.0.0",
"vue-fragment": "1.5.1",
"vue-i18n": "^8.26.7",
"vue-infinite-loading": "^2.4.5",
"vue-json-pretty": "1.9.3",
"vue-prism-editor": "^0.3.0",
"vue-router": "^3.6.5",
@@ -86,6 +88,7 @@
"@types/dateformat": "^3.0.0",
"@types/express": "^4.17.6",
"@types/file-saver": "^2.0.1",
"@types/humanize-duration": "^3.27.1",
"@types/jsonpath": "^0.2.0",
"@types/lodash-es": "^4.17.6",
"@types/lodash.camelcase": "^4.3.6",

View File

@@ -40,6 +40,7 @@ import {
IAbstractEventMessage,
} from 'n8n-workflow';
import { FAKE_DOOR_FEATURES } from './constants';
import { SignInType } from './constants';
import { BulkCommand, Undoable } from '@/models/history';
export * from 'n8n-design-system/types';
@@ -642,6 +643,7 @@ export interface IUserResponse {
};
personalizationAnswers?: IPersonalizationSurveyVersions | null;
isPending: boolean;
signInType?: SignInType;
}
export interface IUser extends IUserResponse {
@@ -808,6 +810,10 @@ export interface IN8nUISettings {
enabled: boolean;
};
};
ldap: {
loginLabel: string;
loginEnabled: boolean;
};
onboardingCallPromptEnabled: boolean;
allowedModules: {
builtIn?: string[];
@@ -1224,6 +1230,10 @@ export interface ISettingsState {
enabled: boolean;
};
};
ldap: {
loginLabel: string;
loginEnabled: boolean;
};
onboardingCallPromptEnabled: boolean;
saveDataErrorExecution: string;
saveDataSuccessExecution: string;
@@ -1385,6 +1395,50 @@ export type SchemaType =
| 'function'
| 'null'
| 'undefined';
export interface ILdapSyncData {
id: number;
startedAt: string;
endedAt: string;
created: number;
updated: number;
disabled: number;
scanned: number;
status: string;
error: string;
runMode: string;
}
export interface ILdapSyncTable {
status: string;
endedAt: string;
runTime: string;
runMode: string;
details: string;
}
export interface ILdapConfig {
loginEnabled: boolean;
loginLabel: string;
connectionUrl: string;
allowUnauthorizedCerts: boolean;
connectionSecurity: string;
connectionPort: number;
baseDn: string;
bindingAdminDn: string;
bindingAdminPassword: string;
firstNameAttribute: string;
lastNameAttribute: string;
emailAttribute: string;
loginIdAttribute: string;
ldapIdAttribute: string;
userFilter: string;
synchronizationEnabled: boolean;
synchronizationInterval: number; // minutes
searchPageSize: number;
searchTimeout: number;
}
export type Schema = { type: SchemaType; key?: string; value: string | Schema[]; path: string };
export type UsageState = {

View File

@@ -0,0 +1,29 @@
import { ILdapConfig, ILdapSyncData, IRestApiContext } from '@/Interface';
import { makeRestApiRequest } from '@/utils';
import { IDataObject } from 'n8n-workflow';
export function getLdapConfig(context: IRestApiContext): Promise<ILdapConfig> {
return makeRestApiRequest(context, 'GET', '/ldap/config');
}
export function testLdapConnection(context: IRestApiContext): Promise<{}> {
return makeRestApiRequest(context, 'POST', '/ldap/test-connection');
}
export function updateLdapConfig(
context: IRestApiContext,
adConfig: ILdapConfig,
): Promise<ILdapConfig> {
return makeRestApiRequest(context, 'PUT', '/ldap/config', adConfig as unknown as IDataObject);
}
export function runLdapSync(context: IRestApiContext, data: IDataObject): Promise<{}> {
return makeRestApiRequest(context, 'POST', '/ldap/sync', data as unknown as IDataObject);
}
export function getLdapSynchronizations(
context: IRestApiContext,
pagination: { page: number },
): Promise<ILdapSyncData[]> {
return makeRestApiRequest(context, 'GET', '/ldap/sync', pagination);
}

View File

@@ -74,6 +74,14 @@ export default mixins(userHelpers, pushConnection).extend({
available: this.canAccessApiSettings(),
activateOnRouteNames: [VIEWS.API_SETTINGS],
},
{
id: 'settings-ldap',
icon: 'network-wired',
label: this.$locale.baseText('settings.ldap'),
position: 'top',
available: this.canAccessLdapSettings(),
activateOnRouteNames: [VIEWS.LDAP_SETTINGS],
},
];
for (const item of this.settingsFakeDoorFeatures) {
@@ -126,6 +134,9 @@ export default mixins(userHelpers, pushConnection).extend({
canAccessApiSettings(): boolean {
return this.canUserAccessRouteByName(VIEWS.API_SETTINGS);
},
canAccessLdapSettings(): boolean {
return this.canUserAccessRouteByName(VIEWS.LDAP_SETTINGS);
},
canAccessLogStreamingSettings(): boolean {
return this.canUserAccessRouteByName(VIEWS.LOG_STREAMING_SETTINGS);
},
@@ -155,6 +166,11 @@ export default mixins(userHelpers, pushConnection).extend({
this.$router.push({ name: VIEWS.API_SETTINGS });
}
break;
case 'settings-ldap':
if (this.$router.currentRoute.name !== VIEWS.LDAP_SETTINGS) {
this.$router.push({ name: VIEWS.LDAP_SETTINGS });
}
break;
case 'settings-log-streaming':
if (this.$router.currentRoute.name !== VIEWS.LOG_STREAMING_SETTINGS) {
this.$router.push({ name: VIEWS.LOG_STREAMING_SETTINGS });

View File

@@ -312,6 +312,7 @@ export enum VIEWS {
FORGOT_PASSWORD = 'ForgotMyPasswordView',
CHANGE_PASSWORD = 'ChangePasswordView',
USERS_SETTINGS = 'UsersSettings',
LDAP_SETTINGS = 'LdapSettings',
PERSONAL_SETTINGS = 'PersonalSettings',
API_SETTINGS = 'APISettings',
NOT_FOUND = 'NotFoundView',
@@ -381,6 +382,7 @@ export enum WORKFLOW_MENU_ACTIONS {
*/
export enum EnterpriseEditionFeature {
Sharing = 'sharing',
Ldap = 'ldap',
LogStreaming = 'logStreaming',
}
export const MAIN_NODE_PANEL_WIDTH = 360;
@@ -442,6 +444,15 @@ export enum STORES {
HISTORY = 'history',
}
export enum SignInType {
LDAP = 'ldap',
EMAIL = 'email',
}
export const N8N_SALES_EMAIL = 'sales@n8n.io';
export const N8N_CONTACT_EMAIL = 'contact@n8n.io';
export const EXPRESSION_EDITOR_PARSER_TIMEOUT = 15_000; // ms
export const POSTHOG_ASSUMPTION_TEST = 'adore-assumption-tests';

View File

@@ -527,6 +527,7 @@
"forgotPassword.recoveryEmailSent": "Recovery email sent",
"forgotPassword.returnToSignIn": "Back to sign in",
"forgotPassword.sendingEmailError": "Problem sending email",
"forgotPassword.ldapUserPasswordResetUnavailable": "Please contact your LDAP administrator to reset your password",
"forgotPassword.smtpErrorContactAdministrator": "Please contact your administrator (problem with your SMTP setup)",
"forms.resourceFiltersDropdown.filters": "Filters",
"forms.resourceFiltersDropdown.ownedBy": "Owned by",
@@ -1521,7 +1522,6 @@
"importParameter.showError.invalidProtocol1.title": "Use the {node} node",
"importParameter.showError.invalidProtocol2.title": "Invalid Protocol",
"importParameter.showError.invalidProtocol.message": "The HTTP node doesnt support {protocol} requests",
"contextual.credentials.sharing.unavailable.title": "Upgrade to collaborate",
"contextual.credentials.sharing.unavailable.title.cloud": "Upgrade to collaborate",
"contextual.credentials.sharing.unavailable.title.desktop": "Upgrade to n8n Cloud to collaborate",
@@ -1547,5 +1547,86 @@
"contextual.workflows.sharing.unavailable.button.desktop": "View plans",
"contextual.upgradeLinkUrl": "https://subscription.n8n.io/",
"contextual.upgradeLinkUrl.cloud": "https://app.n8n.cloud/manage?edition=cloud",
"contextual.upgradeLinkUrl.desktop": "https://n8n.io/pricing"
"contextual.upgradeLinkUrl.desktop": "https://n8n.io/pricing",
"settings.ldap": "LDAP",
"settings.ldap.infoTip": "Learn more about <a href='https://docs.n8n.io/user-management/ldap/' target='_blank'>LDAP in the Docs</a>",
"settings.ldap.save": "Save connection",
"settings.ldap.connectionTestError": "Problem testing LDAP connection",
"settings.ldap.connectionTest": "LDAP connection tested",
"settings.ldap.runSync.title": "LDAP synchronization done",
"settings.ldap.runSync.showError.message": "Problem during synchronization. Check the logs",
"settings.ldap.updateConfiguration": "LDAP configuration updated",
"settings.ldap.testingConnection": "Testing connection",
"settings.ldap.testConnection": "Test connection",
"settings.ldap.synchronizationTable.column.status": "Status",
"settings.ldap.synchronizationTable.column.endedAt": "Ended At",
"settings.ldap.synchronizationTable.column.runMode": "Run Mode",
"settings.ldap.synchronizationTable.column.runTime": "Run Time",
"settings.ldap.synchronizationTable.column.details": "Details",
"settings.ldap.synchronizationTable.empty.message": "Test synchronization to preview updates",
"settings.ldap.dryRun": "Test synchronization",
"settings.ldap.synchronizeNow": "Run synchronization",
"settings.ldap.synchronizationError": "LDAP Synchronization Error",
"settings.ldap.configurationError": "LDAP Configuration Error",
"settings.ldap.usersScanned": "Users scanned {scanned}",
"settings.ldap.confirmMessage.beforeSaveForm.cancelButtonText": "Yes, disable it",
"settings.ldap.confirmMessage.beforeSaveForm.confirmButtonText": "No",
"settings.ldap.confirmMessage.beforeSaveForm.headline": "Are you sure you want to disable LDAP login?",
"settings.ldap.confirmMessage.beforeSaveForm.message": "If you do so, all LDAP users will be converted to email users.",
"settings.ldap.disabled.title": "Available in custom plans",
"settings.ldap.disabled.description": "LDAP is available as a paid feature. Get in touch to learn more about it.",
"settings.ldap.disabled.buttonText": "Contact us",
"settings.ldap.toast.sync.success": "Synchronization succeeded",
"settings.ldap.toast.connection.success": "Connection succeeded",
"settings.ldap.form.loginEnabled.label": "Enable LDAP Login",
"settings.ldap.form.loginEnabled.tooltip": "Connection settings and data will still be saved if you disable LDAP Login",
"settings.ldap.form.loginLabel.label": "LDAP Login",
"settings.ldap.form.loginLabel.placeholder": "e.g. LDAP Username or email address",
"settings.ldap.form.loginLabel.infoText": "The placeholder text that appears in the login field on the login page",
"settings.ldap.form.serverAddress.label": "LDAP Server Address",
"settings.ldap.form.serverAddress.placeholder": "123.123.123.123",
"settings.ldap.form.serverAddress.infoText": "IP or domain of the LDAP server",
"settings.ldap.form.port.label": "LDAP Server Port",
"settings.ldap.form.port.infoText": "Port used to connect to the LDAP server",
"settings.ldap.form.connectionSecurity.label": "Connection Security",
"settings.ldap.form.connectionSecurity.infoText": "Type of connection security",
"settings.ldap.form.allowUnauthorizedCerts.label": "Ignore SSL/TLS Issues",
"settings.ldap.form.baseDn.label": "Base DN",
"settings.ldap.form.baseDn.placeholder": "o=acme,dc=example,dc=com",
"settings.ldap.form.baseDn.infoText": "Distinguished Name of the location where n8n should start its search for user in the AD/LDAP tree",
"settings.ldap.form.bindingType.label": "Binding as",
"settings.ldap.form.bindingType.infoText": "Type of binding used to connection to the LDAP server",
"settings.ldap.form.adminDn.label": "Binding DN",
"settings.ldap.form.adminDn.placeholder": "uid=2da2de69435c,ou=Users,o=Acme,dc=com",
"settings.ldap.form.adminDn.infoText": "Distinguished Name of the user to perform the search",
"settings.ldap.form.adminPassword.label": "Binding Password",
"settings.ldap.form.adminPassword.infoText": "Password of the user provided in the Binding DN field above",
"settings.ldap.form.userFilter.label": "User Filter",
"settings.ldap.form.userFilter.placeholder": "(ObjectClass=user)",
"settings.ldap.form.userFilter.infoText": "LDAP query to use when searching for user. Only users returned by this filter will be allowed to sign-in in n8n",
"settings.ldap.form.attributeMappingInfo.label": "Attribute mapping",
"settings.ldap.form.ldapId.label": "ID",
"settings.ldap.form.ldapId.placeholder": "uid",
"settings.ldap.form.ldapId.infoText": "The attribute in the LDAP server used as a unique identifier in n8n. It should be an unique LDAP attribute like uid",
"settings.ldap.form.loginId.label": "Login ID",
"settings.ldap.form.loginId.placeholder": "mail",
"settings.ldap.form.loginId.infoText": "The attribute in the LDAP server used to log-in in n8n",
"settings.ldap.form.email.label": "Email",
"settings.ldap.form.email.placeholder": "mail",
"settings.ldap.form.email.infoText": "The attribute in the LDAP server used to populate the email in n8n",
"settings.ldap.form.firstName.label": "First Name",
"settings.ldap.form.firstName.placeholder": "givenName",
"settings.ldap.form.firstName.infoText": "The attribute in the LDAP server used to populate the first name in n8n",
"settings.ldap.form.lastName.label": "Last Name",
"settings.ldap.form.lastName.placeholder": "sn",
"settings.ldap.form.lastName.infoText": "The attribute in the LDAP server used to populate the last name in n8n",
"settings.ldap.form.synchronizationEnabled.label": "Enable periodic LDAP synchronization",
"settings.ldap.form.synchronizationEnabled.tooltip": "Enable users to be synchronized periodically",
"settings.ldap.form.synchronizationInterval.label": "Synchronization Interval (Minutes)",
"settings.ldap.form.synchronizationInterval.infoText": "How often the synchronization should run",
"settings.ldap.form.pageSize.label": "Page Size",
"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"
}

View File

@@ -120,6 +120,7 @@ import {
faUserFriends,
faUsers,
faVideo,
faTree,
faStickyNote as faSolidStickyNote,
} from '@fortawesome/free-solid-svg-icons';
import { faStickyNote } from '@fortawesome/free-regular-svg-icons';
@@ -250,5 +251,6 @@ addIcon(faUserCircle);
addIcon(faUserFriends);
addIcon(faUsers);
addIcon(faVideo);
addIcon(faTree);
Vue.component('font-awesome-icon', FontAwesomeIcon);

View File

@@ -10,6 +10,7 @@ import WorkflowExecutionsList from '@/components/ExecutionsView/ExecutionsList.v
import ExecutionsLandingPage from '@/components/ExecutionsView/ExecutionsLandingPage.vue';
import ExecutionPreview from '@/components/ExecutionsView/ExecutionPreview.vue';
import SettingsView from './views/SettingsView.vue';
import SettingsLdapView from './views/SettingsLdapView.vue';
import SettingsPersonalView from './views/SettingsPersonalView.vue';
import SettingsUsersView from './views/SettingsUsersView.vue';
import SettingsCommunityNodesView from './views/SettingsCommunityNodesView.vue';
@@ -30,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 { VIEWS } from './constants';
import { EnterpriseEditionFeature, VIEWS } from './constants';
import { useSettingsStore } from './stores/settings';
import { useTemplatesStore } from './stores/templates';
import SettingsUsageAndPlanVue from './views/SettingsUsageAndPlan.vue';
@@ -606,6 +607,20 @@ const router = new Router({
},
},
},
{
path: 'ldap',
name: VIEWS.LDAP_SETTINGS,
components: {
settingsView: SettingsLdapView,
},
meta: {
permissions: {
allow: {
role: [ROLE.Owner],
},
},
},
},
],
},
{

View File

@@ -1,4 +1,11 @@
import { createApiKey, deleteApiKey, getApiKey } from '@/api/api-keys';
import {
getLdapConfig,
getLdapSynchronizations,
runLdapSync,
testLdapConnection,
updateLdapConfig,
} from '@/api/ldap';
import { getPromptsData, getSettings, submitContactInfo, submitValueSurvey } from '@/api/settings';
import { testHealthEndpoint } from '@/api/templates';
import {
@@ -15,6 +22,7 @@ import {
IN8nValueSurveyData,
ISettingsState,
WorkflowCallerPolicyDefaultOption,
ILdapConfig,
} from '@/Interface';
import { ITelemetrySettings } from 'n8n-workflow';
import { defineStore } from 'pinia';
@@ -42,6 +50,10 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
enabled: false,
},
},
ldap: {
loginLabel: '',
loginEnabled: false,
},
onboardingCallPromptEnabled: false,
saveDataErrorExecution: 'all',
saveDataSuccessExecution: 'all',
@@ -69,6 +81,12 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
publicApiPath(): string {
return this.api.path;
},
isLdapLoginEnabled(): boolean {
return this.ldap.loginEnabled;
},
ldapLoginLabel(): string {
return this.ldap.loginLabel;
},
showSetupPage(): boolean {
return this.userManagement.showSetupOnFirstLoad === true;
},
@@ -147,6 +165,8 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
this.userManagement.smtpSetup = settings.userManagement.smtpSetup;
this.api = settings.publicApi;
this.onboardingCallPromptEnabled = settings.onboardingCallPromptEnabled;
this.ldap.loginEnabled = settings.ldap.loginEnabled;
this.ldap.loginLabel = settings.ldap.loginLabel;
},
async getSettings(): Promise<void> {
const rootStore = useRootStore();
@@ -253,6 +273,26 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, {
const rootStore = useRootStore();
await deleteApiKey(rootStore.getRestApiContext);
},
async getLdapConfig() {
const rootStore = useRootStore();
return await getLdapConfig(rootStore.getRestApiContext);
},
async getLdapSynchronizations(pagination: { page: number }) {
const rootStore = useRootStore();
return await getLdapSynchronizations(rootStore.getRestApiContext, pagination);
},
async testLdapConnection() {
const rootStore = useRootStore();
return await testLdapConnection(rootStore.getRestApiContext);
},
async updateLdapConfig(ldapConfig: ILdapConfig) {
const rootStore = useRootStore();
return await updateLdapConfig(rootStore.getRestApiContext, ldapConfig);
},
async runLdapSync(data: IDataObject) {
const rootStore = useRootStore();
return await runLdapSync(rootStore.getRestApiContext, data);
},
setSaveDataErrorExecution(newValue: string) {
Vue.set(this, 'saveDataErrorExecution', newValue);
},

View File

@@ -38,6 +38,7 @@ import { useUIStore } from './ui';
const isDefaultUser = (user: IUserResponse | null) =>
Boolean(user && user.isPending && user.globalRole && user.globalRole.name === ROLE.Owner);
const isPendingUser = (user: IUserResponse | null) => Boolean(user && user.isPending);
export const useUsersStore = defineStore(STORES.USERS, {
@@ -58,8 +59,8 @@ export const useUsersStore = defineStore(STORES.USERS, {
getUserById(state) {
return (userId: string): IUser | null => state.users[userId];
},
globalRoleName(): string {
return this.currentUser?.globalRole?.name || '';
globalRoleName(): IRole {
return this.currentUser?.globalRole?.name ?? 'default';
},
canUserDeleteTags(): boolean {
return isAuthorized(PERMISSIONS.TAGS.CAN_DELETE_TAGS, this.currentUser);
@@ -116,7 +117,7 @@ export const useUsersStore = defineStore(STORES.USERS, {
: undefined,
isDefaultUser: isDefaultUser(updatedUser),
isPendingUser: isPendingUser(updatedUser),
isOwner: Boolean(updatedUser.globalRole && updatedUser.globalRole.name === ROLE.Owner),
isOwner: updatedUser.globalRole?.name === ROLE.Owner,
};
Vue.set(this.users, user.id, user);
});

View File

@@ -59,3 +59,7 @@ export function isChildOf(parent: Element, child: Element): boolean {
return isChildOf(parent, child.parentElement);
}
export const capitalizeFirstLetter = (text: string): string => {
return text.charAt(0).toUpperCase() + text.slice(1);
};

View File

@@ -142,7 +142,7 @@ export const isAuthorized = (permissions: IPermissions, currentUser: IUser | nul
return false;
}
if (currentUser && currentUser.globalRole) {
if (currentUser?.globalRole?.name) {
const role = currentUser.isDefaultUser ? ROLE.Default : currentUser.globalRole.name;
if (permissions.deny.role && permissions.deny.role.includes(role)) {
return false;
@@ -163,7 +163,7 @@ export const isAuthorized = (permissions: IPermissions, currentUser: IUser | nul
return true;
}
if (currentUser && currentUser.globalRole) {
if (currentUser?.globalRole?.name) {
const role = currentUser.isDefaultUser ? ROLE.Default : currentUser.globalRole.name;
if (permissions.allow.role && permissions.allow.role.includes(role)) {
return true;

View File

@@ -82,10 +82,14 @@ export default mixins(showMessage).extend({
}),
});
} catch (error) {
let message = this.$locale.baseText('forgotPassword.smtpErrorContactAdministrator');
if (error.httpStatusCode === 422) {
message = this.$locale.baseText(error.message);
}
this.$showMessage({
type: 'error',
title: this.$locale.baseText('forgotPassword.sendingEmailError'),
message: this.$locale.baseText('forgotPassword.smtpErrorContactAdministrator'),
message,
});
}
this.loading = false;

View File

@@ -0,0 +1,786 @@
<template>
<div v-if="!isLDAPFeatureEnabled">
<div :class="[$style.header, 'mb-2xl']">
<n8n-heading size="2xlarge">
{{ $locale.baseText('settings.ldap') }}
</n8n-heading>
</div>
<n8n-info-tip type="note" theme="info-light" tooltipPlacement="right">
<div>
LDAP allows users to authenticate with their centralized account. It's compatible with
services that provide an LDAP interface like Active Directory, Okta and Jumpcloud.
</div>
<br />
</n8n-info-tip>
<n8n-action-box
:description="$locale.baseText('settings.ldap.disabled.description')"
:buttonText="$locale.baseText('settings.ldap.disabled.buttonText')"
@click="onContactUsClick"
>
<template #heading>
<span>{{ $locale.baseText('settings.ldap.disabled.title') }}</span>
</template>
</n8n-action-box>
</div>
<div v-else>
<div :class="$style.container">
<div :class="$style.header">
<n8n-heading size="2xlarge">
{{ $locale.baseText('settings.ldap') }}
</n8n-heading>
</div>
<div :class="$style.docsInfoTip">
<n8n-info-tip theme="info" type="note">
<template>
<span v-html="$locale.baseText('settings.ldap.infoTip')"></span>
</template>
</n8n-info-tip>
</div>
<div :class="$style.settingsForm">
<n8n-form-inputs
v-if="formInputs"
ref="ldapConfigForm"
:inputs="formInputs"
:eventBus="formBus"
:columnView="true"
verticalSpacing="l"
@input="onInput"
@ready="onReadyToSubmit"
@submit="onSubmit"
/>
</div>
<div>
<n8n-button
v-if="loginEnabled"
:label="
loadingTestConnection
? $locale.baseText('settings.ldap.testingConnection')
: $locale.baseText('settings.ldap.testConnection')
"
size="large"
class="mr-s"
:disabled="hasAnyChanges || !readyToSubmit"
:loading="loadingTestConnection"
@click="onTestConnectionClick"
/>
<n8n-button
:label="$locale.baseText('settings.ldap.save')"
size="large"
:disabled="!hasAnyChanges || !readyToSubmit"
@click="onSaveClick"
/>
</div>
</div>
<div v-if="loginEnabled">
<n8n-heading tag="h1" class="mb-xl mt-3xl" size="medium">{{
$locale.baseText('settings.ldap.section.synchronization.title')
}}</n8n-heading>
<div :class="$style.syncTable">
<el-table
v-loading="loadingTable"
:border="true"
:stripe="true"
:data="dataTable"
:cell-style="cellClassStyle"
style="width: 100%"
height="250"
:key="tableKey"
>
<el-table-column
prop="status"
:label="$locale.baseText('settings.ldap.synchronizationTable.column.status')"
>
</el-table-column>
<el-table-column
prop="endedAt"
:label="$locale.baseText('settings.ldap.synchronizationTable.column.endedAt')"
>
</el-table-column>
<el-table-column
prop="runMode"
:label="$locale.baseText('settings.ldap.synchronizationTable.column.runMode')"
>
</el-table-column>
<el-table-column
prop="runTime"
:label="$locale.baseText('settings.ldap.synchronizationTable.column.runTime')"
>
</el-table-column>
<el-table-column
prop="details"
:label="$locale.baseText('settings.ldap.synchronizationTable.column.details')"
>
</el-table-column>
<template #empty>{{
$locale.baseText('settings.ldap.synchronizationTable.empty.message')
}}</template>
<template #append>
<infinite-loading
@infinite="getLdapSynchronizations"
force-use-infinite-wrapper=".el-table__body-wrapper"
>
</infinite-loading>
</template>
</el-table>
</div>
<div class="pb-3xl">
<n8n-button
:label="$locale.baseText('settings.ldap.dryRun')"
type="secondary"
size="large"
class="mr-s"
:disabled="hasAnyChanges || !readyToSubmit"
:loading="loadingDryRun"
@click="onDryRunClick"
/>
<n8n-button
:label="$locale.baseText('settings.ldap.synchronizeNow')"
size="large"
:disabled="hasAnyChanges || !readyToSubmit"
:loading="loadingLiveRun"
@click="onLiveRunClick"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { convertToDisplayDate } from '@/utils';
import { showMessage } from '@/mixins/showMessage';
import {
ILdapConfig,
ILdapSyncData,
ILdapSyncTable,
IFormInput,
IFormInputs,
IUser,
} from '@/Interface';
import Vue from 'vue';
import mixins from 'vue-typed-mixins';
import humanizeDuration from 'humanize-duration';
import { rowCallbackParams, cellCallbackParams, ElTable } from 'element-ui/types/table';
import { capitalizeFirstLetter } from '@/utils';
import InfiniteLoading from 'vue-infinite-loading';
import { mapStores } from 'pinia';
import { useUsersStore } from '@/stores/users';
import { useSettingsStore } from '@/stores/settings';
import { getLdapSynchronizations } from '@/api/ldap';
import { N8N_CONTACT_EMAIL, N8N_SALES_EMAIL } from '@/constants';
import { ElTableColumn } from 'element-ui/types/table-column';
type FormValues = {
loginEnabled: boolean;
loginLabel: string;
serverAddress: string;
baseDn: string;
bindingType: string;
adminDn: string;
adminPassword: string;
loginId: string;
email: string;
lastName: string;
firstName: string;
ldapId: string;
synchronizationEnabled: boolean;
allowUnauthorizedCerts: boolean;
synchronizationInterval: number;
userFilter: string;
pageSize: number;
searchTimeout: number;
port: number;
connectionSecurity: string;
};
type tableRow = {
status: string;
startAt: string;
endedAt: string;
error: string;
runMode: string;
};
type rowType = rowCallbackParams & tableRow;
type cellType = cellCallbackParams & { property: keyof tableRow };
export default mixins(showMessage).extend({
name: 'SettingsLdapView',
components: {
InfiniteLoading,
},
data() {
return {
dataTable: [] as ILdapSyncTable[],
tableKey: 0,
adConfig: {} as ILdapConfig,
loadingTestConnection: false,
loadingDryRun: false,
loadingLiveRun: false,
loadingTable: false,
hasAnyChanges: false,
formInputs: null as null | IFormInputs,
formBus: new Vue(),
readyToSubmit: false,
page: 0,
loginEnabled: false,
syncEnabled: false,
};
},
async mounted() {
if (!this.isLDAPFeatureEnabled) return;
await this.getLdapConfig();
},
computed: {
...mapStores(useUsersStore, useSettingsStore),
currentUser(): null | IUser {
return this.usersStore.currentUser;
},
isLDAPFeatureEnabled(): boolean {
return this.settingsStore.settings.enterprise.ldap === true;
},
},
methods: {
onContactUsClick(event: MouseEvent): void {
const email = this.settingsStore.isCloudDeployment ? N8N_CONTACT_EMAIL : N8N_SALES_EMAIL;
location.href = `mailto:${email}`;
},
cellClassStyle({ row, column }: { row: rowType; column: cellType }) {
if (column.property === 'status') {
if (row.status === 'Success') {
return { color: 'green' };
} else if (row.status === 'Error') {
return { color: 'red' };
}
}
if (column.property === 'runMode') {
if (row.runMode === 'Dry') {
return { color: 'orange' };
} else if (row.runMode === 'Live') {
return { color: 'blue' };
}
}
return {};
},
onInput(input: { name: string; value: string | number | boolean }) {
if (input.name === 'loginEnabled' && typeof input.value === 'boolean') {
this.loginEnabled = input.value;
}
if (input.name === 'synchronizationEnabled' && typeof input.value === 'boolean') {
this.syncEnabled = input.value;
}
this.hasAnyChanges = true;
},
onReadyToSubmit(ready: boolean) {
this.readyToSubmit = ready;
},
syncDataMapper(sync: ILdapSyncData): ILdapSyncTable {
const startedAt = new Date(sync.startedAt);
const endedAt = new Date(sync.endedAt);
const runTimeInMinutes = endedAt.getTime() - startedAt.getTime();
return {
runTime: humanizeDuration(runTimeInMinutes),
runMode: capitalizeFirstLetter(sync.runMode),
status: capitalizeFirstLetter(sync.status),
endedAt: convertToDisplayDate(endedAt.getTime()),
details: this.$locale.baseText('settings.ldap.usersScanned', {
interpolate: {
scanned: sync.scanned.toString(),
},
}),
};
},
async onSubmit(): Promise<void> {
// We want to save all form values (incl. the hidden onces), so we are using
// `values` data prop of the `FormInputs` child component since they are all preserved there
const formInputs = this.$refs.ldapConfigForm as (Vue & { values: FormValues }) | undefined;
if (!this.hasAnyChanges || !formInputs) {
return;
}
const newConfiguration: ILdapConfig = {
loginEnabled: formInputs.values.loginEnabled,
loginLabel: formInputs.values.loginLabel,
connectionUrl: formInputs.values.serverAddress,
allowUnauthorizedCerts: formInputs.values.allowUnauthorizedCerts,
connectionPort: +formInputs.values.port,
connectionSecurity: formInputs.values.connectionSecurity,
baseDn: formInputs.values.baseDn,
bindingAdminDn: formInputs.values.bindingType === 'admin' ? formInputs.values.adminDn : '',
bindingAdminPassword:
formInputs.values.bindingType === 'admin' ? formInputs.values.adminPassword : '',
emailAttribute: formInputs.values.email,
firstNameAttribute: formInputs.values.firstName,
lastNameAttribute: formInputs.values.lastName,
loginIdAttribute: formInputs.values.loginId,
ldapIdAttribute: formInputs.values.ldapId,
userFilter: formInputs.values.userFilter,
synchronizationEnabled: formInputs.values.synchronizationEnabled,
synchronizationInterval: +formInputs.values.synchronizationInterval,
searchPageSize: +formInputs.values.pageSize,
searchTimeout: +formInputs.values.searchTimeout,
};
let saveForm = true;
try {
if (this.adConfig.loginEnabled === true && newConfiguration.loginEnabled === false) {
saveForm = await this.confirmMessage(
this.$locale.baseText('settings.ldap.confirmMessage.beforeSaveForm.message'),
this.$locale.baseText('settings.ldap.confirmMessage.beforeSaveForm.headline'),
null,
this.$locale.baseText('settings.ldap.confirmMessage.beforeSaveForm.cancelButtonText'),
this.$locale.baseText('settings.ldap.confirmMessage.beforeSaveForm.confirmButtonText'),
);
}
if (!saveForm) {
this.hasAnyChanges = true;
}
this.adConfig = await this.settingsStore.updateLdapConfig(newConfiguration);
this.$showToast({
title: this.$locale.baseText('settings.ldap.updateConfiguration'),
message: '',
type: 'success',
});
} catch (error) {
this.$showError(error, this.$locale.baseText('settings.ldap.configurationError'));
} finally {
if (saveForm) {
this.hasAnyChanges = false;
}
}
},
onSaveClick() {
this.formBus.$emit('submit');
},
async onTestConnectionClick() {
this.loadingTestConnection = true;
try {
await this.settingsStore.testLdapConnection();
this.$showToast({
title: this.$locale.baseText('settings.ldap.connectionTest'),
message: this.$locale.baseText('settings.ldap.toast.connection.success'),
type: 'success',
});
} catch (error) {
this.$showToast({
title: this.$locale.baseText('settings.ldap.connectionTestError'),
message: error.message,
type: 'error',
});
} finally {
this.loadingTestConnection = false;
}
},
async onDryRunClick() {
this.loadingDryRun = true;
try {
await this.settingsStore.runLdapSync({ type: 'dry' });
this.$showToast({
title: this.$locale.baseText('settings.ldap.runSync.title'),
message: this.$locale.baseText('settings.ldap.toast.sync.success'),
type: 'success',
});
} catch (error) {
this.$showError(error, this.$locale.baseText('settings.ldap.synchronizationError'));
} finally {
this.loadingDryRun = false;
await this.reloadLdapSynchronizations();
}
},
async onLiveRunClick() {
this.loadingLiveRun = true;
try {
await this.settingsStore.runLdapSync({ type: 'live' });
this.$showToast({
title: this.$locale.baseText('settings.ldap.runSync.title'),
message: this.$locale.baseText('settings.ldap.toast.sync.success'),
type: 'success',
});
} catch (error) {
this.$showError(error, this.$locale.baseText('settings.ldap.synchronizationError'));
} finally {
this.loadingLiveRun = false;
await this.reloadLdapSynchronizations();
}
},
async getLdapConfig() {
try {
this.adConfig = await this.settingsStore.getLdapConfig();
this.loginEnabled = this.adConfig.loginEnabled;
this.syncEnabled = this.adConfig.synchronizationEnabled;
const whenLoginEnabled: IFormInput['shouldDisplay'] = (values) =>
values['loginEnabled'] === true;
const whenSyncAndLoginEnabled: IFormInput['shouldDisplay'] = (values) =>
values['synchronizationEnabled'] === true && values['loginEnabled'] === true;
const whenAdminBindingAndLoginEnabled: IFormInput['shouldDisplay'] = (values) =>
values['bindingType'] === 'admin' && values['loginEnabled'] === true;
this.formInputs = [
{
name: 'loginEnabled',
initialValue: this.adConfig.loginEnabled,
properties: {
type: 'toggle',
label: this.$locale.baseText('settings.ldap.form.loginEnabled.label'),
tooltipText: this.$locale.baseText('settings.ldap.form.loginEnabled.tooltip'),
required: true,
},
},
{
name: 'loginLabel',
initialValue: this.adConfig.loginLabel,
properties: {
label: this.$locale.baseText('settings.ldap.form.loginLabel.label'),
required: true,
placeholder: this.$locale.baseText('settings.ldap.form.loginLabel.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.loginLabel.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'serverAddress',
initialValue: this.adConfig.connectionUrl,
properties: {
label: this.$locale.baseText('settings.ldap.form.serverAddress.label'),
required: true,
capitalize: true,
placeholder: this.$locale.baseText('settings.ldap.form.serverAddress.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.serverAddress.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'port',
initialValue: this.adConfig.connectionPort,
properties: {
type: 'number',
label: this.$locale.baseText('settings.ldap.form.port.label'),
capitalize: true,
infoText: this.$locale.baseText('settings.ldap.form.port.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'connectionSecurity',
initialValue: this.adConfig.connectionSecurity,
properties: {
type: 'select',
label: this.$locale.baseText('settings.ldap.form.connectionSecurity.label'),
infoText: this.$locale.baseText('settings.ldap.form.connectionSecurity.infoText'),
options: [
{
label: 'None',
value: 'none',
},
{
label: 'TLS',
value: 'tls',
},
{
label: 'STARTTLS',
value: 'startTls',
},
],
required: true,
capitalize: true,
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'allowUnauthorizedCerts',
initialValue: this.adConfig.allowUnauthorizedCerts,
properties: {
type: 'toggle',
label: this.$locale.baseText('settings.ldap.form.allowUnauthorizedCerts.label'),
required: false,
},
shouldDisplay(values): boolean {
return values['connectionSecurity'] !== 'none' && values['loginEnabled'] === true;
},
},
{
name: 'baseDn',
initialValue: this.adConfig.baseDn,
properties: {
label: this.$locale.baseText('settings.ldap.form.baseDn.label'),
required: true,
capitalize: true,
placeholder: this.$locale.baseText('settings.ldap.form.baseDn.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.baseDn.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'bindingType',
initialValue: 'admin',
properties: {
type: 'select',
label: this.$locale.baseText('settings.ldap.form.bindingType.label'),
infoText: this.$locale.baseText('settings.ldap.form.bindingType.infoText'),
options: [
{
value: 'admin',
label: 'Admin',
},
{
value: 'anonymous',
label: 'Anonymous',
},
],
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'adminDn',
initialValue: this.adConfig.bindingAdminDn,
properties: {
label: this.$locale.baseText('settings.ldap.form.adminDn.label'),
placeholder: this.$locale.baseText('settings.ldap.form.adminDn.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.adminDn.infoText'),
capitalize: true,
},
shouldDisplay: whenAdminBindingAndLoginEnabled,
},
{
name: 'adminPassword',
initialValue: this.adConfig.bindingAdminPassword,
properties: {
label: this.$locale.baseText('settings.ldap.form.adminPassword.label'),
type: 'password',
capitalize: true,
infoText: this.$locale.baseText('settings.ldap.form.adminPassword.infoText'),
},
shouldDisplay: whenAdminBindingAndLoginEnabled,
},
{
name: 'userFilter',
initialValue: this.adConfig.userFilter,
properties: {
label: this.$locale.baseText('settings.ldap.form.userFilter.label'),
type: 'text',
required: false,
capitalize: true,
placeholder: this.$locale.baseText('settings.ldap.form.userFilter.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.userFilter.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'attributeMappingInfo',
properties: {
label: this.$locale.baseText('settings.ldap.form.attributeMappingInfo.label'),
type: 'info',
labelSize: 'large',
labelAlignment: 'left',
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'ldapId',
initialValue: this.adConfig.ldapIdAttribute,
properties: {
label: this.$locale.baseText('settings.ldap.form.ldapId.label'),
type: 'text',
required: true,
capitalize: true,
placeholder: this.$locale.baseText('settings.ldap.form.ldapId.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.ldapId.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'loginId',
initialValue: this.adConfig.loginIdAttribute,
properties: {
label: this.$locale.baseText('settings.ldap.form.loginId.label'),
type: 'text',
autocomplete: 'email',
required: true,
capitalize: true,
placeholder: this.$locale.baseText('settings.ldap.form.loginId.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.loginId.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'email',
initialValue: this.adConfig.emailAttribute,
properties: {
label: this.$locale.baseText('settings.ldap.form.email.label'),
type: 'text',
autocomplete: 'email',
required: true,
capitalize: true,
placeholder: this.$locale.baseText('settings.ldap.form.email.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.email.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'firstName',
initialValue: this.adConfig.firstNameAttribute,
properties: {
label: this.$locale.baseText('settings.ldap.form.firstName.label'),
type: 'text',
autocomplete: 'email',
required: true,
capitalize: true,
placeholder: this.$locale.baseText('settings.ldap.form.firstName.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.firstName.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'lastName',
initialValue: this.adConfig.lastNameAttribute,
properties: {
label: this.$locale.baseText('settings.ldap.form.lastName.label'),
type: 'text',
autocomplete: 'email',
required: true,
capitalize: true,
placeholder: this.$locale.baseText('settings.ldap.form.lastName.placeholder'),
infoText: this.$locale.baseText('settings.ldap.form.lastName.infoText'),
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'synchronizationEnabled',
initialValue: this.adConfig.synchronizationEnabled,
properties: {
type: 'toggle',
label: this.$locale.baseText('settings.ldap.form.synchronizationEnabled.label'),
tooltipText: this.$locale.baseText(
'settings.ldap.form.synchronizationEnabled.tooltip',
),
required: true,
},
shouldDisplay: whenLoginEnabled,
},
{
name: 'synchronizationInterval',
initialValue: this.adConfig.synchronizationInterval,
properties: {
type: 'number',
label: this.$locale.baseText('settings.ldap.form.synchronizationInterval.label'),
infoText: this.$locale.baseText(
'settings.ldap.form.synchronizationInterval.infoText',
),
},
shouldDisplay: whenSyncAndLoginEnabled,
},
{
name: 'pageSize',
initialValue: this.adConfig.searchPageSize,
properties: {
type: 'number',
label: this.$locale.baseText('settings.ldap.form.pageSize.label'),
infoText: this.$locale.baseText('settings.ldap.form.pageSize.infoText'),
},
shouldDisplay: whenSyncAndLoginEnabled,
},
{
name: 'searchTimeout',
initialValue: this.adConfig.searchTimeout,
properties: {
type: 'number',
label: this.$locale.baseText('settings.ldap.form.searchTimeout.label'),
infoText: this.$locale.baseText('settings.ldap.form.searchTimeout.infoText'),
},
shouldDisplay: whenSyncAndLoginEnabled,
},
];
} catch (error) {
this.$showError(error, this.$locale.baseText('settings.ldap.configurationError'));
}
},
async getLdapSynchronizations(state: any) {
try {
this.loadingTable = true;
const data = await this.settingsStore.getLdapSynchronizations({
page: this.page,
});
if (data.length !== 0) {
this.dataTable.push(...data.map(this.syncDataMapper));
this.page += 1;
state.loaded();
} else {
state.complete();
}
this.loadingTable = false;
} catch (error) {
this.$showError(error, this.$locale.baseText('settings.ldap.synchronizationError'));
}
},
async reloadLdapSynchronizations() {
try {
this.page = 0;
this.tableKey += 1;
this.dataTable = [];
} catch (error) {
this.$showError(error, this.$locale.baseText('settings.ldap.synchronizationError'));
}
},
},
});
</script>
<style lang="scss" module>
.container {
> * {
margin-bottom: var(--spacing-2xl);
}
}
.syncTable {
margin-bottom: var(--spacing-2xl);
}
.header {
display: flex;
align-items: center;
white-space: nowrap;
*:first-child {
flex-grow: 1;
}
}
.enableFeatureContainer {
margin-bottom: var(--spacing-1xl);
}
.enableFeatureContainer > span {
font-size: var(--font-size-s);
font-weight: var(--font-weight-bold);
padding: 0;
}
.enableFeatureContainer {
> * {
padding: 0.5em;
}
}
.sectionHeader {
margin-bottom: var(--spacing-s);
}
.settingsForm {
:global(.form-text) {
margin-top: var(--spacing-xl);
}
}
.docsInfoTip {
&,
& > div {
margin-bottom: var(--spacing-xl);
}
}
</style>

View File

@@ -32,7 +32,7 @@
/>
</div>
</div>
<div>
<div v-if="!signInWithLdap">
<div :class="$style.sectionHeader">
<n8n-heading size="large">{{ $locale.baseText('settings.personal.security') }}</n8n-heading>
</div>
@@ -58,10 +58,11 @@
<script lang="ts">
import { showMessage } from '@/mixins/showMessage';
import { CHANGE_PASSWORD_MODAL_KEY } from '@/constants';
import { CHANGE_PASSWORD_MODAL_KEY, SignInType } from '@/constants';
import { IFormInputs, IUser } from '@/Interface';
import { useUIStore } from '@/stores/ui';
import { useUsersStore } from '@/stores/users';
import { useSettingsStore } from '@/stores/settings';
import { mapStores } from 'pinia';
import Vue from 'vue';
import mixins from 'vue-typed-mixins';
@@ -87,6 +88,7 @@ export default mixins(showMessage).extend({
required: true,
autocomplete: 'given-name',
capitalize: true,
disabled: this.isLDAPFeatureEnabled && this.signInWithLdap,
},
},
{
@@ -98,6 +100,7 @@ export default mixins(showMessage).extend({
required: true,
autocomplete: 'family-name',
capitalize: true,
disabled: this.isLDAPFeatureEnabled && this.signInWithLdap,
},
},
{
@@ -110,15 +113,22 @@ export default mixins(showMessage).extend({
validationRules: [{ name: 'VALID_EMAIL' }],
autocomplete: 'email',
capitalize: true,
disabled: this.isLDAPFeatureEnabled && this.signInWithLdap,
},
},
];
},
computed: {
...mapStores(useUIStore, useUsersStore),
...mapStores(useUIStore, useUsersStore, useSettingsStore),
currentUser(): IUser | null {
return this.usersStore.currentUser;
},
signInWithLdap(): boolean {
return this.currentUser?.signInType === 'ldap';
},
isLDAPFeatureEnabled(): boolean {
return this.settingsStore.settings.enterprise.ldap === true;
},
},
methods: {
onInput() {

View File

@@ -16,6 +16,7 @@ import { IFormBoxConfig } from '@/Interface';
import { VIEWS } from '@/constants';
import { mapStores } from 'pinia';
import { useUsersStore } from '@/stores/users';
import { useSettingsStore } from '@/stores/settings';
export default mixins(showMessage).extend({
name: 'SigninView',
@@ -23,7 +24,22 @@ export default mixins(showMessage).extend({
AuthView,
},
data() {
const FORM_CONFIG: IFormBoxConfig = {
return {
FORM_CONFIG: {} as IFormBoxConfig,
loading: false,
};
},
computed: {
...mapStores(useUsersStore, useSettingsStore),
},
mounted() {
let emailLabel = this.$locale.baseText('auth.email');
const ldapLoginLabel = this.settingsStore.ldapLoginLabel;
const isLdapLoginEnabled = this.settingsStore.isLdapLoginEnabled;
if (isLdapLoginEnabled && ldapLoginLabel) {
emailLabel = ldapLoginLabel;
}
this.FORM_CONFIG = {
title: this.$locale.baseText('auth.signin'),
buttonText: this.$locale.baseText('auth.signin'),
redirectText: this.$locale.baseText('forgotPassword'),
@@ -32,10 +48,10 @@ export default mixins(showMessage).extend({
{
name: 'email',
properties: {
label: this.$locale.baseText('auth.email'),
label: emailLabel,
type: 'email',
required: true,
validationRules: [{ name: 'VALID_EMAIL' }],
...(!isLdapLoginEnabled && { validationRules: [{ name: 'VALID_EMAIL' }] }),
showRequiredAsterisk: false,
validateOnBlur: false,
autocomplete: 'email',
@@ -56,14 +72,6 @@ export default mixins(showMessage).extend({
},
],
};
return {
FORM_CONFIG,
loading: false,
};
},
computed: {
...mapStores(useUsersStore),
},
methods: {
async onSubmit(values: { [key: string]: string }) {