加固历史恢复入口默认禁用
This commit is contained in:
parent
2e926451a4
commit
5be1d5ba45
87
app/api/restore-data/__tests__/route.test.ts
Normal file
87
app/api/restore-data/__tests__/route.test.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { NextRequest } from 'next/server'
|
||||
|
||||
jest.mock('@/lib/prisma', () => ({
|
||||
prisma: {
|
||||
user: {
|
||||
create: jest.fn(),
|
||||
findUnique: jest.fn(),
|
||||
},
|
||||
category: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
scene: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
material: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
function createRequest(token?: string) {
|
||||
return new NextRequest('http://localhost/api/restore-data', {
|
||||
method: 'POST',
|
||||
headers: token ? { 'x-restore-data-token': token } : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
async function loadPost() {
|
||||
const route = await import('../route')
|
||||
return route.POST
|
||||
}
|
||||
|
||||
describe('POST /api/restore-data', () => {
|
||||
const originalEnv = process.env
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules()
|
||||
process.env = { ...originalEnv }
|
||||
delete process.env.RESTORE_DATA_ENABLED
|
||||
delete process.env.RESTORE_DATA_TOKEN
|
||||
delete process.env.RESTORE_ADMIN_PASSWORD
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
process.env = originalEnv
|
||||
})
|
||||
|
||||
it('默认未显式启用时拒绝执行', async () => {
|
||||
process.env.RESTORE_DATA_TOKEN = 'restore-token'
|
||||
process.env.RESTORE_ADMIN_PASSWORD = 'configured-password'
|
||||
const POST = await loadPost()
|
||||
|
||||
const response = await POST(createRequest('restore-token'))
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(503)
|
||||
expect(data.success).toBe(false)
|
||||
expect(data.message).toContain('RESTORE_DATA_ENABLED=1')
|
||||
})
|
||||
|
||||
it('启用后仍要求配置恢复令牌', async () => {
|
||||
process.env.RESTORE_DATA_ENABLED = '1'
|
||||
process.env.RESTORE_ADMIN_PASSWORD = 'configured-password'
|
||||
const POST = await loadPost()
|
||||
|
||||
const response = await POST(createRequest())
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(503)
|
||||
expect(data.success).toBe(false)
|
||||
expect(data.message).toContain('RESTORE_DATA_TOKEN')
|
||||
})
|
||||
|
||||
it('启用且配置令牌后会拒绝错误令牌', async () => {
|
||||
process.env.RESTORE_DATA_ENABLED = '1'
|
||||
process.env.RESTORE_DATA_TOKEN = 'restore-token'
|
||||
process.env.RESTORE_ADMIN_PASSWORD = 'configured-password'
|
||||
const POST = await loadPost()
|
||||
|
||||
const response = await POST(createRequest('wrong-token'))
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(401)
|
||||
expect(data.success).toBe(false)
|
||||
expect(data.message).toBe('恢复令牌无效')
|
||||
})
|
||||
})
|
||||
@ -9,6 +9,7 @@ import bcrypt from 'bcryptjs';
|
||||
const UPLOADS_DIR = '/app/uploads';
|
||||
const S3_BUCKET_URL = process.env.RESTORE_S3_BUCKET_URL as string;
|
||||
const S3_PUBLIC_URL = process.env.RESTORE_S3_PUBLIC_URL as string;
|
||||
const RESTORE_DATA_ENABLED = process.env.RESTORE_DATA_ENABLED?.trim() === '1';
|
||||
const RESTORE_DATA_TOKEN = process.env.RESTORE_DATA_TOKEN?.trim() || '';
|
||||
|
||||
// 默认配置
|
||||
@ -107,6 +108,13 @@ function matchFiles(previewFiles: string[], materialFiles: string[]): Match[] {
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
if (!RESTORE_DATA_ENABLED) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: '未显式启用 RESTORE_DATA_ENABLED=1,恢复接口已禁用',
|
||||
}, { status: 503 });
|
||||
}
|
||||
|
||||
if (!RESTORE_DATA_TOKEN) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
|
||||
@ -539,7 +539,7 @@
|
||||
- 已修复:`failWithCleanup()` 会在模板校验、数据库写入等失败路径调用 `cleanupUploadedArtifacts()`。
|
||||
- 已修复:成功创建素材记录后会清空待清理列表,避免误删正常素材文件。
|
||||
- 已修复:上传入口会在处理预览图、展示图和原始素材文件前先确认模板存在且启用。模板已停用或不存在时会直接拒绝,不再让客户等大文件上传完才失败,也不会写入对象存储。
|
||||
- 仍需后续处理:上传过程仍有整文件进内存问题,归到 P1-11。
|
||||
- 仍需后续处理:大文件内存风险已在 P1-11 做过三轮流式收口;当前剩余重点是预览图、PSD 展示图限制,以及后续分片上传/断点续传。
|
||||
|
||||
建议动作:
|
||||
|
||||
@ -888,6 +888,22 @@ AI 改图以后会消耗外部额度、CPU、存储和客户素材数据。即
|
||||
- 已完成:删除公开 debug API 路由。
|
||||
- 已完成:增加生产路由边界测试,后续需要诊断模板结构时应走登录后的管理员页面或受控脚本,不再开放匿名 debug API。
|
||||
|
||||
### P1-22 历史数据恢复接口是强写入口(已完成默认禁用加固)
|
||||
|
||||
客户影响:
|
||||
|
||||
`/api/restore-data` 是旧的一次性数据恢复入口,会创建管理员、默认分类、默认场景,并把本地文件和外部 S3 预览图写回素材表。它不属于普通后台操作,如果客户环境误留恢复令牌,等于留下一个绕过后台登录体系的强写入口。
|
||||
|
||||
证据:
|
||||
|
||||
- `app/api/restore-data/route.ts`:恢复入口现在必须同时满足 `RESTORE_DATA_ENABLED=1` 和 `RESTORE_DATA_TOKEN` 正确才会执行;默认未显式启用时直接返回禁用。
|
||||
- `app/api/restore-data/__tests__/route.test.ts`:新增测试覆盖默认禁用、启用后缺少令牌仍拒绝、错误令牌拒绝。
|
||||
|
||||
建议动作:
|
||||
|
||||
- 已完成:恢复接口默认显式禁用,避免只因误留 token 就开放历史强写入口。
|
||||
- 后续建议:正式交付包如不再需要该历史恢复能力,应单独评估物理移除或迁移到离线脚本。
|
||||
|
||||
## 5. P2 问题
|
||||
|
||||
### P2-01 监控页普通登录用户可进入(已修复)
|
||||
@ -1339,8 +1355,8 @@ NAS 私有化系统可能在平板或窄屏浏览器里使用。模板编辑器
|
||||
1. 统一超管、全局管理员、部门管理员、普通用户权限 helper。
|
||||
2. 系统设置、用户管理、破坏性操作拆权限。
|
||||
3. 统一素材编辑权限。(已完成第一轮)
|
||||
4. 模板管理增加免费版升级提示。
|
||||
5. 模板管理增加免费版升级提示。
|
||||
4. 模板管理增加免费版升级提示。(已完成)
|
||||
5. 继续确认系统监控、转码统计、模板管理可见性是否需要按部门范围裁剪或仅最高权限可见。
|
||||
|
||||
并行推进的前端整改顺序:
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user