168 lines
5.9 KiB
JavaScript
168 lines
5.9 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
|
|
const {
|
|
createRechargeCredentialHandlers,
|
|
isLocalRequest,
|
|
} = require('../src/routes/recharge_credentials');
|
|
|
|
function createResponse() {
|
|
return {
|
|
headers: {},
|
|
statusCode: 200,
|
|
payload: undefined,
|
|
set(name, value) {
|
|
this.headers[String(name).toLowerCase()] = String(value);
|
|
return this;
|
|
},
|
|
status(code) {
|
|
this.statusCode = code;
|
|
return this;
|
|
},
|
|
json(payload) {
|
|
this.payload = payload;
|
|
return this;
|
|
},
|
|
};
|
|
}
|
|
|
|
function createRequest(remoteAddress, body, forwardedFor) {
|
|
return {
|
|
socket: { remoteAddress },
|
|
body,
|
|
headers: forwardedFor ? { 'x-forwarded-for': forwardedFor } : {},
|
|
};
|
|
}
|
|
|
|
function assertNoStore(res) {
|
|
assert.strictEqual(res.headers['cache-control'], 'no-store');
|
|
assert.strictEqual(res.headers.pragma, 'no-cache');
|
|
assert.strictEqual(res.headers.expires, '0');
|
|
}
|
|
|
|
(async () => {
|
|
assert.strictEqual(isLocalRequest(createRequest('127.0.0.1')), true);
|
|
assert.strictEqual(isLocalRequest(createRequest('127.255.42.9')), true);
|
|
assert.strictEqual(isLocalRequest(createRequest('::1')), true);
|
|
assert.strictEqual(isLocalRequest(createRequest('0:0:0:0:0:0:0:1')), true);
|
|
assert.strictEqual(isLocalRequest(createRequest('::ffff:127.0.0.1')), true);
|
|
assert.strictEqual(isLocalRequest(createRequest('192.0.2.10')), false);
|
|
assert.strictEqual(isLocalRequest(createRequest('127.999.0.1')), false);
|
|
assert.strictEqual(
|
|
isLocalRequest(createRequest('192.0.2.10', undefined, '127.0.0.1')),
|
|
false,
|
|
'不得信任 X-Forwarded-For 伪造的本机地址',
|
|
);
|
|
assert.strictEqual(
|
|
isLocalRequest({ ip: '127.0.0.1', socket: { remoteAddress: '192.0.2.10' } }),
|
|
false,
|
|
'必须以 TCP 对端地址为准',
|
|
);
|
|
|
|
const fullKeyId = 'kid-route-full-value-1234';
|
|
const fullSecret = 'route-secret-never-return';
|
|
const updatedAt = '2026-08-21T01:02:03.000Z';
|
|
let getCalls = 0;
|
|
let saveArgs = null;
|
|
let deleteCalls = 0;
|
|
const store = {
|
|
getRechargeCredentialMeta() {
|
|
getCalls += 1;
|
|
return { configured: true, keyIdMasked: 'kid-••••1234', updatedAt };
|
|
},
|
|
async saveRechargeCredentials(keyId, secret) {
|
|
saveArgs = { keyId, secret };
|
|
return { configured: true, keyIdMasked: 'kid-••••1234', updatedAt };
|
|
},
|
|
async deleteRechargeCredentials() {
|
|
deleteCalls += 1;
|
|
return { configured: false, keyIdMasked: '', updatedAt: null };
|
|
},
|
|
};
|
|
const handlers = createRechargeCredentialHandlers(store);
|
|
|
|
const getResponse = createResponse();
|
|
handlers.get(createRequest('127.0.0.1'), getResponse);
|
|
assert.strictEqual(getResponse.statusCode, 200);
|
|
assert.deepStrictEqual(getResponse.payload, {
|
|
success: true,
|
|
data: { configured: true, keyIdMasked: 'kid-••••1234', updatedAt },
|
|
});
|
|
assertNoStore(getResponse);
|
|
assert.strictEqual(getCalls, 1);
|
|
|
|
const putResponse = createResponse();
|
|
await handlers.put(createRequest('::1', { keyId: fullKeyId, secret: fullSecret }), putResponse);
|
|
assert.strictEqual(putResponse.statusCode, 200);
|
|
assert.deepStrictEqual(saveArgs, { keyId: fullKeyId, secret: fullSecret });
|
|
assert.deepStrictEqual(putResponse.payload, {
|
|
success: true,
|
|
data: { configured: true, keyIdMasked: 'kid-••••1234', updatedAt },
|
|
});
|
|
assertNoStore(putResponse);
|
|
const putJson = JSON.stringify(putResponse.payload);
|
|
assert(!putJson.includes(fullKeyId), 'PUT 响应不得返回完整 Key ID');
|
|
assert(!putJson.includes(fullSecret), 'PUT 响应不得返回 Secret');
|
|
|
|
const deleteResponse = createResponse();
|
|
await handlers.remove(createRequest('::ffff:127.0.0.1'), deleteResponse);
|
|
assert.strictEqual(deleteResponse.statusCode, 200);
|
|
assert.deepStrictEqual(deleteResponse.payload, {
|
|
success: true,
|
|
data: { configured: false, keyIdMasked: '', updatedAt: null },
|
|
});
|
|
assertNoStore(deleteResponse);
|
|
assert.strictEqual(deleteCalls, 1);
|
|
|
|
const missingResponse = createResponse();
|
|
await handlers.put(createRequest('127.0.0.1', { keyId: fullKeyId }), missingResponse);
|
|
assert.strictEqual(missingResponse.statusCode, 400);
|
|
assertNoStore(missingResponse);
|
|
|
|
const validationHandlers = createRechargeCredentialHandlers({
|
|
async saveRechargeCredentials() {
|
|
const error = new Error('Secret 格式无效');
|
|
error.code = 'RECHARGE_SECRET_INVALID';
|
|
throw error;
|
|
},
|
|
});
|
|
const validationResponse = createResponse();
|
|
await validationHandlers.put(
|
|
createRequest('127.0.0.1', { keyId: fullKeyId, secret: fullSecret }),
|
|
validationResponse,
|
|
);
|
|
assert.strictEqual(validationResponse.statusCode, 400);
|
|
assert(!JSON.stringify(validationResponse.payload).includes(fullSecret));
|
|
assertNoStore(validationResponse);
|
|
|
|
const remoteResponse = createResponse();
|
|
handlers.get(createRequest('192.0.2.10', undefined, '127.0.0.1'), remoteResponse);
|
|
assert.strictEqual(remoteResponse.statusCode, 403);
|
|
assert.strictEqual(getCalls, 1, '远程请求不应触发凭据存储读取');
|
|
assertNoStore(remoteResponse);
|
|
assert(!JSON.stringify(remoteResponse.payload).includes('kid-••••1234'));
|
|
|
|
const errorHandlers = createRechargeCredentialHandlers({
|
|
getRechargeCredentialMeta() { throw new Error(fullSecret); },
|
|
async saveRechargeCredentials() { throw new Error(fullSecret); },
|
|
async deleteRechargeCredentials() { throw new Error(fullSecret); },
|
|
});
|
|
for (const [handlerName, body] of [
|
|
['get', undefined],
|
|
['put', { keyId: fullKeyId, secret: fullSecret }],
|
|
['remove', undefined],
|
|
]) {
|
|
const response = createResponse();
|
|
await errorHandlers[handlerName](createRequest('127.0.0.1', body), response);
|
|
assert.strictEqual(response.statusCode, 500);
|
|
assert(!JSON.stringify(response.payload).includes(fullSecret), '错误响应不得泄露 Secret');
|
|
assertNoStore(response);
|
|
}
|
|
|
|
console.log('recharge_credentials_route.test.js: PASS');
|
|
})().catch(error => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
});
|