Skip to content

feat: WebUI 迁移 FastAPI 并对齐 AstrBot v4.26–v4.27.5(3.7.0) - #247

Merged
EterUltimate merged 6 commits into
mainfrom
feat/fastapi-webui-3.7.0
Sep 3, 2026
Merged

feat: WebUI 迁移 FastAPI 并对齐 AstrBot v4.26–v4.27.5(3.7.0)#247
EterUltimate merged 6 commits into
mainfrom
feat/fastapi-webui-3.7.0

Conversation

@EterUltimate

@EterUltimate EterUltimate commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

概述

基于 AstrBot v4.26.0 → v4.27.5(2026-06-24 ~ 2026-09-01 共 14 个稳定版、269 个 commit)的逐项排查结果,完成三部分工作:兼容性核查与修复、独立 WebUI 从 Quart 重构为 FastAPI(与 AstrBot core 技术栈对齐)、安全审查加强。版本号提升至 3.7.0

一、兼容性排查结论(v4.26.0 → v4.27.5)

逐项核对插件可见的变更面,确认以下全部兼容、无需改动:

  • v4.26.0 Quart→FastAPI 大迁移(#8688):register_web_api 签名不变,dashboard 侧 _match_registered_web_api 桥接 + Quart 兼容适配层完整保留;插件页 /plugins/{author}/{name}/pages/{page} 路由扫描契约不变。
  • v4.27.0 废弃 API 运行时告警(#9468/#9469):PersonaManager.update_persona / get_default_persona_v3 / StarTools.get_data_dir / astrbot.api.web 等签名核对无破坏;to_openai_to_calls_model 旧别名保留。
  • 消息媒体统一处理(#8764)、群聊上下文引用内容(#8634):插件不解析 Image/Record 组件,无影响。
  • v4.26.2 KV 存储卸载清理(#8291)、插件/工具启用状态拆分(#9048):插件不使用 KV、不注册 LLM Tool。
  • v4.26.8 按插件日志级别(#9342)+ astrbot.api.logger 插件级路由:零改动受益(热运行已验证生效)。
  • 可利用但暂缓的改进:v4.27.0 群消息历史持久化 + 检索 LLM Tool(#9465)可用于插件「停机补抓」学习素材,建议作为后续独立 feature。

已修复的 1 处不兼容:3 处已废弃的 Context.get_using_provider() 同步调用(v4.27.0 起运行时 DeprecationWarning)。新增 utils/framework_compat.py 兼容层,v4.27+ 优先 get_using_provider_async(),旧版自动回退同步接口。
另:core/page_api.py_body()/_query() 由惰性 from quart import request 改为官方契约 astrbot.api.web.request 代理(.json(default) / .query)。

二、WebUI 重构:Quart + Hypercorn → FastAPI + uvicorn

  • 新增 webui/compat.py 兼容层:真实 FastAPI 应用(uvicorn 承载)之上运行既有 Quart 风格处理器——模块级 request/session ContextVar 代理、jsonify/redirect/url_for/render_template/send_file(body, status) 元组返回归一化、<int:x> 路由转换;19 个蓝图文件仅更换导入行,约 135 条路由行为不变。
  • 会话迁移至 starlette SessionMiddleware(itsdangerous 签名 cookie,HttpOnly + SameSite=Lax + 7 天有效期不变);旧 Quart 会话 cookie 自然失效,需重新登录一次。
  • 服务线程 Hypercorn → uvicorn.Server(非主线程安全);端口清理与启动校验逻辑保留。
  • requirements.txtquart/quart-corsfastapi>=0.124.0/uvicorn>=0.30.0/itsdangerous>=2.2.0(core 自带,声明用于最小化安装);WebUI「依赖安装」基础清单同步。
  • 集成测试经兼容 test_client 原样运行(重定向不跟随、session_transaction 语义);test_webui_manager_imports_without_manual_web_dependencies 更新为新契约:禁用 quart/hypercorn 导入时服务器创建必须成功(防回退守卫保留)。

三、安全审查加强(VulnClaw MCP + 代码审查)

  • 密码哈希升级:MD5+盐 → PBKDF2-HMAC-SHA256(390,000 次迭代),历史 MD5/明文口令在登录成功后透明升级并持久化;比较使用 secrets.compare_digest
  • 免密模式保持不变(防止用户把自己锁在面板外),按讨论新增:/api/password_status 状态端点 + 前端一次性「建议启用 WebUI 密码」提醒横幅(可关闭,localStorage 记忆)。
  • CORS:不再返回 Access-Control-Allow-Credentials: true(原 Quart 回退路径会对任意 Origin 反射凭据授权)。
  • 安全响应头:全部响应新增 X-Content-Type-Options: nosniffX-Frame-Options: SAMEORIGINReferrer-Policy: no-referrerPermissions-Policy
  • 已核查无风险面:依赖安装端点使用固定包列表 + exec 形式子进程(无注入面);分享 token 为 secrets.token_urlsafe(32) + 过期 + 撤销;无 KV、无 LLM Tool、无 shell 注入参数来源。
  • 遗留说明:CSP 需与 SPA 内联样式 nonce 改造配合,暂未启用(见 CHANGELOG);HSTS 仅适用于 HTTPS 部署。

四、验证

  • 测试pytest tests/ 全量 759 passed(基线 756 + 新增密码迁移/免密提醒相关用例);web_src typecheck + 39 前端测试全绿;dashboard 产物已按 3.7.0 本地重建并提交。
  • 热运行(本地 AstrBot v4.27.5):插件 3.7.0 正常加载并初始化、SQLite WAL 引擎创建、独立 WebUI 于 uvicorn 正常服务、官方插件页 API 注册成功、学习调度器正常轮询;运行日志 0 Traceback(唯一 ERROR 为本机 PostgreSQL 不可达的既有环境问题,插件按设计回退 SQLite)。验证完成后本机已恢复原状态(bot 停止、插件恢复原禁用清单)。

版本同步

metadata.yaml、__init__.py、web_src/package.json、README.md、README_EN.md、docs/README.md 六处 + CHANGELOG 条目。

Summary by Sourcery

将独立 WebUI 迁移至 FastAPI/uvicorn,完成 AstrBot v4.26–v4.27.5 兼容性对齐,并强化密码认证与 WebUI 安全防护。

New Features:

  • 在免密 WebUI 模式下提供密码状态查询和一次性安全提醒。

Bug Fixes:

  • 修复 AstrBot v4.27 弃用同步 provider API 的兼容性问题,并改进错误响应以避免向客户端暴露内部异常信息。

Enhancements:

  • 将独立 WebUI 从 Quart/Hypercorn 迁移至 FastAPI/uvicorn,同时通过兼容层保持现有路由和处理器行为。
  • 将 WebUI 会话迁移至签名 Cookie,并加强 CORS、密码验证和安全响应头保护。
  • 完成 AstrBot v4.26.0 至 v4.27.5 的兼容性对齐并同步更新依赖、构建产物和版本信息。

Build:

  • 将 WebUI 运行依赖从 Quart/Hypercorn 体系切换为 FastAPI、uvicorn 和 itsdangerous。

Documentation:

  • 更新变更日志、README 及文档中的版本信息和 WebUI 架构说明。

Tests:

  • 更新 WebUI 集成测试以覆盖 FastAPI 兼容层和无 Quart/Hypercorn 环境。
  • 补充 PBKDF2 密码迁移、旧 MD5 拒绝和免密提醒相关测试。

…Bot v4.26-v4.27.5 (3.7.0)

- Add webui/compat.py: run existing Quart-style blueprint handlers on a real
  FastAPI app served by uvicorn (context proxies for request/session, jsonify,
  redirect/url_for/render_template/send_file, tuple-return normalization,
  Quart-semantic test client); blueprint files only swap their imports.
- Switch server thread from Hypercorn to uvicorn; session handling moves to
  starlette SessionMiddleware (itsdangerous cookie, HttpOnly + Lax + 7d).
- requirements.txt: quart/quart-cors -> fastapi/uvicorn/itsdangerous; update
  WebUI basic dependency tier accordingly.
- AstrBot v4.26.0-v4.27.5 compatibility sweep: migrate 3 deprecated
  Context.get_using_provider() calls via utils/framework_compat.py
  (async-first with sync fallback); verify register_web_api bridge,
  PersonaManager/StarTools APIs, plugin page routes all compatible.
- Security hardening: PBKDF2-HMAC-SHA256 password hashing with transparent
  MD5/plaintext upgrade on login (constant-time compare); drop
  Access-Control-Allow-Credentials from CORS; add nosniff / SAMEORIGIN /
  Referrer-Policy / Permissions-Policy response headers; add
  /api/password_status endpoint + one-time passwordless-mode reminder banner.
- page_api: read request body/query via astrbot.api.web request proxy
  (official FastAPI bridge contract) instead of lazy quart import.
- Tests: 759 passed; dashboard bundle rebuilt for 3.7.0.
@sourcery-ai

sourcery-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

本 PR 将独立 WebUI 的运行时从 Quart/Hypercorn 重构为 FastAPI/uvicorn,以 762 行兼容层承载既有 Quart 风格处理器并保持路由和测试语义,同时升级密码存储、补充安全响应头与免密提醒,修复 AstrBot v4.27 provider 弃用兼容性并统一发布版本至 3.7.0;验证覆盖 759 个 Python 测试、前端类型检查及 39 个前端测试。

Sequence diagram for transparent WebUI password migration

sequenceDiagram
    actor User
    User->>WebUI: Login request
    WebUI->>AuthService: verify_password_with_migration
    AuthService->>PasswordHasher: verify_password
    PasswordHasher-->>AuthService: Valid MD5 or PBKDF2 result
    alt Legacy plaintext or MD5 password
        AuthService->>PasswordHasher: hash_password
        PasswordHasher-->>AuthService: PBKDF2 hash and salt
        AuthService->>AuthService: Persist upgraded password config
    end
    AuthService-->>WebUI: Authentication result
    WebUI-->>User: Session cookie or login failure
Loading

Sequence diagram for the password status reminder

sequenceDiagram
    actor User
    User->>Dashboard: Open WebUI
    Dashboard->>Dashboard: Check localStorage dismissal
    alt Reminder not dismissed
        Dashboard->>WebUI: GET /api/password_status
        WebUI->>AuthService: is_password_enabled
        AuthService-->>WebUI: password_enabled status
        WebUI-->>Dashboard: Password status
        alt Password disabled
            Dashboard-->>User: Show password reminder banner
            User->>Dashboard: Click dismissal
            Dashboard->>Dashboard: Save dismissal in localStorage
        end
    end
Loading

File-Level Changes

Change Details Files
将独立 WebUI 从 Quart/Hypercorn 迁移到 FastAPI/uvicorn,并通过兼容层保持既有蓝图与路由行为。
  • 新增 FastAPI 应用包装、Quart 风格 Blueprint、request/session ContextVar 代理及响应/模板/文件辅助函数。
  • 将 19 个蓝图和相关中间件改用兼容层导入,保留约 135 条处理器路由。
  • 使用 Starlette SessionMiddleware 保留签名 Cookie、HttpOnly、SameSite=Lax 和 7 天有效期;服务线程改用 uvicorn.Server。
  • 同步替换运行时依赖、依赖安装清单及测试客户端,并增加禁止回退导入 Quart/Hypercorn 的测试。
webui/compat.py
webui/app.py
webui/server.py
webui/blueprints/__init__.py
webui/blueprints/*.py
webui/middleware/auth.py
webui/middleware/error_handler.py
webui/middleware/hub_aspects.py
webui/utils/response.py
requirements.txt
tests/integration/test_*
加强 WebUI 密码存储与认证安全,同时维持免密部署兼容性。
  • 将新密码哈希升级为 PBKDF2-HMAC-SHA256(390,000 次迭代),使用恒定时间比较。
  • 登录成功后将历史明文或 MD5 配置透明迁移为 PBKDF2,并持久化版本、算法和迭代参数。
  • 新增密码状态接口及前端一次性免密提醒,使用 localStorage 记录关闭状态。
  • 移除带凭据的任意 Origin CORS 响应,并为动态响应添加基础安全响应头。
utils/security_utils.py
webui/services/auth_service.py
webui/blueprints/auth.py
webui/app.py
webui/compat.py
web_src/src/components/feedback/PasswordReminder.tsx
web_src/src/components/feedback/PasswordReminder.module.scss
web_src/src/components/feedback/PasswordReminder.test.tsx
tests/unit/test_security_utils.py
修复 AstrBot v4.27 provider API 弃用问题并对齐官方 Web API 请求代理。
  • 新增跨版本 provider 访问兼容函数,优先异步接口,旧版本回退同步接口。
  • 替换 3 处同步 provider 调用,避免 v4.27 运行时弃用告警。
  • 将页面 API 的请求体和查询参数读取切换到 astrbot.api.web.request 契约。
utils/framework_compat.py
services/persona/persona_backup_manager.py
services/response/intelligent_responder.py
core/page_api.py
统一发布版本并提交重新构建的 Dashboard 产物与变更说明。
  • 将插件元数据、Python 包、前端包、Dashboard 和中英文文档版本提升至 3.7.0。
  • 更新 CHANGELOG,记录框架迁移、兼容性修复和安全变更。
  • 替换 Dashboard 构建后的 JS/CSS 哈希资源引用。
metadata.yaml
__init__.py
web_src/package.json
web_res/static/dashboard/index.html
web_res/static/dashboard/assets/index-Z__CDKuy.js
web_res/static/dashboard/assets/index-C77TMjSi.css
README.md
README_EN.md
docs/README.md
CHANGELOG.md

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Comment thread webui/compat.py


def jsonify(data: Any, **kwargs) -> WebUIJSONResponse:
return WebUIJSONResponse(data, **kwargs)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="utils/framework_compat.py" line_range="25" />
<code_context>
+    async_getter = getattr(context, "get_using_provider_async", None)
+    if inspect.iscoroutinefunction(async_getter):
+        return await async_getter()
+    return context.get_using_provider()
</code_context>
<issue_to_address>
**issue (bug_risk):** The compatibility helper calls `context.get_using_provider()` unconditionally when the async accessor is absent, despite documenting that unavailable providers return `None`. A compatible context lacking both accessors therefore raises `AttributeError` instead of returning `None` or handling the missing API.

**Triggers:** When a context object exposes neither `get_using_provider_async` nor `get_using_provider`.

**Suggested fix:** Check for the synchronous getter before calling it and return `None` when neither accessor exists.

```suggestion
    sync_getter = getattr(context, "get_using_provider", None)
    if sync_getter is None:
        return None
    return sync_getter()
```
</issue_to_address>

### Comment 2
<location path="utils/security_utils.py" line_range="366" />
<code_context>

         # 直接比较明文
         if password == stored_password:
-            # 验证成功后迁移到新格式
+            # 验证成功后迁移到哈希格式
             new_config = migrate_password_to_hashed(password_config)
</code_context>
<issue_to_address>
**🚨 issue (security):** Legacy plaintext passwords are still compared with ordinary `==`, while the new implementation only uses `secrets.compare_digest` for hashed passwords. Login timing therefore remains dependent on the matching prefix for plaintext configurations, contradicting the claimed constant-time password comparison during migration.

**Triggers:** When a user logs in against a legacy plaintext password configuration that has not yet been migrated.

**Suggested fix:** Compare encoded plaintext values with `secrets.compare_digest` before migrating the configuration.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 2 findings to address first, and this changes password hashing and legacy-login migration, session-cookie handling, CORS, and the production ASGI server, while routing all existing handlers through a large compatibility layer. A defect could expose or reject WebUI access, leak authenticated data through incorrect cookie/CORS behavior, or cause an outage; reverting would not undo sessions or data already exposed, although the affected scope is bounded and can be repaired.

Blocking findings: utils/framework_compat.py:25, utils/security_utils.py:366


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread utils/framework_compat.py Outdated
Comment thread utils/security_utils.py
- Constant-time compare for legacy plaintext password verification
- Guard both provider accessors in framework_compat (return None if absent)
- Suppress CodeQL weak-hashing alert on legacy MD5 verification path with
  justification; break exception chain in compat.get_json
Comment thread utils/security_utils.py Fixed
… connect

- Remove hash_password_md5 and the MD5 upgrade branch: legacy MD5 password
  configs are now rejected with a reset hint instead of being accepted
  (no real-world MD5 config instances exist; PBKDF2 is the only format).
- Probe port availability with connect instead of binding the wildcard
  address (CodeQL py/bind-socket-all-network-interfaces).
- Raise the JSON parse error outside the except block so no exception
  chain can leak to clients (CodeQL py/stack-trace-exposure).
…esponses

Replace str(e)/f-string exception details in jsonify/error_response payloads
with fixed user-facing messages across blueprints; internal details stay in
server logs via logger.error(..., exc_info=True). ValueError messages from
the persona backup service are kept (they are user-facing validation copy,
not stack traces) and annotated accordingly.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New security issues found

Comment thread web_res/static/dashboard/assets/index-Z__CDKuy.js
Comment thread web_res/static/dashboard/assets/index-Z__CDKuy.js
@EterUltimate

Copy link
Copy Markdown
Collaborator Author

@sourcery-ai review

@sourcery-ai

sourcery-ai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Sorry @EterUltimate, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 1 day and 11 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@EterUltimate
EterUltimate merged commit 122ffa8 into main Sep 3, 2026
19 checks passed
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.

2 participants