Skip to content

工程化加固、安全与性能修复、API 契约统一 snake_case(含 CI 测试流水线) - #514

Open
Marrrrrrrrry wants to merge 36 commits into
vastsa:masterfrom
Marrrrrrrrry:fix/m0-stop-bleeding
Open

工程化加固、安全与性能修复、API 契约统一 snake_case(含 CI 测试流水线)#514
Marrrrrrrrry wants to merge 36 commits into
vastsa:masterfrom
Marrrrrrrrry:fix/m0-stop-bleeding

Conversation

@Marrrrrrrrry

Copy link
Copy Markdown

…cleanup

  • add pyproject.toml with pytest config and dev dependency group (PEP 735)
  • add ruff (core correctness rules) + pre-commit hook
  • fix stored XSS: html.escape site config before index template injection, with regression tests
  • log previously swallowed cleanup exceptions in upload rollback paths
  • consolidate three duplicate path generators into build_file_path (also aligns chunk/presign date dirs to get_now() instead of local time)
  • drop redundant ip_limit sync in load_config (refresh_settings covers all limiters)
  • remove dead code (calculate_file_hash, commented-out block), two orphan guide docs, and stale .gitignore entries
  • add dependabot for pip, docker, and github-actions
  • bump starlette 1.3.1 -> 1.6.0 (no breaking API changes; verified with the full test suite plus an ASGI smoke test incl. Range downloads)
  • skip theme asset tests when themes/ is not built locally

…cleanup

- add pyproject.toml with pytest config and dev dependency group (PEP 735)
- add ruff (core correctness rules) + pre-commit hook
- fix stored XSS: html.escape site config before index template injection,
  with regression tests
- log previously swallowed cleanup exceptions in upload rollback paths
- consolidate three duplicate path generators into build_file_path
  (also aligns chunk/presign date dirs to get_now() instead of local time)
- drop redundant ip_limit sync in load_config (refresh_settings covers all limiters)
- remove dead code (calculate_file_hash, commented-out block), two orphan
  guide docs, and stale .gitignore entries
- add dependabot for pip, docker, and github-actions
- bump starlette 1.3.1 -> 1.6.0 (no breaking API changes; verified with the
  full test suite plus an ASGI smoke test incl. Range downloads)
- skip theme asset tests when themes/ is not built locally
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

@Marrrrrrrrry is attempting to deploy a commit to the vastsa's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Marrrrrrrrry

Copy link
Copy Markdown
Author

我要加入vercel组吗?然后问下最近的迭代计划 我不太喜欢处理分支冲突(我知道ai能做但是怕我们在相同开发位置撞车) 如果你最近有开发计划我就等等再修复/做新功能和pr

- tests/helpers.py: SettingsOverrideMixin + init_memory_db()/close_db()
- tests/conftest.py: httpx ASGI client fixtures (db, client, initialized_client)
- enable pytest asyncio_mode=auto
- replace 6 duplicated SettingsOverrideMixin copies and 4 duplicated
  Tortoise.init blocks across the legacy unittest suite
… rehash

- new hashes use scrypt (n=2^14, r=8, p=1); sha256 and plaintext formats
  keep verifying unchanged, so existing deployments are unaffected
- on successful login a legacy-stored admin password is rehashed to scrypt
  in the settings KeyValue row
- add password_needs_rehash() and extend scheme detection; stdlib only, no
  new dependency
…148 lines)

- apps/base/setup_wizard.py: form parsing rules, setup options, and the
  two admin-facing HTML pages
- apps/base/pages.py: setup/assets/index/robots/public-config routes and
  theme resolution (404 handler registered on the app in main.py)
- tests updated to import from the new modules
…) and add config schema guard

- opendal_scheme was read directly by the OpenDAL backend but never declared
  in DEFAULT_CONFIG: selecting that backend crashed with AttributeError
