diff --git a/conf/production.json b/conf/production.json index e4075ac..d531bb3 100644 --- a/conf/production.json +++ b/conf/production.json @@ -1,11 +1,17 @@ { "server": { - "port": 3000 - }, - "session": { - "secret": "CHANGE-ME-NOW" + "port": 3389, + "host": "0.0.0.0" }, "auth": { - "disabled": false + "disabled": false, + "ldap": { + "enabled": true, + "url": "ldap://10.1.0.55:389", + "baseDN": "dc=theta42,dc=com", + "bindDN": "cn=ldapclient service,ou=people,dc=theta42,dc=com", + "bindPassword": "", + "searchFilter": "(&(memberof=cn=app_openclaw_access,ou=groups,dc=theta42,dc=com)(objectClass=posixAccount)(uid={{username}}))" + } } } \ No newline at end of file diff --git a/conf/secrets.json b/conf/secrets.json new file mode 100644 index 0000000..46850ac --- /dev/null +++ b/conf/secrets.json @@ -0,0 +1,14 @@ +{ + "gateway": { + "token": "a41984619a5f4b9bf9148ab6eb4abca53eb796d046cbbec5" + }, + "session": { + "secret": "dev-session-secret-change-in-production" + }, + "auth": { + "ldap": { + "bindDN": "cn=ldapclient service,ou=people,dc=theta42,dc=com", + "bindPassword": "REPLACE_WITH_ACTUAL_PASSWORD" + } + } +} \ No newline at end of file diff --git a/server/index.js b/server/index.js index 7a67140..155a2bd 100644 --- a/server/index.js +++ b/server/index.js @@ -85,39 +85,37 @@ async function authenticateLDAP(username, password) { reject(new Error('LDAP connection failed')); }); - // Construct user DN - const userDN = `uid=${username},${CONFIG.ldap.baseDN}`; - - client.bind(userDN, password, (err) => { - if (err) { - client.destroy(); - reject(new Error('Invalid credentials')); - return; - } + // Step 1: Bind with service account (if configured) to search for user + const doSearch = () => { + // Build search filter from config, replacing {{username}} with actual username + const filter = CONFIG.ldap.searchFilter.replace('{{username}}', username); - // Successfully authenticated - get user info const searchOptions = { - scope: 'base', - filter: `(uid=${username})`, + scope: 'sub', + filter: filter, attributes: ['dn', 'uid', 'cn', 'mail', 'displayName', 'memberOf'] }; - client.search(userDN, searchOptions, (err, res) => { + let userDN = null; + const user = { username }; + + client.search(CONFIG.ldap.baseDN, searchOptions, (err, res) => { if (err) { client.destroy(); reject(err); return; } - const user = { username }; - res.on('searchEntry', (entry) => { + userDN = entry.object.dn; user.dn = entry.object.dn; user.uid = entry.object.uid; user.cn = entry.object.cn; user.email = entry.object.mail; user.displayName = entry.object.displayName || entry.object.cn; - user.groups = entry.object.memberOf || []; + user.groups = entry.object.memberOf + ? (Array.isArray(entry.object.memberOf) ? entry.object.memberOf : [entry.object.memberOf]) + : []; }); res.on('error', (err) => { @@ -126,11 +124,41 @@ async function authenticateLDAP(username, password) { }); res.on('end', () => { - client.destroy(); - resolve(user); + if (!userDN) { + client.destroy(); + reject(new Error('User not found')); + return; + } + + // Step 2: Bind as the user to verify password + client.bind(userDN, password, (err) => { + if (err) { + client.destroy(); + reject(new Error('Invalid credentials')); + return; + } + + client.destroy(); + resolve(user); + }); }); }); - }); + }; + + // If we have a service account bind, use it first + if (CONFIG.ldap.bindDN && CONFIG.ldap.bindPassword) { + client.bind(CONFIG.ldap.bindDN, CONFIG.ldap.bindPassword, (err) => { + if (err) { + client.destroy(); + reject(new Error('LDAP service bind failed')); + return; + } + doSearch(); + }); + } else { + // No service bind - search anonymously (may not work with all LDAP servers) + doSearch(); + } }); }