refactor: modularize backend security and observability

This commit is contained in:
237899745
2026-07-27 13:06:54 +08:00
parent f90311e68c
commit dce1e3622a
25 changed files with 1633 additions and 849 deletions

View File

@@ -0,0 +1,155 @@
const assert = require('assert');
const { normalizeNonNegativeInteger, parseBooleanLike } = require('../utils/coerce');
const {
buildDeviceName,
detectDeviceTypeFromUserAgent,
inferPlatformFromUserAgent,
normalizeClientIp,
normalizeClientType,
resolveClientType,
sanitizeDeviceText
} = require('../utils/device');
const { parseDownloadTrafficLine, parseDownloadTrafficLogTime } = require('../utils/oss-log-parser');
const { isPathInside, normalizeVirtualPath } = require('../utils/path-safety');
const { isPathWithinShare, isShareIpAllowed, isValidShareCode, parseShareIpWhitelist } = require('../utils/share');
const { buildHttpDownloadUrl, sanitizeHttpBaseUrl } = require('../utils/url');
const {
compareLooseVersion,
normalizeReleaseNotes,
normalizeSha256,
normalizeVersion
} = require('../utils/version');
const results = { passed: 0, failed: 0 };
function test(name, fn) {
try {
fn();
results.passed += 1;
console.log(` [PASS] ${name}`);
} catch (error) {
results.failed += 1;
console.error(` [FAIL] ${name}: ${error.message}`);
}
}
console.log('\n========== 领域纯函数测试 ==========\n');
test('版本号规范化与宽松比较', () => {
assert.strictEqual(normalizeVersion(' v1.2.3 '), 'v1.2.3');
assert.strictEqual(normalizeVersion('', '1.0.0'), '1.0.0');
assert.strictEqual(compareLooseVersion('v1.10.0', '1.9.9'), 1);
assert.strictEqual(compareLooseVersion('1.2', '1.2.0'), 0);
assert.strictEqual(compareLooseVersion('1.2.0', '2.0.0'), -1);
});
test('发布说明转义换行被还原', () => {
assert.strictEqual(normalizeReleaseNotes('first\\r\\nsecond\\n'), 'first\nsecond');
});
test('SHA256 只接受完整十六进制摘要', () => {
assert.strictEqual(normalizeSha256(' A'.repeat(64)), '');
assert.strictEqual(normalizeSha256('A'.repeat(64)), 'a'.repeat(64));
assert.strictEqual(normalizeSha256('a'.repeat(63)), '');
});
test('非负整数与布尔配置规范化', () => {
assert.strictEqual(normalizeNonNegativeInteger(12.9), 12);
assert.strictEqual(normalizeNonNegativeInteger(-1, 4), 4);
assert.strictEqual(parseBooleanLike('YES'), true);
assert.strictEqual(parseBooleanLike('off', true), false);
assert.strictEqual(parseBooleanLike('invalid', true), true);
});
test('物理路径边界不接受兄弟目录', () => {
const parent = require('path').resolve('/tmp/root');
assert.strictEqual(isPathInside(parent, require('path').join(parent, 'child')), true);
assert.strictEqual(isPathInside(parent, require('path').resolve('/tmp/root-other')), false);
});
test('虚拟路径统一分隔符并拒绝遍历和空字节', () => {
assert.strictEqual(normalizeVirtualPath('folder\\file.txt'), '/folder/file.txt');
assert.strictEqual(normalizeVirtualPath('/folder/../secret'), null);
assert.strictEqual(normalizeVirtualPath('/folder/%2e%2e/secret'), null);
assert.strictEqual(normalizeVirtualPath('/folder/%2500.txt'), null);
assert.strictEqual(normalizeVirtualPath('/'), '/');
});
test('HTTP 基础地址拒绝危险协议并移除查询参数', () => {
assert.strictEqual(sanitizeHttpBaseUrl('javascript:alert(1)'), null);
assert.strictEqual(sanitizeHttpBaseUrl('https://example.test/base/?token=x#hash'), 'https://example.test/base');
});
test('下载地址逐段编码且不继承查询参数', () => {
assert.strictEqual(
buildHttpDownloadUrl('https://example.test/base?token=x', '/目录/a b.txt'),
'https://example.test/base/%E7%9B%AE%E5%BD%95/a%20b.txt'
);
});
test('OSS 日志时间正确应用时区', () => {
const date = parseDownloadTrafficLogTime('[27/Jul/2026:12:34:56 +0800]');
assert.strictEqual(date.toISOString(), '2026-07-27T04:34:56.000Z');
assert.strictEqual(parseDownloadTrafficLogTime('[31/Feb/2026:12:00:00 +0800]'), null);
assert.strictEqual(parseDownloadTrafficLogTime('[27/Jul/2026:12:00:00 +2460]'), null);
});
test('OSS 下载日志解析用户、对象键、字节和时间', () => {
const line = '1.2.3.4 - - [27/Jul/2026:12:34:56 +0800] "GET /user_42/folder/a%20b.txt?signature=x HTTP/1.1" 206 1024 "-" "ua"';
const result = parseDownloadTrafficLine(line);
assert.strictEqual(result.userId, 42);
assert.strictEqual(result.bytes, 1024);
assert.strictEqual(result.objectKey, 'user_42/folder/a b.txt');
assert.strictEqual(result.eventAt.toISOString(), '2026-07-27T04:34:56.000Z');
});
test('OSS 日志只统计成功 GET 下载', () => {
const fallback = new Date('2026-01-01T00:00:00Z');
assert.strictEqual(parseDownloadTrafficLine('"HEAD /user_1/a HTTP/1.1" 200 10', fallback), null);
assert.strictEqual(parseDownloadTrafficLine('"GET /user_1/a HTTP/1.1" 404 10', fallback), null);
assert.strictEqual(parseDownloadTrafficLine('"GET /public/a HTTP/1.1" 200 10', fallback), null);
assert.strictEqual(parseDownloadTrafficLine('"GET /user_1/a HTTP/1.1" 200 -', fallback), null);
});
test('分享码与分享路径严格限定', () => {
assert.strictEqual(isValidShareCode('Abc123'), true);
assert.strictEqual(isValidShareCode('../bad'), false);
assert.strictEqual(isPathWithinShare('/folder/a.txt', { share_type: 'folder', share_path: '/folder' }), true);
assert.strictEqual(isPathWithinShare('/folder-other/a.txt', { share_type: 'folder', share_path: '/folder' }), false);
assert.strictEqual(isPathWithinShare('/folder/a.txt', { share_type: 'file', share_path: '/folder/a.txt' }), true);
assert.strictEqual(isPathWithinShare('/folder/b.txt', { share_type: 'file', share_path: '/folder/a.txt' }), false);
});
test('IP 白名单支持分隔符、精确匹配与前缀规则', () => {
const whitelist = parseShareIpWhitelist('127.0.0.1, 10.0.*;192.168.1.5');
assert.deepStrictEqual(whitelist, ['127.0.0.1', '10.0.*', '192.168.1.5']);
assert.strictEqual(isShareIpAllowed('10.0.2.3', whitelist), true);
assert.strictEqual(isShareIpAllowed('192.168.1.6', whitelist), false);
assert.strictEqual(isShareIpAllowed('', whitelist), false);
assert.strictEqual(isShareIpAllowed('', []), true);
});
test('客户端 IP 与设备类型被稳定规范化', () => {
assert.strictEqual(normalizeClientIp('::ffff:127.0.0.1'), '127.0.0.1');
assert.strictEqual(normalizeClientIp('::1'), '127.0.0.1');
assert.strictEqual(detectDeviceTypeFromUserAgent('Mozilla/5.0 iPhone'), 'mobile');
assert.strictEqual(inferPlatformFromUserAgent('Mozilla/5.0 (Windows NT 10.0)'), 'Windows');
assert.strictEqual(normalizeClientType(' DESKTOP '), 'desktop');
assert.strictEqual(normalizeClientType('bot'), '');
assert.strictEqual(resolveClientType('', 'wanwan-cloud-desktop/1.0'), 'desktop');
});
test('设备名称限制长度并按客户端类型回退', () => {
assert.strictEqual(sanitizeDeviceText(' abc ', 2), 'ab');
assert.strictEqual(buildDeviceName({ clientType: 'mobile', platform: 'iOS' }), '移动端浏览器 · iOS');
assert.strictEqual(buildDeviceName({ clientType: 'web', deviceName: ' 我的设备 ', platform: 'Windows' }), '我的设备');
});
console.log('\n========================================');
console.log('测试总结');
console.log('========================================');
console.log(`通过: ${results.passed}`);
console.log(`失败: ${results.failed}`);
process.exit(results.failed > 0 ? 1 : 0);