🚀 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:
Yuichi Oneda 2024-06-21 07:14:53 -07:00 committed by GitHub
parent a53312bbd4
commit a8c874267f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 96 additions and 36 deletions

View file

@ -372,6 +372,9 @@ LDAP_BIND_CREDENTIALS=
LDAP_USER_SEARCH_BASE=
LDAP_SEARCH_FILTER=mail={{username}}
LDAP_CA_CERT_PATH=
# LDAP_ID=
# LDAP_USERNAME=
# LDAP_FULL_NAME=
#========================#
# Email Password Reset #

View file

@ -61,7 +61,7 @@ const startServer = async () => {
passport.use(passportLogin());
// 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);
}

View file

@ -13,7 +13,7 @@ const requireLdapAuth = (req, res, next) => {
console.log({
title: '(requireLdapAuth) Error: No user',
});
return res.status(422).send(info);
return res.status(404).send(info);
}
req.user = user;
next();

View file

@ -21,8 +21,7 @@ const {
const router = express.Router();
const ldapAuth =
!!process.env.LDAP_URL && !!process.env.LDAP_BIND_DN && !!process.env.LDAP_USER_SEARCH_BASE;
const ldapAuth = !!process.env.LDAP_URL && !!process.env.LDAP_USER_SEARCH_BASE;
//Local
router.post('/logout', requireJwtAuth, logoutController);
router.post(

View file

@ -33,8 +33,7 @@ router.get('/', async function (req, res) {
const instanceProject = await getProjectByName('instance', '_id');
const ldapLoginEnabled =
!!process.env.LDAP_URL && !!process.env.LDAP_BIND_DN && !!process.env.LDAP_USER_SEARCH_BASE;
const ldapLoginEnabled = !!process.env.LDAP_URL && !!process.env.LDAP_USER_SEARCH_BASE;
try {
/** @type {TStartupConfig} */
const payload = {

View file

@ -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);
}
});