'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;