Files
go_bt/tests/recharge_credentials_store.test.js

155 lines
6.1 KiB
JavaScript

'use strict';
const assert = require('assert');
const crypto = require('crypto');
const fs = require('fs');
const os = require('os');
const path = require('path');
const {
createRechargeCredentialStore,
_private,
} = require('../src/services/recharge_credentials_store');
function createTestProtection() {
const key = crypto.randomBytes(32);
return {
async protect(plain) {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(plain), cipher.final()]);
return Buffer.concat([iv, cipher.getAuthTag(), encrypted]);
},
async unprotect(payload) {
const iv = payload.subarray(0, 12);
const tag = payload.subarray(12, 28);
const encrypted = payload.subarray(28);
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
},
clear() { key.fill(0); },
};
}
(async () => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'recharge-credentials-'));
const protection = createTestProtection();
try {
const store = createRechargeCredentialStore({
dataDir: tempDir,
protect: protection.protect,
unprotect: protection.unprotect,
});
assert.deepStrictEqual(store.getMeta(), {
configured: false,
keyIdMasked: '',
updatedAt: null,
});
const keyId = 'kid-example-12345678';
const secret = 'unit-test-secret-value';
const saved = await store.saveCredentials(keyId, secret);
assert.strictEqual(saved.configured, true);
assert.strictEqual(saved.keyIdMasked, 'kid-••••5678');
assert.match(saved.updatedAt, /^\d{4}-\d{2}-\d{2}T/);
const diskText = fs.readFileSync(store.filePath, 'utf8');
assert(!diskText.includes(keyId), '凭据文件不得出现完整 Key ID 明文');
assert(!diskText.includes(secret), '凭据文件不得出现 Secret 明文');
const envelope = JSON.parse(diskText);
assert.strictEqual(envelope.provider, 'external_recharge');
assert.strictEqual(envelope.version, 1);
assert.strictEqual(typeof envelope.protectedData, 'string');
const loaded = await store.loadCredentials();
assert.deepStrictEqual(loaded, { keyId, secret });
const replacementKeyId = 'kid-replacement-87654321';
const replacementSecret = 'replacement-unit-secret';
const replaced = await store.saveCredentials(replacementKeyId, replacementSecret);
assert.strictEqual(replaced.configured, true, '已配置凭据必须支持原子更新');
assert.deepStrictEqual(
await store.loadCredentials(),
{ keyId: replacementKeyId, secret: replacementSecret },
);
const replacedDiskText = fs.readFileSync(store.filePath, 'utf8');
assert(!replacedDiskText.includes(replacementKeyId));
assert(!replacedDiskText.includes(replacementSecret));
const meta = store.getMeta();
assert.deepStrictEqual(meta, {
configured: true,
keyIdMasked: 'kid-••••4321',
updatedAt: replaced.updatedAt,
});
await assert.rejects(
() => store.saveCredentials('', secret),
error => error && error.code === 'RECHARGE_KEY_ID_INVALID',
);
await assert.rejects(
() => store.saveCredentials(keyId, '••••••••'),
error => error && error.code === 'RECHARGE_SECRET_PLACEHOLDER',
);
await store.deleteCredentials();
assert.strictEqual(fs.existsSync(store.filePath), false);
assert.strictEqual(await store.loadCredentials(), null);
fs.writeFileSync(store.filePath, '{not-json', 'utf8');
assert.throws(
() => store.getMeta(),
error => error && error.code === 'RECHARGE_CREDENTIALS_CORRUPT',
);
assert.strictEqual(_private.maskKeyId('a'), 'a••••');
assert.strictEqual(_private.maskKeyId('abcd'), 'a••••d');
assert.strictEqual(_private.maskKeyId('abcdefgh'), 'ab••••gh');
assert.strictEqual(_private.maskKeyId('abcdefghijkl'), 'abcd••••ijkl');
const sentinel = 'must-never-appear-in-process-arguments';
const invocation = _private.buildPowerShellInvocation();
const sentinelBuffer = Buffer.from(sentinel, 'utf8');
const stdinPayload = _private.buildDpapiStdinPayload('protect', sentinelBuffer);
const commandLine = [invocation.command, ...invocation.args].join(' ');
assert(!commandLine.includes(sentinel));
assert(!commandLine.includes(sentinelBuffer.toString('base64')));
assert(stdinPayload.toString('utf8').includes(sentinelBuffer.toString('base64')));
const encodedIndex = invocation.args.indexOf('-EncodedCommand') + 1;
assert(encodedIndex > 0 && invocation.args[encodedIndex]);
const decodedScript = Buffer.from(invocation.args[encodedIndex], 'base64').toString('utf16le');
assert(decodedScript.includes('[Console]::In.ReadToEnd()'), 'DPAPI 输入必须来自 PowerShell stdin');
assert(decodedScript.includes('DataProtectionScope]::CurrentUser'), 'DPAPI 必须使用 CurrentUser 范围');
sentinelBuffer.fill(0);
stdinPayload.fill(0);
if (process.platform === 'win32' && !process.env.CODEX_CI) {
const probe = crypto.randomBytes(37);
let protectedProbe = null;
let unprotectedProbe = null;
try {
protectedProbe = await _private.runDpapiPowerShell('protect', probe);
assert(!protectedProbe.equals(probe));
unprotectedProbe = await _private.runDpapiPowerShell('unprotect', protectedProbe);
assert(unprotectedProbe.equals(probe), 'Windows DPAPI 应能在当前用户下完成往返解密');
} finally {
probe.fill(0);
if (protectedProbe) protectedProbe.fill(0);
if (unprotectedProbe) unprotectedProbe.fill(0);
}
} else if (process.platform === 'win32') {
console.log('recharge_credentials_store.test.js: DPAPI runtime probe skipped in Codex CI sandbox');
}
console.log('recharge_credentials_store.test.js: PASS');
} finally {
protection.clear();
fs.rmSync(tempDir, { recursive: true, force: true });
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});