"use strict";
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
};
|
Object.defineProperty(exports, "__esModule", { value: true });
|
exports.escapeLdapFilterValue = escapeLdapFilterValue;
|
exports.verifyCredentials = verifyCredentials;
|
const ldapjs_1 = __importDefault(require("ldapjs"));
|
/** Escape value for use inside LDAP filter (RFC4515 subset). */
|
function escapeLdapFilterValue(value) {
|
return value.replace(/[\\*()\0]/g, (ch) => {
|
const code = ch.charCodeAt(0);
|
return `\\${code.toString(16).padStart(2, "0")}`;
|
});
|
}
|
function bindAsync(client, dn, password) {
|
return new Promise((resolve, reject) => {
|
client.bind(dn, password, (err) => {
|
if (err)
|
reject(err);
|
else
|
resolve();
|
});
|
});
|
}
|
function unbindSafe(client) {
|
try {
|
client.unbind();
|
}
|
catch {
|
/* ignore */
|
}
|
}
|
function searchUserDn(client, searchBase, filter) {
|
return new Promise((resolve, reject) => {
|
const opts = {
|
filter,
|
scope: "sub",
|
attributes: ["dn"],
|
sizeLimit: 1,
|
};
|
client.search(searchBase, opts, (err, res) => {
|
if (err) {
|
reject(err);
|
return;
|
}
|
let dn = null;
|
res.on("searchEntry", (entry) => {
|
dn = entry.dn.toString();
|
});
|
res.on("error", reject);
|
res.on("end", (result) => {
|
if (result?.status !== 0 && result?.status !== undefined) {
|
reject(new Error(`LDAP search end status ${result.status}`));
|
return;
|
}
|
resolve(dn);
|
});
|
});
|
});
|
}
|
/**
|
* Verify username/password against AD/LDAP:
|
* service bind → search user DN → bind as user with password.
|
*/
|
async function verifyCredentials(username, password, cfg) {
|
const user = (username ?? "").trim();
|
if (!user || !password) {
|
return { ok: false, reason: "invalid_credentials" };
|
}
|
const filter = cfg.searchFilter.replace(/\{\{username\}\}/g, escapeLdapFilterValue(user));
|
const clientOpts = { url: cfg.url };
|
if (cfg.tlsOptions) {
|
clientOpts.tlsOptions = { ...cfg.tlsOptions };
|
}
|
const searchClient = ldapjs_1.default.createClient(clientOpts);
|
try {
|
await bindAsync(searchClient, cfg.bindDN, cfg.bindPassword);
|
const userDn = await searchUserDn(searchClient, cfg.searchBase, filter);
|
unbindSafe(searchClient);
|
if (!userDn) {
|
return { ok: false, reason: "user_not_found" };
|
}
|
const verifyClient = ldapjs_1.default.createClient(clientOpts);
|
try {
|
await bindAsync(verifyClient, userDn, password);
|
unbindSafe(verifyClient);
|
return { ok: true };
|
}
|
catch {
|
unbindSafe(verifyClient);
|
return { ok: false, reason: "invalid_credentials" };
|
}
|
}
|
catch {
|
unbindSafe(searchClient);
|
return { ok: false, reason: "connection_error" };
|
}
|
}
|
//# sourceMappingURL=ldapAuth.js.map
|