Files
ldap-auth-service/lib/passwordSetter.js
2026-08-21 11:15:07 +05:00

90 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
const { spawn } = require('child_process');
// Sets a user's password via PowerShell/ADSI (IADsUser.SetPassword).
// On a domain-joined machine ADSI negotiates a Kerberos signed/sealed
// connection, which Active Directory accepts as a "protected channel"
// for unicodePwd — unlike a plain LDAP bind over TCP (AD error 53).
//
// The DN and password are passed via environment variables so they never
// appear in the process command line, and the script body is pure ASCII
// (no encoding issues with Windows PowerShell 5.1). The script itself is
// sent over stdin to avoid temp files.
//
// The server is passed explicitly (when `ldapUrl` is provided) so ADSI
// targets the SAME domain controller that the LDAP write went to. Without
// it ADSI does a serverless bind and the DC Locator may pick another DC
// (e.g. on a remote site) that has not replicated the freshly created user
// yet, which fails with "Такой объект на сервере отсутствует" (0x8007203A).
function adsiServerPrefix(ldapUrl) {
const match = /^([a-z0-9+.-]+):\/\/([^/:]+)(?::(\d+))?/i.exec(String(ldapUrl || '').trim());
if (!match) return '';
const scheme = match[1].toLowerCase();
const host = match[2];
const port = match[3];
const prefix = scheme === 'ldaps' ? 'LDAPS://' : 'LDAP://';
return prefix + host + (port ? ':' + port : '') + '/';
}
function setPasswordViaPowerShell(userDN, password, { exe = 'powershell.exe', timeoutMs = 15000, ldapUrl = '' } = {}) {
return new Promise((resolve, reject) => {
const serverPrefix = adsiServerPrefix(ldapUrl);
const script = [
'$ErrorActionPreference = "Stop"',
'$dn = [Environment]::GetEnvironmentVariable("LDAP_PW_DN")',
'$pw = [Environment]::GetEnvironmentVariable("LDAP_PW_VALUE")',
'$server = [Environment]::GetEnvironmentVariable("LDAP_PW_SERVER")',
'$target = if ($server) { $server + $dn } else { "LDAP://" + $dn }',
'$user = [adsi]($target)',
'$user.SetPassword($pw)'
].join('; ');
let child;
try {
child = spawn(exe, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', '-'], {
env: { ...process.env, LDAP_PW_DN: userDN, LDAP_PW_VALUE: password, LDAP_PW_SERVER: serverPrefix },
windowsHide: true,
stdio: ['pipe', 'pipe', 'pipe']
});
} catch (err) {
reject(new Error(`Не удалось запустить PowerShell для установки пароля: ${err.message}`));
return;
}
let stdout = '';
let stderr = '';
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
try { child.kill(); } catch (_) { /* ignore */ }
}, timeoutMs);
child.stdout.on('data', (d) => { stdout += d; });
child.stderr.on('data', (d) => { stderr += d; });
child.on('error', (err) => {
clearTimeout(timer);
reject(new Error(`Не удалось запустить PowerShell для установки пароля: ${err.message}`));
});
child.on('close', (code) => {
clearTimeout(timer);
if (timedOut) {
reject(new Error('Таймаут при установке пароля через PowerShell'));
return;
}
if (code === 0) {
resolve();
return;
}
const detail = String(stderr || stdout || '').trim();
reject(new Error(`Ошибка установки пароля через PowerShell (код ${code})${detail ? ': ' + detail : ''}`));
});
child.stdin.end(script);
});
}
module.exports = setPasswordViaPowerShell;