sc-idp/lib/ldap/serviceAccount.js
2026-06-01 16:40:54 -05:00

56 lines
1.7 KiB
JavaScript

// LDAP service account (search-then-bind binder). A configured service DN +
// sealed password lets an application bind as a non-user principal, search for a
// user by mail, then re-bind as that user DN to validate the password -- the
// standard enterprise flow -- without exposing a real user as the binder. The
// password is sealed at rest with the same KEK pattern as client secrets and is
// never returned to the wire or echoed in the admin UI. Single-row table,
// tenant-scoped (all db.* calls resolve the current tenant schema).
const db = require("@saltcorn/data/db");
const idpCrypto = require("../crypto");
const { TABLE_LDAP_SERVICE } = require("../constants");
const getServiceAccount = async () => {
const row = await db.selectMaybeOne(TABLE_LDAP_SERVICE, {});
if (!row || !row.dn) {
return null;
}
const password = idpCrypto.openText({
ciphertext: row.secret_ciphertext,
iv: row.secret_iv,
tag: row.secret_tag
}).toString("utf8");
return { dn: row.dn, password: password };
};
const getServiceDn = async () => {
const row = await db.selectMaybeOne(TABLE_LDAP_SERVICE, {});
return row && row.dn ? row.dn : null;
};
const setServiceAccount = async (dn, plaintext) => {
await db.deleteWhere(TABLE_LDAP_SERVICE, {});
if (!dn || !plaintext) {
return;
}
const sealed = idpCrypto.sealText(plaintext);
await db.insert(TABLE_LDAP_SERVICE, {
dn: dn,
secret_ciphertext: sealed.ciphertext,
secret_iv: sealed.iv,
secret_tag: sealed.tag,
created_at: new Date().toISOString()
}, { noid: true });
};
module.exports = {
getServiceAccount,
getServiceDn,
setServiceAccount
};