9.3 KiB
9.3 KiB
视频下载同源修复 + 安全漏洞第一批修复 (Day 32)
概述
今天的工作聚焦四件事:
- 修复首页与发布成功弹窗点击下载时被浏览器当作在线播放(新开标签页)的问题。
- 将下载修复开始后的开发内容从
Day31拆分到Day32,保持日志按天清晰归档。 - 根据安全审计报告(
Temp/安全审计报告.md),实施第一批 6 项无功能风险的安全修复。 - 统一弹窗关闭交互:默认支持点空白关闭,发布成功清理弹窗保持强制留存。
✅ 1) 视频下载链路修复(避免新开标签页播放)
问题现象
- 首页“下载视频”与发布成功弹窗“下载视频备份”在部分浏览器会打开新标签页播放视频,而不是直接触发下载。
- 根因是跨域签名 URL 场景下,浏览器可能忽略
<a download>。
修复方案
- 后端新增同源下载接口:
GET /api/videos/generated/{video_id}/download- 使用
FileResponse返回本地视频文件 - 显式返回
Content-Disposition: attachment - 浏览器直接进入保存文件流程
- 使用
- 发布成功弹窗下载改为传
videoId,不再依赖签名 URL。 - 首页作品预览下载同步改为同源下载接口,下载行为与发布弹窗统一。
- 兼容旧清理状态:
CleanupContext对旧videoDownloadUrl持久化字段做videoId解析回填。
✅ 2) 配套调整与文档拆分
前端联动
CleanupContext继续沿用“清理失败不关弹窗、不清本地”的逻辑,下载链路仅替换为同源接口。- 首页
PreviewPanel支持传入generatedVideoId,下载按钮优先走/api/videos/generated/{id}/download。
日志归档
- 将“下载修复开始后的内容”从
Day31移出并归档到Day32。 Day31保留 Day31 当日核心内容(到 cleanup 链路加固为止)。
✅ 3) 安全漏洞第一批修复(6 项,无功能风险)
根据安全审计报告,实施第一批 6 项可直接修复的安全加固项。
3.1 JWT 默认密钥启动拦截
- 文件:
backend/app/main.py - 新增
check_jwt_secretstartup 事件(在init_admin之前) - 当
JWT_SECRET_KEY仍为默认值"your-secret-key-change-in-production"时:- 生产环境(
DEBUG=False):raise RuntimeError直接阻止服务启动 - 开发环境(
DEBUG=True):输出CRITICAL级别日志告警,不阻止启动
- 生产环境(
3.2 AI / Tools 接口加认证
- 文件:
backend/app/modules/ai/router.py、backend/app/modules/tools/router.py - AI 路由 3 个端点(
/translate、/generate-meta、/rewrite)均增加Depends(get_current_user) - Tools 路由 1 个端点(
/extract-script)增加Depends(get_current_user) - 前端 axios 已有
withCredentials: true,401 自动跳登录页,无需前端改动
3.3 素材路径穿越修复
- 文件:
backend/app/modules/materials/router.py、backend/app/modules/materials/service.py stream、delete_material、rename_material三处在startswith(user_id)校验之前新增..拒绝- 含
..的material_id直接返回 400 delete_material路由补充except ValueError→ 400(原先仅 catchPermissionError,ValueError会被Exception兜底返回 500)
3.4 video_id 白名单校验
- 文件:
backend/app/modules/videos/router.py download_generated和delete_generated两个端点在函数开头增加正则校验- 仅允许
^[A-Za-z0-9_-]+$,不符合直接返回 400
3.5 上传/下载大小限制
- materials/service.py(流式上传):在 chunk 累加后检查
MAX_UPLOAD_SIZE_MB(默认 500MB),超限抛ValueError - ref_audios/service.py(参考音频):
await file.read()后检查 5MB 上限 - tools/service.py(文案提取文件上传):将
shutil.copyfileobj替换为分块拷贝 + 500MB 限制 - tools/service.py(URL 下载分支):
_download_video返回后检查文件体积,超 500MB 删除临时文件并拒绝
3.6 错误信息通用化
- ai/router.py:3 处
detail=str(e)分别改为"翻译服务暂时不可用"、"生成标题标签失败"、"改写服务暂时不可用" - tools/router.py:保留 "Fresh cookies" 特定分支提示,fallback 改为"文案提取失败,请稍后重试"
- generated_audios/service.py:任务失败
error字段从traceback.format_exc()改为str(e),traceback 仅写入服务端日志
✅ 4) 弹窗关闭交互统一(UX)
目标
- 保持统一交互预期:业务弹窗默认可通过
X与点击遮罩关闭。 - 保留关键流程保护:发布成功清理弹窗继续禁止遮罩关闭,避免误触导致流程中断。
调整内容
- 文案提取弹窗(
ScriptExtractionModal)支持点击遮罩关闭。 - AI 改写弹窗(
RewriteModal)支持点击遮罩关闭。 - 发布页扫码登录弹窗支持点击遮罩关闭。
- 修改密码弹窗支持点击遮罩关闭。
- 录音弹窗采用动态策略:
closeOnOverlay={!isRecording}- 未录音:允许遮罩关闭
- 录音中:禁止遮罩关闭(防误触);
X关闭仍可用,且会先停止录音再关闭
- 发布成功清理弹窗维持
closeOnOverlay=false,并且不提供onClose(无右上角关闭按钮)。
📁 今日主要修改文件
| 文件 | 改动 |
|---|---|
backend/app/modules/videos/router.py |
新增 GET /api/videos/generated/{video_id}/download,返回 attachment 下载响应;新增 video_id 白名单正则校验(^[A-Za-z0-9_-]+$) |
frontend/src/features/publish/model/usePublishController.ts |
发布成功后 triggerCleanup() 传 video.id(替换签名 URL) |
frontend/src/shared/contexts/CleanupContext.tsx |
下载字段改为 videoId;兼容旧 videoDownloadUrl 回填;下载按钮改同源路径 |
frontend/src/features/home/ui/PreviewPanel.tsx |
首页下载改为同源下载接口 |
frontend/src/features/home/ui/HomePage.tsx |
透传 generatedVideoId 给 PreviewPanel |
frontend/src/features/home/ui/ScriptExtractionModal.tsx |
弹窗支持点击遮罩关闭(closeOnOverlay) |
frontend/src/features/home/ui/RewriteModal.tsx |
弹窗支持点击遮罩关闭(closeOnOverlay) |
frontend/src/features/publish/ui/PublishPage.tsx |
扫码登录弹窗支持点击遮罩关闭 |
frontend/src/components/AccountSettingsDropdown.tsx |
修改密码弹窗支持点击遮罩关闭 |
frontend/src/features/home/ui/RefAudioPanel.tsx |
录音弹窗改为 closeOnOverlay={!isRecording}(录音中禁遮罩关闭) |
Docs/DevLogs/Day31.md |
移除下载修复章节与对应验证/覆盖项(迁入 Day32) |
Docs/TASK_COMPLETE.md |
新增 Day32 Current 区块,Day31 取消 Current |
Docs/BACKEND_README.md |
补充 /api/videos/generated/{video_id}/download 接口说明 |
Docs/BACKEND_DEV.md |
补充下载接口 attachment 约定 |
Docs/FRONTEND_README.md |
补充首页/发布弹窗下载统一同源接口说明 |
Docs/FRONTEND_DEV.md |
补充 CleanupContext 下载策略规范 |
Docs/PUBLISH_DEPLOY.md |
补充发布成功后同源下载联动说明 |
README.md |
补充”一键下载直达(同源 attachment)”能力描述 |
backend/app/main.py |
check_jwt_secret startup 事件:生产环境(DEBUG=False)强拦截启动,开发环境 CRITICAL 告警 |
backend/app/modules/ai/router.py |
3 个端点加 Depends(get_current_user) 认证;错误返回改为通用消息 |
backend/app/modules/tools/router.py |
extract-script 端点加 Depends(get_current_user) 认证;错误返回改为通用消息 |
backend/app/modules/materials/router.py |
stream 端点新增 .. 路径穿越拒绝;delete 端点补充 except ValueError → 400 |
backend/app/modules/materials/service.py |
delete_material / rename_material 新增 .. 路径穿越拒绝;流式上传增加 MAX_UPLOAD_SIZE_MB 大小限制 |
backend/app/modules/ref_audios/service.py |
参考音频上传增加 5MB 大小限制 |
backend/app/modules/tools/service.py |
文案提取文件上传替换为限大小分块拷贝(500MB);URL 下载分支增加下载后体积检查(500MB) |
backend/app/modules/generated_audios/service.py |
任务失败错误字段从 traceback.format_exc() 改为 str(e),避免泄露内部路径 |
🔍 验证记录
python -m py_compile backend/app/modules/videos/router.py✅npm run build(frontend)✅npm run build(frontend,弹窗关闭策略调整后复验)✅pm2 restart vigent2-frontend✅pm2 restart vigent2-backend✅curl http://127.0.0.1:8006/health返回{"status":"ok"}✅- 安全修复第一批语法验证:
python -m py_compile backend/app/main.py backend/app/modules/materials/router.py backend/app/modules/tools/service.py backend/app/modules/ai/router.py backend/app/modules/tools/router.py backend/app/modules/materials/service.py backend/app/modules/ref_audios/service.py backend/app/modules/videos/router.py backend/app/modules/generated_audios/service.py✅ - 未登录调用
/api/ai/translate→ 返回 401 ✅ - 未登录调用
/api/tools/extract-script→ 返回 401 ✅ - 收尾三刀语法验证:
python -m py_compile backend/app/main.py backend/app/modules/materials/router.py backend/app/modules/tools/service.py✅