mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
🚀 feat(LDAP): Add Flexible Configuration Options (#3124)
* chore: add detailed logs * feat: added a variable to specify which attributes to be stored * chore: Add new optiona variables * refactor: change BIND_DN as an option * chore: revert commits that fail testing * refactor: use ldapid to retrieve users * chore: remove unused variable * chore: reverting unintended changes * fix: return 404 if authentication fails, in accordance with requireLocalAuth. * fix: handling when ldap settings do not exist * chore: remove unnecessary check
This commit is contained in:
parent
a53312bbd4
commit
a8c874267f
6 changed files with 96 additions and 36 deletions
|
@ -1,17 +1,66 @@
|
|||
const fs = require('fs');
|
||||
const LdapStrategy = require('passport-ldapauth');
|
||||
const { findUser, createUser, updateUser } = require('~/models/userMethods');
|
||||
const fs = require('fs');
|
||||
const logger = require('~/utils/logger');
|
||||
|
||||
const {
|
||||
LDAP_URL,
|
||||
LDAP_BIND_DN,
|
||||
LDAP_BIND_CREDENTIALS,
|
||||
LDAP_USER_SEARCH_BASE,
|
||||
LDAP_SEARCH_FILTER,
|
||||
LDAP_CA_CERT_PATH,
|
||||
LDAP_FULL_NAME,
|
||||
LDAP_ID,
|
||||
LDAP_USERNAME,
|
||||
} = process.env;
|
||||
|
||||
// Check required environment variables
|
||||
if (!LDAP_URL || !LDAP_USER_SEARCH_BASE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const searchAttributes = [
|
||||
'displayName',
|
||||
'mail',
|
||||
'uid',
|
||||
'cn',
|
||||
'name',
|
||||
'commonname',
|
||||
'givenName',
|
||||
'sn',
|
||||
'sAMAccountName',
|
||||
];
|
||||
|
||||
if (LDAP_FULL_NAME) {
|
||||
searchAttributes.push(...LDAP_FULL_NAME.split(','));
|
||||
}
|
||||
if (LDAP_ID) {
|
||||
searchAttributes.push(LDAP_ID);
|
||||
}
|
||||
if (LDAP_USERNAME) {
|
||||
searchAttributes.push(LDAP_USERNAME);
|
||||
}
|
||||
|
||||
const ldapOptions = {
|
||||
server: {
|
||||
url: process.env.LDAP_URL,
|
||||
bindDN: process.env.LDAP_BIND_DN,
|
||||
bindCredentials: process.env.LDAP_BIND_CREDENTIALS,
|
||||
searchBase: process.env.LDAP_USER_SEARCH_BASE,
|
||||
searchFilter: process.env.LDAP_SEARCH_FILTER || 'mail={{username}}',
|
||||
searchAttributes: ['displayName', 'mail', 'uid', 'cn', 'name', 'commonname', 'givenName', 'sn'],
|
||||
...(process.env.LDAP_CA_CERT_PATH && {
|
||||
tlsOptions: { ca: [fs.readFileSync(process.env.LDAP_CA_CERT_PATH)] },
|
||||
url: LDAP_URL,
|
||||
bindDN: LDAP_BIND_DN,
|
||||
bindCredentials: LDAP_BIND_CREDENTIALS,
|
||||
searchBase: LDAP_USER_SEARCH_BASE,
|
||||
searchFilter: LDAP_SEARCH_FILTER || 'mail={{username}}',
|
||||
searchAttributes: [...new Set(searchAttributes)],
|
||||
...(LDAP_CA_CERT_PATH && {
|
||||
tlsOptions: {
|
||||
ca: (() => {
|
||||
try {
|
||||
return [fs.readFileSync(LDAP_CA_CERT_PATH)];
|
||||
} catch (err) {
|
||||
logger.error('[ldapStrategy]', 'Failed to read CA certificate', err);
|
||||
throw err;
|
||||
}
|
||||
})(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
usernameField: 'email',
|
||||
|
@ -23,45 +72,55 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
|||
return done(null, false, { message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
try {
|
||||
const firstName = userinfo.givenName;
|
||||
const familyName = userinfo.surname || userinfo.sn;
|
||||
const fullName =
|
||||
firstName && familyName
|
||||
? `${firstName} ${familyName}`
|
||||
: userinfo.cn ||
|
||||
userinfo.name ||
|
||||
userinfo.commonname ||
|
||||
userinfo.displayName ||
|
||||
userinfo.mail;
|
||||
if (!userinfo.mail) {
|
||||
logger.warn(
|
||||
'[ldapStrategy]',
|
||||
'No email attributes found in userinfo',
|
||||
JSON.stringify(userinfo, null, 2),
|
||||
);
|
||||
return done(null, false, { message: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
try {
|
||||
const ldapId =
|
||||
(LDAP_ID && userinfo[LDAP_ID]) || userinfo.uid || userinfo.sAMAccountName || userinfo.mail;
|
||||
|
||||
let user = await findUser({ ldapId });
|
||||
|
||||
const fullNameAttributes = LDAP_FULL_NAME && LDAP_FULL_NAME.split(',');
|
||||
const fullName =
|
||||
fullNameAttributes && fullNameAttributes.length > 0
|
||||
? fullNameAttributes.map((attr) => userinfo[attr]).join(' ')
|
||||
: userinfo.cn || userinfo.name || userinfo.commonname || userinfo.displayName;
|
||||
|
||||
const username =
|
||||
(LDAP_USERNAME && userinfo[LDAP_USERNAME]) || userinfo.givenName || userinfo.mail;
|
||||
|
||||
const username = userinfo.givenName || userinfo.mail;
|
||||
let user = await findUser({ email: userinfo.mail });
|
||||
if (user && user.provider !== 'ldap') {
|
||||
return done(null, false, { message: 'Invalid credentials' });
|
||||
}
|
||||
if (!user) {
|
||||
user = {
|
||||
provider: 'ldap',
|
||||
ldapId: userinfo.uid,
|
||||
ldapId,
|
||||
username,
|
||||
email: userinfo.mail || '',
|
||||
emailVerified: true,
|
||||
email: userinfo.mail,
|
||||
emailVerified: true, // The ldap server administrator should verify the email
|
||||
name: fullName,
|
||||
};
|
||||
const userId = await createUser(user);
|
||||
user._id = userId;
|
||||
} else {
|
||||
// Users registered in LDAP are assumed to have their user information managed in LDAP,
|
||||
// so update the user information with the values registered in LDAP
|
||||
user.provider = 'ldap';
|
||||
user.ldapId = userinfo.uid;
|
||||
user.ldapId = ldapId;
|
||||
user.email = userinfo.mail;
|
||||
user.username = username;
|
||||
user.name = fullName;
|
||||
}
|
||||
|
||||
user = await updateUser(user._id, user);
|
||||
|
||||
done(null, user);
|
||||
} catch (err) {
|
||||
logger.error('[ldapStrategy]', err);
|
||||
done(err);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue