397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
2.6 KiB
JavaScript
49 lines
2.6 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* keychain-read — синхронное чтение OS-keychain через async-only keytar.
|
|
*
|
|
* keytar НЕ имеет getPasswordSync (только async getPassword → Promise). Хуки роутер-наставника
|
|
* читают ключ СИНХРОННО (resolveReceiptKey/resolveJudgeKey возвращают string|null без await).
|
|
* Чтобы не делать асинхронными все гейты, читаем ключ в коротком node-subprocess: execFileSync
|
|
* блокирует вызывающий поток, а внутри subprocess await'ит keytar.getPassword и печатает значение.
|
|
*
|
|
* keytar резолвится по АБСОЛЮТНОМУ пути (require.resolve относительно этого .mjs), чтобы subprocess
|
|
* нашёл его независимо от своего cwd. Любая ошибка (нет keytar / spawn упал / пусто) → null
|
|
* (fail-closed чтение: отсутствие ключа = неподписанная расписка невалидна на стороне verify).
|
|
*/
|
|
import { execFileSync } from 'node:child_process';
|
|
import { createRequire } from 'node:module';
|
|
|
|
/** Pure: node -e скрипт, который читает keytar.getPassword (async) и пишет значение в stdout. */
|
|
export function buildKeychainReadScript(keytarPath, service, account) {
|
|
return `require(${JSON.stringify(keytarPath)}).getPassword(${JSON.stringify(service)},${JSON.stringify(account)})`
|
|
+ `.then(v=>{if(v!=null)process.stdout.write(String(v));}).catch(()=>{});`;
|
|
}
|
|
|
|
/** Pure: stdout subprocess → ключ или null (пусто/отсутствие → null). */
|
|
export function parseKeychainStdout(out) {
|
|
const v = out == null ? '' : String(out);
|
|
return v.length ? v : null;
|
|
}
|
|
|
|
/**
|
|
* Синхронно прочитать запись keychain (service/account) через async keytar в subprocess.
|
|
* requireImpl/exec инъектируются для тестов. Никогда не бросает → null на любой сбой.
|
|
*/
|
|
export function readKeychainSync(service, account, { requireImpl, exec } = {}) {
|
|
try {
|
|
const req = requireImpl || createRequire(import.meta.url);
|
|
const keytarPath = req.resolve('keytar');
|
|
const script = buildKeychainReadScript(keytarPath, service, account);
|
|
const runner = exec || execFileSync;
|
|
const out = runner(process.execPath, ['-e', script], {
|
|
encoding: 'utf-8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
timeout: 5000,
|
|
});
|
|
return parseKeychainStdout(out);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|