Add production config with LDAP, fix LDAP auth flow
- Production port: 3389 - LDAP enabled with theta42.com config - Proper bind -> search -> user bind flow - Support service account bind for user search - Add systemd service file
This commit is contained in:
@@ -1,11 +1,17 @@
|
|||||||
{
|
{
|
||||||
"server": {
|
"server": {
|
||||||
"port": 3000
|
"port": 3389,
|
||||||
},
|
"host": "0.0.0.0"
|
||||||
"session": {
|
|
||||||
"secret": "CHANGE-ME-NOW"
|
|
||||||
},
|
},
|
||||||
"auth": {
|
"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}}))"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
14
conf/secrets.json
Normal file
14
conf/secrets.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,39 +85,37 @@ async function authenticateLDAP(username, password) {
|
|||||||
reject(new Error('LDAP connection failed'));
|
reject(new Error('LDAP connection failed'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Construct user DN
|
// Step 1: Bind with service account (if configured) to search for user
|
||||||
const userDN = `uid=${username},${CONFIG.ldap.baseDN}`;
|
const doSearch = () => {
|
||||||
|
// Build search filter from config, replacing {{username}} with actual username
|
||||||
|
const filter = CONFIG.ldap.searchFilter.replace('{{username}}', username);
|
||||||
|
|
||||||
client.bind(userDN, password, (err) => {
|
|
||||||
if (err) {
|
|
||||||
client.destroy();
|
|
||||||
reject(new Error('Invalid credentials'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Successfully authenticated - get user info
|
|
||||||
const searchOptions = {
|
const searchOptions = {
|
||||||
scope: 'base',
|
scope: 'sub',
|
||||||
filter: `(uid=${username})`,
|
filter: filter,
|
||||||
attributes: ['dn', 'uid', 'cn', 'mail', 'displayName', 'memberOf']
|
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) {
|
if (err) {
|
||||||
client.destroy();
|
client.destroy();
|
||||||
reject(err);
|
reject(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = { username };
|
|
||||||
|
|
||||||
res.on('searchEntry', (entry) => {
|
res.on('searchEntry', (entry) => {
|
||||||
|
userDN = entry.object.dn;
|
||||||
user.dn = entry.object.dn;
|
user.dn = entry.object.dn;
|
||||||
user.uid = entry.object.uid;
|
user.uid = entry.object.uid;
|
||||||
user.cn = entry.object.cn;
|
user.cn = entry.object.cn;
|
||||||
user.email = entry.object.mail;
|
user.email = entry.object.mail;
|
||||||
user.displayName = entry.object.displayName || entry.object.cn;
|
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) => {
|
res.on('error', (err) => {
|
||||||
@@ -126,11 +124,41 @@ async function authenticateLDAP(username, password) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
client.destroy();
|
if (!userDN) {
|
||||||
resolve(user);
|
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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user