- chunk_expire_hours existed only as getattr fallbacks in two modules
- refresh_settings now warns about unknown user_config keys
- meta-test scans the codebase so any future settings.X reference without a
  DEFAULT_CONFIG declaration fails the suite
- 20 camelCase keys (uploadSize, expireStyle, openUpload, ...) renamed across
  DEFAULT_CONFIG, settings accessors, setup form fields, and ConfigService
- migrations_007 renames keys inside the existing settings KeyValue row,
  preserving all values for existing deployments
- schema guard meta-test keeps future keys declared
…share (D2)

- storage contract drops UploadFile in favor of BinaryIO (+content_type param),
  completing the framework decoupling of the storage layer
- LocalFileClass.read() leaked an open fd and its handle was passed where
  save_file expected UploadFile — /admin/local/share 500ed on every call;
  now reads bytes via with-block and shares correctly (regression tests added)
- OneDrive save_file additionally had save_path called as a function — fixed
- quota.get_storage_usage (runs on every upload) now aggregates via Sum
  instead of loading every row
- dashboard simple counters (total/size/expired/active/text/chunked/used,
  yesterday/today, top suffixes, recent 8) moved to SQL; the per-row health
  rules engine keeps its single pass (documented)
…ess lock (D5)

settings/activities/view-presets are whole-blob KeyValue rows; concurrent
admin operations could silently drop each other's writes (last-writer-wins).
Single-worker deployment assumption documented; lock is process-local.
…e (2.3)

share_text/share_file/complete_upload/presign proxy/presign confirm now
delegate quota reservation, storage writes, record creation, rollback and
chunk cleanup to service methods; handlers keep auth, request validation,
rate limiting and response wrapping. validate_file_size and
PRESIGN_SESSION_EXPIRES move to services as shared single sources.
Drop the message/msg envelope twin, all camel+snake dual-field response
pairs (file list/detail/policy/storage, dashboard counters, batch op
counts, status insights, presets, metadata), and camel request fields
(clearExpiredAt, downloadLimit, sortBy/sortOrder, maxChars). Stored
KeyValue JSON (metadata, presets, activities) keeps legacy-key read
fallbacks so existing deployments normalize transparently.
…sign page

API version 2.1.0 -> 2.5.6; response examples use the single message
field; dashboard/list examples and params match current snake_case
contract; drop stale save_path from presign init response; add English
presign-upload page and sidebar entry; uploadSize -> upload_size.
Sync zh/en guide docs with migration 007 key names (upload_size,
open_upload, enable_chunk, expire_style, error_count, etc.) so existing
deployments following the docs use the current schema.
build_public_config/build_public_meta served a mixed camel/snake
contract (allowedFileTypes, meta.features.*, api.legacyConfig,
limits.uploadWindowMinutes). Rename to snake_case; frontend reads
allowed_file_types only.
- ci.yml: ruff + pytest on push/PR, installing from the hashed lockfile
  with --require-hashes, plus a lockfile-vs-requirements consistency
  check (dependabot cannot regenerate the lockfile; drift was silent).
- test_api_contract.py: deep-scans public config, dashboard, admin file
  list and share metadata responses for camelCase keys - the regression
  net for the D7 public-config leak found during review.
@Marrrrrrrrry Marrrrrrrrry changed the title chore: engineering hardening - test tooling, lint, security fix, and … 工程化加固、安全与性能修复、API 契约统一 snake_case(含 CI 测试流水线) Sep 10, 2026
@Marrrrrrrrry

Copy link
Copy Markdown
Author

概述

本 PR 包含 36 个提交,覆盖:工程化基建补齐、安全加固、性能修复、架构重构与 API 契约统一。
全部变更已经过四层验证(详见文末测试报告),并附带 GitHub Actions 测试流水线,使回归可被 CI 捕获。

