// 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 };