Files
Juno ChowandClaude Fable 5.1 f665082dad chore: 建立产品仓库,完成开源前的敏感信息清理
前端(Vite + React)、API/worker(Fastify + BullMQ)、api-contracts 契约包、
迁移脚本、测试与自托管 docker-compose 示例。

开源准备:运维脚本与机器信息已拆到私有仓库;公司名、备案号、隐私邮箱改读
publicCompliance.local.json(仓库只保留占位模板);站点地址与客服二维码改为
VITE_ 环境变量;fixtures 中的真实用户标识已匿名化;三个 package.json 标注
Apache-2.0。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-16 02:10:46 +08:00

133 lines
4.5 KiB
TypeScript

import { afterAll, describe, expect, it } from 'vitest';
import { buildApp, redactSensitiveRequestUrl } from '../src/app.js';
import { pool } from '../src/db/pool.js';
describe('API 全局响应安全边界', () => {
afterAll(async () => {
await pool.end();
});
it('成功响应携带 request ID 和安全响应头', async () => {
const app = buildApp();
try {
const response = await app.inject({ method: 'GET', url: '/api/health' });
expect(response.statusCode).toBe(200);
expect(response.headers['x-request-id']).toBeTruthy();
expect(response.headers['x-content-type-options']).toBe('nosniff');
} finally {
await app.close();
}
});
it('缺少 Origin 但浏览器声明 cross-site 的写请求会被拒绝', async () => {
const app = buildApp();
try {
const response = await app.inject({
method: 'POST',
url: '/api/auth/logout',
headers: { 'sec-fetch-site': 'cross-site' },
});
expect(response.statusCode).toBe(403);
expect(response.json()).toEqual(expect.objectContaining({
code: 'ORIGIN_NOT_ALLOWED',
requestId: expect.any(String),
}));
expect(response.headers['x-request-id']).toBe(response.json().requestId);
} finally {
await app.close();
}
});
it('火山真人回跳即使校验失败也禁止引用、缓存和索引', async () => {
const app = buildApp();
try {
const response = await app.inject({
method: 'GET',
url: '/api/providers/volc/real-portrait-validation/invalid?bytedToken=face-secret',
});
expect(response.statusCode).toBe(400);
expect(response.headers['referrer-policy']).toBe('no-referrer');
expect(response.headers['cache-control']).toBe('no-store, max-age=0');
expect(response.headers['x-robots-tag']).toBe('noindex, nofollow');
} finally {
await app.close();
}
});
it('真实路由使用共享严格 Schema 拒绝遗留字段', async () => {
const app = buildApp();
try {
const response = await app.inject({
method: 'POST',
url: '/api/auth/login',
payload: {
email: 'member@example.com',
password: 'Password123',
legacyToken: 'must-be-rejected-before-handler',
},
});
expect(response.statusCode).toBe(400);
expect(response.json()).toEqual({
message: '请求参数不正确',
code: 'VALIDATION_ERROR',
requestId: expect.any(String),
issues: [expect.objectContaining({
code: 'unrecognized_keys',
path: [],
})],
});
expect(response.headers['x-request-id']).toBe(response.json().requestId);
} finally {
await app.close();
}
});
it.each([
[
'/api/public/real-portrait-invites/invite-secret',
'/api/public/real-portrait-invites/[REDACTED]',
],
[
'/api/public/real-portrait-invites/invite-secret/status?poll=1',
'/api/public/real-portrait-invites/[REDACTED]/status?poll=1',
],
[
'/api/public/real-portrait-invites/invite-secret/start-verification',
'/api/public/real-portrait-invites/[REDACTED]/start-verification',
],
[
'/api/public/real-portrait-authorizations/manage-secret',
'/api/public/real-portrait-authorizations/[REDACTED]',
],
[
'/api/public/real-portrait-authorizations/manage-secret/revoke',
'/api/public/real-portrait-authorizations/[REDACTED]/revoke',
],
[
'/api/providers/volc/real-portrait-validation/callback-secret?source=volc',
'/api/providers/volc/real-portrait-validation/[REDACTED]?source=volc',
],
[
'/api/providers/volc/real-portrait-validation/callback-secret?bytedToken=face-secret&resultCode=10000&verify_type=real_time',
'/api/providers/volc/real-portrait-validation/[REDACTED]?bytedToken=[REDACTED]&resultCode=10000&verify_type=real_time',
],
[
'/portrait-consent/invite/invite-secret',
'/portrait-consent/invite/[REDACTED]',
],
[
'/portrait-consent/authorization/manage-secret?view=status',
'/portrait-consent/authorization/[REDACTED]?view=status',
],
])('日志 URL 会移除真人 bearer,同时保留路由后缀:%s', (value, expected) => {
expect(redactSensitiveRequestUrl(value)).toBe(expected);
});
it('日志 URL 不改写普通 API 路径与查询参数', () => {
const value = '/api/real-portrait-actors/11111111-1111-4111-8111-111111111111?include=assets';
expect(redactSensitiveRequestUrl(value)).toBe(value);
});
});