mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 08:50:15 +01: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
|
|
@ -372,6 +372,9 @@ LDAP_BIND_CREDENTIALS=
|
||||||
LDAP_USER_SEARCH_BASE=
|
LDAP_USER_SEARCH_BASE=
|
||||||
LDAP_SEARCH_FILTER=mail={{username}}
|
LDAP_SEARCH_FILTER=mail={{username}}
|
||||||
LDAP_CA_CERT_PATH=
|
LDAP_CA_CERT_PATH=
|
||||||
|
# LDAP_ID=
|
||||||
|
# LDAP_USERNAME=
|
||||||
|
# LDAP_FULL_NAME=
|
||||||
|
|
||||||
#========================#
|
#========================#
|
||||||
# Email Password Reset #
|
# Email Password Reset #
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ const startServer = async () => {
|
||||||
passport.use(passportLogin());
|
passport.use(passportLogin());
|
||||||
|
|
||||||
// LDAP Auth
|
// LDAP Auth
|
||||||
if (process.env.LDAP_URL && process.env.LDAP_BIND_DN && process.env.LDAP_USER_SEARCH_BASE) {
|
if (process.env.LDAP_URL && process.env.LDAP_USER_SEARCH_BASE) {
|
||||||
passport.use(ldapLogin);
|
passport.use(ldapLogin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const requireLdapAuth = (req, res, next) => {
|
||||||
console.log({
|
console.log({
|
||||||
title: '(requireLdapAuth) Error: No user',
|
title: '(requireLdapAuth) Error: No user',
|
||||||
});
|
});
|
||||||
return res.status(422).send(info);
|
return res.status(404).send(info);
|
||||||
}
|
}
|
||||||
req.user = user;
|
req.user = user;
|
||||||
next();
|
next();
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,7 @@ const {
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
const ldapAuth =
|
const ldapAuth = !!process.env.LDAP_URL && !!process.env.LDAP_USER_SEARCH_BASE;
|
||||||
!!process.env.LDAP_URL && !!process.env.LDAP_BIND_DN && !!process.env.LDAP_USER_SEARCH_BASE;
|
|
||||||
//Local
|
//Local
|
||||||
router.post('/logout', requireJwtAuth, logoutController);
|
router.post('/logout', requireJwtAuth, logoutController);
|
||||||
router.post(
|
router.post(
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,7 @@ router.get('/', async function (req, res) {
|
||||||
|
|
||||||
const instanceProject = await getProjectByName('instance', '_id');
|
const instanceProject = await getProjectByName('instance', '_id');
|
||||||
|
|
||||||
const ldapLoginEnabled =
|
const ldapLoginEnabled = !!process.env.LDAP_URL && !!process.env.LDAP_USER_SEARCH_BASE;
|
||||||
!!process.env.LDAP_URL && !!process.env.LDAP_BIND_DN && !!process.env.LDAP_USER_SEARCH_BASE;
|
|
||||||
try {
|
try {
|
||||||
/** @type {TStartupConfig} */
|
/** @type {TStartupConfig} */
|
||||||
const payload = {
|
const payload = {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,66 @@
|
||||||
|
const fs = require('fs');
|
||||||
const LdapStrategy = require('passport-ldapauth');
|
const LdapStrategy = require('passport-ldapauth');
|
||||||
const { findUser, createUser, updateUser } = require('~/models/userMethods');
|
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 = {
|
const ldapOptions = {
|
||||||
server: {
|
server: {
|
||||||
url: process.env.LDAP_URL,
|
url: LDAP_URL,
|
||||||
bindDN: process.env.LDAP_BIND_DN,
|
bindDN: LDAP_BIND_DN,
|
||||||
bindCredentials: process.env.LDAP_BIND_CREDENTIALS,
|
bindCredentials: LDAP_BIND_CREDENTIALS,
|
||||||
searchBase: process.env.LDAP_USER_SEARCH_BASE,
|
searchBase: LDAP_USER_SEARCH_BASE,
|
||||||
searchFilter: process.env.LDAP_SEARCH_FILTER || 'mail={{username}}',
|
searchFilter: LDAP_SEARCH_FILTER || 'mail={{username}}',
|
||||||
searchAttributes: ['displayName', 'mail', 'uid', 'cn', 'name', 'commonname', 'givenName', 'sn'],
|
searchAttributes: [...new Set(searchAttributes)],
|
||||||
...(process.env.LDAP_CA_CERT_PATH && {
|
...(LDAP_CA_CERT_PATH && {
|
||||||
tlsOptions: { ca: [fs.readFileSync(process.env.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',
|
usernameField: 'email',
|
||||||
|
|
@ -23,45 +72,55 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
||||||
return done(null, false, { message: 'Invalid credentials' });
|
return done(null, false, { message: 'Invalid credentials' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (!userinfo.mail) {
|
||||||
const firstName = userinfo.givenName;
|
logger.warn(
|
||||||
const familyName = userinfo.surname || userinfo.sn;
|
'[ldapStrategy]',
|
||||||
const fullName =
|
'No email attributes found in userinfo',
|
||||||
firstName && familyName
|
JSON.stringify(userinfo, null, 2),
|
||||||
? `${firstName} ${familyName}`
|
);
|
||||||
: userinfo.cn ||
|
|
||||||
userinfo.name ||
|
|
||||||
userinfo.commonname ||
|
|
||||||
userinfo.displayName ||
|
|
||||||
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' });
|
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;
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = {
|
user = {
|
||||||
provider: 'ldap',
|
provider: 'ldap',
|
||||||
ldapId: userinfo.uid,
|
ldapId,
|
||||||
username,
|
username,
|
||||||
email: userinfo.mail || '',
|
email: userinfo.mail,
|
||||||
emailVerified: true,
|
emailVerified: true, // The ldap server administrator should verify the email
|
||||||
name: fullName,
|
name: fullName,
|
||||||
};
|
};
|
||||||
const userId = await createUser(user);
|
const userId = await createUser(user);
|
||||||
user._id = userId;
|
user._id = userId;
|
||||||
} else {
|
} 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.provider = 'ldap';
|
||||||
user.ldapId = userinfo.uid;
|
user.ldapId = ldapId;
|
||||||
|
user.email = userinfo.mail;
|
||||||
user.username = username;
|
user.username = username;
|
||||||
user.name = fullName;
|
user.name = fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
user = await updateUser(user._id, user);
|
user = await updateUser(user._id, user);
|
||||||
|
|
||||||
done(null, user);
|
done(null, user);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
logger.error('[ldapStrategy]', err);
|
||||||
done(err);
|
done(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue