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