合入前置条件(请务必阅读)

  1. 必须与本仓库前端仓库的契约 PR 同窗口合入:本 PR 将 API 字段统一为 snake_case。
    请先合入 FileCodeBoxFronted 的对应 PR,再合入本 PR。两个仓库各自独立发版,
    合入一半时构建的镜像会出现前后端字段不匹配。
  2. 2023 主题仓库(FileCodeBoxFronted2023)需要同样的字段同步,否则新后端下 2023 主题的管理端
    无法正常显示。建议在本 PR 合入前同步修改,或临时将 themes_choices 收敛为 2024。
  3. 本 PR 含 .github/workflows/ci.yml(测试流水线,无 secrets 依赖)。

变更清单

工程化基建

  • 新增 pytest 配置(pyproject.toml)、ruff + pre-commit、.github/workflows/ci.yml
    (ruff + pytest + 锁定文件一致性校验,依赖经 --require-hashes 安装,与 Docker 构建同路径)
  • 新增 requirements.lock.txt(uv pip compile --generate-hashes,全树 SHA256 锁定),Docker 构建改用锁定文件
  • 测试基座去重(conftest.py + ASGI fixtures),补齐三组高危路径测试与真实 ASGI 旅程测试

安全修复

  • 管理员密码哈希升级为 scrypt(n=2^14,r=8,p=1),存量 sha256/明文格式继续可验证,登录成功时透明重哈希
  • background 配置项校验为 http(s) URL(写入入口 400 拒绝),封堵 2023 主题 CSS url('') 单引号逃逸
  • 首页模板变量 html.escape(普通配置渲染零变化)

性能修复

  • D1 初始化探测不再每请求跑全量 scrypt(实测原 ~56ms/请求)
  • D3 仪表盘计数与存储配额改 SQL 聚合,消除全表载入
  • D6 配置刷新加 2s TTL,写路径强制刷新(消除每请求查库)

功能修复

  • D2 管理端本地文件分享从未可用(文件句柄泄漏 + save_file 入参契约),已修复并补测试
  • D4 FileCodes.expired_count 默认值 -1(时间式),避免创建即过期的记录
  • D5 KeyValue JSON 读改写段(settings/activities/presets)加进程锁串行化

架构重构

  • main.py 945 → 148 行:setup 向导与公开页面拆至 apps/base/setup_wizard.py、pages.py
  • 打破 core↔apps 循环依赖:config/tasks 迁至 apps/base;core/ 不再 import apps
  • 存储层剥离框架:StorageError(响应形状与 HTTPException 逐字一致)+ StoredDownload 数据契约,
    core/ 零 ORM import,五个存储后端不再构造 Response
  • FileUploadService 统一五个上传入口的业务编排(配额预留/存储写入/建记录/失败回滚)

API 契约统一(向前不兼容)

  • 配置键全量 snake_case,附 migrations_007 对存量部署的 KeyValue 值保留式改名(已用仿真存量库验证)
  • API 响应删除 message/msg 双发;所有 camel+snake 双发字段仅保留 snake_case
  • 请求字段 clearExpiredAt/downloadLimit/sortBy/maxChars → snake_case
  • 存量 KeyValue JSON(元数据/预设/活动)读取侧保留旧键 fallback,升级零手工操作

测试验证

  • 自动化测试:104 passed / 2 skipped(含 ASGI 全链路旅程、数据删除任务、并发配额、契约守护)
  • 代码层复核:36 提交分组核验(行为等价/牵连面/误删扫描)全部通过
  • 浏览器端到端:11 项真实浏览器全链路(初始化→分享→取件→管理端全功能→字节级下载校验)
  • Docker 产物:真实构建(双主题 + --require-hashes)+ 容器运行冒烟 7 项

已知边界

  • 2023 主题前端需同步(见前置条件 2)
  • S3/WebDAV/OneDrive/OpenDAL 远端后端未做真机验证(本地存储全链路已覆盖)
  • 反向代理部署形态建议上线前实测 trusted_proxies 行为

@Marrrrrrrrry

Copy link
Copy Markdown
Author

本地我做了e2e docker构建 冒烟 但是没做现有用户升级测试

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant