mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-19 11:01:15 +00:00
perf(core): Lazyload LDAP during bootup (#15907)
This commit is contained in:
@@ -1,2 +1 @@
|
|||||||
export * from './methods/email';
|
export * from './methods/email';
|
||||||
export * from './methods/ldap';
|
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
import type { User } from '@n8n/db';
|
|
||||||
import { Container } from '@n8n/di';
|
|
||||||
|
|
||||||
import { EventService } from '@/events/event.service';
|
|
||||||
import {
|
|
||||||
createLdapUserOnLocalDb,
|
|
||||||
getUserByEmail,
|
|
||||||
getAuthIdentityByLdapId,
|
|
||||||
isLdapEnabled,
|
|
||||||
mapLdapAttributesToUser,
|
|
||||||
createLdapAuthIdentity,
|
|
||||||
updateLdapUserOnLocalDb,
|
|
||||||
} from '@/ldap.ee/helpers.ee';
|
|
||||||
import { LdapService } from '@/ldap.ee/ldap.service.ee';
|
|
||||||
|
|
||||||
export const handleLdapLogin = async (
|
|
||||||
loginId: string,
|
|
||||||
password: string,
|
|
||||||
): Promise<User | undefined> => {
|
|
||||||
if (!isLdapEnabled()) return undefined;
|
|
||||||
|
|
||||||
const ldapService = Container.get(LdapService);
|
|
||||||
|
|
||||||
if (!ldapService.config.loginEnabled) return undefined;
|
|
||||||
|
|
||||||
const { loginIdAttribute, userFilter } = ldapService.config;
|
|
||||||
|
|
||||||
const ldapUser = await ldapService.findAndAuthenticateLdapUser(
|
|
||||||
loginId,
|
|
||||||
password,
|
|
||||||
loginIdAttribute,
|
|
||||||
userFilter,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ldapUser) return undefined;
|
|
||||||
|
|
||||||
const [ldapId, ldapAttributesValues] = mapLdapAttributesToUser(ldapUser, ldapService.config);
|
|
||||||
|
|
||||||
const { email: emailAttributeValue } = ldapAttributesValues;
|
|
||||||
|
|
||||||
if (!ldapId || !emailAttributeValue) return undefined;
|
|
||||||
|
|
||||||
const ldapAuthIdentity = await getAuthIdentityByLdapId(ldapId);
|
|
||||||
if (!ldapAuthIdentity) {
|
|
||||||
const emailUser = await getUserByEmail(emailAttributeValue);
|
|
||||||
|
|
||||||
// check if there is an email user with the same email as the authenticated LDAP user trying to log-in
|
|
||||||
if (emailUser && emailUser.email === emailAttributeValue) {
|
|
||||||
const identity = await createLdapAuthIdentity(emailUser, ldapId);
|
|
||||||
await updateLdapUserOnLocalDb(identity, ldapAttributesValues);
|
|
||||||
} else {
|
|
||||||
const user = await createLdapUserOnLocalDb(ldapAttributesValues, ldapId);
|
|
||||||
Container.get(EventService).emit('user-signed-up', {
|
|
||||||
user,
|
|
||||||
userType: 'ldap',
|
|
||||||
wasDisabledLdapUser: false,
|
|
||||||
});
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ldapAuthIdentity.user) {
|
|
||||||
if (ldapAuthIdentity.user.disabled) return undefined;
|
|
||||||
await updateLdapUserOnLocalDb(ldapAuthIdentity, ldapAttributesValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the user again as user's data might have been updated
|
|
||||||
return (await getAuthIdentityByLdapId(ldapId))?.user;
|
|
||||||
};
|
|
||||||
@@ -10,6 +10,7 @@ import * as auth from '@/auth';
|
|||||||
import { AuthService } from '@/auth/auth.service';
|
import { AuthService } from '@/auth/auth.service';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { EventService } from '@/events/event.service';
|
import { EventService } from '@/events/event.service';
|
||||||
|
import { LdapService } from '@/ldap.ee/ldap.service.ee';
|
||||||
import { License } from '@/license';
|
import { License } from '@/license';
|
||||||
import { MfaService } from '@/mfa/mfa.service';
|
import { MfaService } from '@/mfa/mfa.service';
|
||||||
import { PostHogClient } from '@/posthog';
|
import { PostHogClient } from '@/posthog';
|
||||||
@@ -32,6 +33,7 @@ describe('AuthController', () => {
|
|||||||
mockInstance(UserRepository);
|
mockInstance(UserRepository);
|
||||||
mockInstance(PostHogClient);
|
mockInstance(PostHogClient);
|
||||||
mockInstance(License);
|
mockInstance(License);
|
||||||
|
const ldapService = mockInstance(LdapService);
|
||||||
const controller = Container.get(AuthController);
|
const controller = Container.get(AuthController);
|
||||||
const userService = Container.get(UserService);
|
const userService = Container.get(UserService);
|
||||||
const authService = Container.get(AuthService);
|
const authService = Container.get(AuthService);
|
||||||
@@ -65,7 +67,7 @@ describe('AuthController', () => {
|
|||||||
|
|
||||||
mockedAuth.handleEmailLogin.mockResolvedValue(member);
|
mockedAuth.handleEmailLogin.mockResolvedValue(member);
|
||||||
|
|
||||||
mockedAuth.handleLdapLogin.mockResolvedValue(member);
|
ldapService.handleLdapLogin.mockResolvedValue(member);
|
||||||
|
|
||||||
config.set('userManagement.authenticationMethod', 'ldap');
|
config.set('userManagement.authenticationMethod', 'ldap');
|
||||||
|
|
||||||
@@ -79,7 +81,8 @@ describe('AuthController', () => {
|
|||||||
body.emailOrLdapLoginId,
|
body.emailOrLdapLoginId,
|
||||||
body.password,
|
body.password,
|
||||||
);
|
);
|
||||||
expect(mockedAuth.handleLdapLogin).toHaveBeenCalledWith(
|
|
||||||
|
expect(ldapService.handleLdapLogin).toHaveBeenCalledWith(
|
||||||
body.emailOrLdapLoginId,
|
body.emailOrLdapLoginId,
|
||||||
body.password,
|
body.password,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { Logger } from '@n8n/backend-common';
|
|||||||
import type { User, PublicUser } from '@n8n/db';
|
import type { User, PublicUser } from '@n8n/db';
|
||||||
import { UserRepository } from '@n8n/db';
|
import { UserRepository } from '@n8n/db';
|
||||||
import { Body, Get, Post, Query, RestController } from '@n8n/decorators';
|
import { Body, Get, Post, Query, RestController } from '@n8n/decorators';
|
||||||
|
import { Container } from '@n8n/di';
|
||||||
import { isEmail } from 'class-validator';
|
import { isEmail } from 'class-validator';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
|
|
||||||
import { handleEmailLogin, handleLdapLogin } from '@/auth';
|
import { handleEmailLogin } from '@/auth';
|
||||||
import { AuthService } from '@/auth/auth.service';
|
import { AuthService } from '@/auth/auth.service';
|
||||||
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
|
||||||
import { AuthError } from '@/errors/response-errors/auth.error';
|
import { AuthError } from '@/errors/response-errors/auth.error';
|
||||||
@@ -73,7 +74,8 @@ export class AuthController {
|
|||||||
user = preliminaryUser;
|
user = preliminaryUser;
|
||||||
usedAuthenticationMethod = 'email';
|
usedAuthenticationMethod = 'email';
|
||||||
} else {
|
} else {
|
||||||
user = await handleLdapLogin(emailOrLdapLoginId, password);
|
const { LdapService } = await import('@/ldap.ee/ldap.service.ee');
|
||||||
|
user = await Container.get(LdapService).handleLdapLogin(emailOrLdapLoginId, password);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user = await handleEmailLogin(emailOrLdapLoginId, password);
|
user = await handleEmailLogin(emailOrLdapLoginId, password);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { LdapConfig } from '@n8n/constants';
|
|||||||
import { LDAP_FEATURE_NAME } from '@n8n/constants';
|
import { LDAP_FEATURE_NAME } from '@n8n/constants';
|
||||||
import { SettingsRepository } from '@n8n/db';
|
import { SettingsRepository } from '@n8n/db';
|
||||||
import type { User, RunningMode, SyncStatus } from '@n8n/db';
|
import type { User, RunningMode, SyncStatus } from '@n8n/db';
|
||||||
import { Service } from '@n8n/di';
|
import { Service, Container } from '@n8n/di';
|
||||||
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import
|
||||||
import { QueryFailedError } from '@n8n/typeorm';
|
import { QueryFailedError } from '@n8n/typeorm';
|
||||||
import type { Entry as LdapUser, ClientOptions } from 'ldapts';
|
import type { Entry as LdapUser, ClientOptions } from 'ldapts';
|
||||||
@@ -17,14 +17,13 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
|||||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||||
import { EventService } from '@/events/event.service';
|
import { EventService } from '@/events/event.service';
|
||||||
import {
|
import {
|
||||||
getCurrentAuthenticationMethod,
|
createLdapUserOnLocalDb,
|
||||||
isEmailCurrentAuthenticationMethod,
|
getUserByEmail,
|
||||||
isLdapCurrentAuthenticationMethod,
|
getAuthIdentityByLdapId,
|
||||||
setCurrentAuthenticationMethod,
|
isLdapEnabled,
|
||||||
} from '@/sso.ee/sso-helpers';
|
mapLdapAttributesToUser,
|
||||||
|
createLdapAuthIdentity,
|
||||||
import { BINARY_AD_ATTRIBUTES, LDAP_LOGIN_ENABLED, LDAP_LOGIN_LABEL } from './constants';
|
updateLdapUserOnLocalDb,
|
||||||
import {
|
|
||||||
createFilter,
|
createFilter,
|
||||||
deleteAllLdapIdentities,
|
deleteAllLdapIdentities,
|
||||||
escapeFilter,
|
escapeFilter,
|
||||||
@@ -38,7 +37,15 @@ import {
|
|||||||
resolveEntryBinaryAttributes,
|
resolveEntryBinaryAttributes,
|
||||||
saveLdapSynchronization,
|
saveLdapSynchronization,
|
||||||
validateLdapConfigurationSchema,
|
validateLdapConfigurationSchema,
|
||||||
} from './helpers.ee';
|
} from '@/ldap.ee/helpers.ee';
|
||||||
|
import {
|
||||||
|
getCurrentAuthenticationMethod,
|
||||||
|
isEmailCurrentAuthenticationMethod,
|
||||||
|
isLdapCurrentAuthenticationMethod,
|
||||||
|
setCurrentAuthenticationMethod,
|
||||||
|
} from '@/sso.ee/sso-helpers';
|
||||||
|
|
||||||
|
import { BINARY_AD_ATTRIBUTES, LDAP_LOGIN_ENABLED, LDAP_LOGIN_LABEL } from './constants';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class LdapService {
|
export class LdapService {
|
||||||
@@ -432,4 +439,54 @@ export class LdapService {
|
|||||||
const remoteAdUserIds = remoteAdUsers.map((adUser) => adUser[this.config.ldapIdAttribute]);
|
const remoteAdUserIds = remoteAdUsers.map((adUser) => adUser[this.config.ldapIdAttribute]);
|
||||||
return localLdapIds.filter((user) => !remoteAdUserIds.includes(user));
|
return localLdapIds.filter((user) => !remoteAdUserIds.includes(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleLdapLogin(loginId: string, password: string): Promise<User | undefined> {
|
||||||
|
if (!isLdapEnabled()) return undefined;
|
||||||
|
|
||||||
|
if (!this.config.loginEnabled) return undefined;
|
||||||
|
|
||||||
|
const { loginIdAttribute, userFilter } = this.config;
|
||||||
|
|
||||||
|
const ldapUser = await this.findAndAuthenticateLdapUser(
|
||||||
|
loginId,
|
||||||
|
password,
|
||||||
|
loginIdAttribute,
|
||||||
|
userFilter,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ldapUser) return undefined;
|
||||||
|
|
||||||
|
const [ldapId, ldapAttributesValues] = mapLdapAttributesToUser(ldapUser, this.config);
|
||||||
|
|
||||||
|
const { email: emailAttributeValue } = ldapAttributesValues;
|
||||||
|
|
||||||
|
if (!ldapId || !emailAttributeValue) return undefined;
|
||||||
|
|
||||||
|
const ldapAuthIdentity = await getAuthIdentityByLdapId(ldapId);
|
||||||
|
if (!ldapAuthIdentity) {
|
||||||
|
const emailUser = await getUserByEmail(emailAttributeValue);
|
||||||
|
|
||||||
|
// check if there is an email user with the same email as the authenticated LDAP user trying to log-in
|
||||||
|
if (emailUser && emailUser.email === emailAttributeValue) {
|
||||||
|
const identity = await createLdapAuthIdentity(emailUser, ldapId);
|
||||||
|
await updateLdapUserOnLocalDb(identity, ldapAttributesValues);
|
||||||
|
} else {
|
||||||
|
const user = await createLdapUserOnLocalDb(ldapAttributesValues, ldapId);
|
||||||
|
Container.get(EventService).emit('user-signed-up', {
|
||||||
|
user,
|
||||||
|
userType: 'ldap',
|
||||||
|
wasDisabledLdapUser: false,
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (ldapAuthIdentity.user) {
|
||||||
|
if (ldapAuthIdentity.user.disabled) return undefined;
|
||||||
|
await updateLdapUserOnLocalDb(ldapAuthIdentity, ldapAttributesValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the user again as user's data might have been updated
|
||||||
|
return (await getAuthIdentityByLdapId(ldapId))?.user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user