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,180 @@
process.env.LOG_LEVEL = 'silent';
const assert = require('assert');
const { EventEmitter } = require('events');
const fs = require('fs');
const {
createCsrfCookieMiddleware,
csrfProtection,
generateCsrfToken,
tokensEqual
} = require('../middleware/csrf');
const { RateLimiter } = require('../middleware/rate-limit');
const { applySecurityHeaders, createHttpsEnforcementMiddleware } = require('../middleware/security');
const { expressErrorHandler } = require('../middleware/error-handler');
const { requestContextMiddleware, redactLogValue } = require('../utils/logger');
const { createHealthService, normalizeMinimumFreeBytes } = require('../services/health-service');
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}`);
}
}
function createResponse() {
const response = new EventEmitter();
response.headers = {};
response.statusCode = 200;
response.payload = null;
response.cookies = [];
response.setHeader = (name, value) => { response.headers[name] = value; };
response.removeHeader = (name) => { delete response.headers[name]; };
response.status = (code) => { response.statusCode = code; return response; };
response.json = (payload) => { response.payload = payload; return response; };
response.cookie = (name, value, options) => { response.cookies.push({ name, value, options }); };
return response;
}
console.log('\n========== 中间件与可观测性测试 ==========\n');
test('安全响应头覆盖 CSP、HSTS、Referrer 与 Permissions Policy', () => {
const response = createResponse();
applySecurityHeaders({ secure: true }, response);
assert.ok(response.headers['Content-Security-Policy'].includes("object-src 'none'"));
assert.ok(response.headers['Strict-Transport-Security']);
assert.strictEqual(response.headers['X-Content-Type-Options'], 'nosniff');
assert.strictEqual(response.headers['Referrer-Policy'], 'strict-origin-when-cross-origin');
assert.ok(response.headers['Permissions-Policy']);
});
test('HTTPS 中间件阻止不安全请求但放行本机健康检查', () => {
const middleware = createHttpsEnforcementMiddleware({ enabled: true });
const blockedResponse = createResponse();
middleware({ path: '/api/config', secure: false, socket: { remoteAddress: '10.0.0.1' }, id: 'req-1' }, blockedResponse, () => assert.fail('不应放行'));
assert.strictEqual(blockedResponse.statusCode, 400);
assert.strictEqual(blockedResponse.payload.requestId, 'req-1');
let nextCalled = false;
middleware({ path: '/api/health', secure: false, socket: { remoteAddress: '127.0.0.1' } }, createResponse(), () => { nextCalled = true; });
assert.strictEqual(nextCalled, true);
});
test('请求上下文接受安全 requestId 并回写响应头', () => {
const request = { headers: { 'x-request-id': 'trace-123' }, method: 'GET', path: '/api/config' };
const response = createResponse();
requestContextMiddleware(request, response, () => {});
assert.strictEqual(request.id, 'trace-123');
assert.strictEqual(response.headers['X-Request-ID'], 'trace-123');
response.emit('finish');
});
test('非法 requestId 被替换,日志敏感字段被脱敏', () => {
const request = { headers: { 'x-request-id': 'bad id\n' }, method: 'GET', path: '/' };
const response = createResponse();
requestContextMiddleware(request, response, () => {});
assert.notStrictEqual(request.id, 'bad id\n');
assert.match(request.id, /^[0-9a-f-]{36}$/);
const redacted = redactLogValue({ password: 'secret', note: 'token=abc', nested: { authorization: 'Bearer value' } });
assert.strictEqual(redacted.password, '[REDACTED]');
assert.strictEqual(redacted.note, 'token=[REDACTED]');
assert.strictEqual(redacted.nested.authorization, '[REDACTED]');
response.emit('finish');
});
test('CSRF token 使用固定长度并支持常量时间比较', () => {
const token = generateCsrfToken();
assert.match(token, /^[a-f0-9]{64}$/);
assert.strictEqual(tokensEqual(token, token), true);
assert.strictEqual(tokensEqual(token, `${token}0`), false);
});
test('CSRF Cookie 中间件只在缺少 token 时设置 Cookie', () => {
const middleware = createCsrfCookieMiddleware({ secureCookies: true });
const response = createResponse();
middleware({ cookies: {} }, response, () => {});
assert.strictEqual(response.cookies.length, 1);
assert.strictEqual(response.cookies[0].options.secure, true);
assert.strictEqual(response.cookies[0].options.sameSite, 'strict');
});
test('Cookie 会话的写请求必须通过 CSRF 校验', () => {
const response = createResponse();
csrfProtection({
method: 'POST',
path: '/api/example',
id: 'req-2',
cookies: { token: 'session', csrf_token: 'a' },
headers: { 'x-csrf-token': 'b' }
}, response, () => assert.fail('不应放行'));
assert.strictEqual(response.statusCode, 403);
assert.strictEqual(response.payload.requestId, 'req-2');
});
test('失败限流器达到阈值后封锁并可在成功后清除', () => {
const limiter = new RateLimiter({ maxAttempts: 2, windowMs: 1000, blockDuration: 1000 });
try {
assert.strictEqual(limiter.recordFailure('key').blocked, false);
assert.strictEqual(limiter.recordFailure('key').blocked, true);
assert.strictEqual(limiter.isBlocked('key'), true);
limiter.recordSuccess('key');
assert.strictEqual(limiter.isBlocked('key'), false);
} finally {
limiter.destroy();
}
});
test('健康检查同时验证数据库与磁盘', () => {
const service = createHealthService({
db: { prepare: () => ({ get: () => ({ ok: 1 }) }) },
diskPath: __dirname,
minimumFreeBytes: 0
});
const result = service.check();
assert.strictEqual(result.healthy, true);
assert.strictEqual(result.checks.database.status, 'ok');
assert.strictEqual(result.checks.disk.status, 'ok');
assert.ok(result.checks.disk.freeBytes > 0);
assert.strictEqual(fs.existsSync(__dirname), true);
assert.strictEqual(normalizeMinimumFreeBytes(-1), 256 * 1024 * 1024);
});
test('数据库异常会让健康状态降级', () => {
const service = createHealthService({
db: { prepare: () => { throw Object.assign(new Error('down'), { code: 'SQLITE_DOWN' }); } },
diskPath: __dirname,
minimumFreeBytes: 0
});
const result = service.check();
assert.strictEqual(result.healthy, false);
assert.strictEqual(result.checks.database.status, 'error');
});
test('错误响应携带 requestId 并隐藏服务端异常', () => {
const response = createResponse();
expressErrorHandler(new Error('secret detail'), {
id: 'req-3',
method: 'GET',
path: '/api/example'
}, response, () => assert.fail('不应继续'));
assert.deepStrictEqual(response.payload, {
success: false,
message: '服务器内部错误',
requestId: 'req-3'
});
});
console.log('\n========================================');
console.log('测试总结');
console.log('========================================');
console.log(`通过: ${results.passed}`);
console.log(`失败: ${results.failed}`);
process.exit(results.failed > 0 ? 1 : 0);