diff --git a/docs/superpowers/specs/2026-06-24-qqbot-napcat-webui-gateway-design.md b/docs/superpowers/specs/2026-06-24-qqbot-napcat-webui-gateway-design.md new file mode 100644 index 0000000..8f1e24a --- /dev/null +++ b/docs/superpowers/specs/2026-06-24-qqbot-napcat-webui-gateway-design.md @@ -0,0 +1,386 @@ +# QQBot NapCat WebUI Gateway Design + +## Background + +QQBot already manages NapCat containers, WebUI tokens, login sessions, runtime +profile evidence, and split account status inside the API. Admin can observe and +drive the curated login flow, but it cannot open the original NapCat WebUI for a +specific account without manually exposing container ports and tokens. + +The new requirement is to open the original NapCat WebUI from Admin for one +QQBot account, with full WebUI operation capability, while keeping NapCat +container addresses, WebUI tokens, credentials, and host topology server-side. + +## Goals + +- Add a QQBot account-row operation that opens the selected account's NapCat + WebUI in a second-level Admin route page. +- Implement NapCat WebUI access through an independent + `kt-napcat-webui-gateway` service, not through direct browser access to + NapCat container ports. +- Bind gateway access sessions to the Admin route page lifecycle. +- Allow full NapCat WebUI operations inside the embedded page. +- Keep NapCat WebUI tokens, NapCat credentials, container IPs, host ports, and + NAS topology out of browser-visible payloads. +- Record auditable evidence for session creation, activation, heartbeat, + revocation, expiration, and proxy failures. + +## Non-goals + +- Do not reimplement the NapCat WebUI as native Admin components. +- Do not make the first version read-only. +- Do not expose NapCat container ports to the public network. +- Do not let the browser call arbitrary upstream URLs through the gateway. +- Do not merge this gateway into the existing login SSE state machine. + +## User Experience + +Admin adds a new WebUI action on the QQBot account list connection/action area. +Clicking it navigates to: + +```text +/qqbot/account/:accountId/napcat-webui +``` + +The route page is a remote console, not a drawer. It has: + +- a compact Admin-owned header with back navigation, account identity, container + status, session status, refresh session, and close session actions; +- one full-height iframe that loads the gateway URL; +- a failure state for unauthorized, missing container, WebUI offline, session + expired, or gateway unavailable; +- no page-level body scrolling beyond the existing Vben layout content area. + +The route page owns the gateway session. Opening the same WebUI from the account +list always creates a new route-bound session. Page refresh creates a new +session and lets the previous session expire or be revoked by heartbeat timeout. + +## High-level Architecture + +```text +Admin account list + -> Admin route /qqbot/account/:accountId/napcat-webui + -> API: create route-bound gateway session + -> Gateway: store active session and NapCat target metadata + -> Admin iframe: gateway bootstrap URL + -> Gateway: proxy NapCat WebUI HTML, assets, APIs, and WebSocket traffic + -> NapCat WebUI container +``` + +The API remains the authority for Admin authentication and QQBot account +binding. The Gateway remains the authority for active WebUI proxy sessions and +NapCat WebUI credential exchange. + +## Service Boundaries + +### API service + +The API service owns Admin-facing authorization and account resolution: + +- verify the Admin JWT and QQBot account permissions; +- resolve account, selfId, primary NapCat binding, container id/name, WebUI + base URL, and WebUI token from existing persistence; +- reject missing, offline, or non-owned NapCat targets before a gateway session + is created; +- call the Gateway internal API with a service-to-service secret; +- return only an opaque `sessionId`, `iframeUrl`, and safe display metadata to + Admin; +- proxy heartbeat/revoke calls from Admin to the Gateway internal API; +- write or forward audit events. + +The API must not return `webuiToken`, NapCat credential, upstream host, upstream +port, Docker container IP, or NAS SSH details. + +### Gateway service + +`kt-napcat-webui-gateway` is deployed as a separate process, image, and K8s +Deployment. It owns: + +- active session storage and TTL enforcement; +- one-time iframe bootstrap tickets; +- NapCat WebUI credential login using the existing `sha256(token + ".napcat")` + contract; +- proxying HTTP methods, static assets, WebUI API calls, redirects, cookies, and + WebSocket upgrade traffic; +- session heartbeat, revocation, expiration, and cleanup; +- gateway-side audit events and structured logs. + +The Gateway only proxies to the target already stored in the session. It never +accepts a browser-supplied upstream URL. + +### Admin frontend + +Admin owns the route UI and lifecycle: + +- navigate from account list action to the second-level route page; +- create a session when the page mounts; +- load the returned iframe URL only after session creation succeeds; +- send heartbeat while the route is mounted; +- revoke the session on route leave, explicit close, component unmount, and + best-effort browser unload; +- render expired/revoked/error states without blank iframes. + +## Gateway Session Lifecycle + +Sessions are route-bound leases: + +```text +created -> active -> revoked +created -> expired +active -> expired +created/active -> failed +``` + +Creation flow: + +1. Admin route mounts and calls `POST /qqbot/napcat/webui/session`. +2. API authorizes the current Admin user and resolves the selected account's + active NapCat target. +3. API calls Gateway internal `POST /internal/sessions` with safe account + metadata plus encrypted or service-internal target credentials. +4. Gateway creates an active-session record with TTL and a one-time bootstrap + ticket. +5. API returns the public iframe bootstrap URL. +6. The iframe loads the bootstrap URL. Gateway redeems the one-time ticket, + sets an HttpOnly path-scoped gateway cookie, and redirects to the session + WebUI root. +7. Gateway marks the session active after the first successful proxied WebUI + response. + +Heartbeat and cleanup: + +- Admin sends heartbeat every 15 to 30 seconds while the route is mounted. +- Gateway updates `lastSeenAt`. +- If no heartbeat arrives for 60 to 90 seconds, Gateway expires the session. +- Route leave, explicit close, and component unmount call revoke. +- `beforeunload` uses a best-effort sendBeacon revoke, but correctness relies on + TTL timeout because browser unload is not guaranteed. + +Concurrent access policy: + +- One active WebUI session is allowed per Admin user plus account. +- Creating a new session for the same Admin user and account revokes the older + session. +- Other Admin users require their own authorization and separate sessions. + +Expired or revoked sessions return HTTP 410 for iframe/proxy requests and a +small gateway error page that tells the Admin page to reopen the route session. + +## Public and Internal Contracts + +### API Admin contracts + +```text +POST /qqbot/napcat/webui/session +Body: { accountId: string } +Result: { + sessionId: string; + iframeUrl: string; + expiresAt: number; + account: { id: string; selfId: string; nickname?: string }; + container: { id: string; name: string; webuiStatus: string }; +} + +POST /qqbot/napcat/webui/session/:sessionId/heartbeat +Result: { sessionId: string; expiresAt: number; status: "active" } + +POST /qqbot/napcat/webui/session/:sessionId/revoke +Result: true +``` + +The Admin API responses contain only display-safe metadata. + +### Gateway public contracts + +```text +GET /napcat-webui/session/:sessionId/bootstrap?ticket=... +GET /napcat-webui/session/:sessionId/webui/* +POST /napcat-webui/session/:sessionId/webui/* +WS /napcat-webui/session/:sessionId/webui/* +``` + +The bootstrap ticket is short-lived and one-time. After redemption, the browser +uses only a path-scoped gateway session cookie. The iframe URL should not keep a +long-lived bearer token in the address bar. + +### Gateway internal contracts + +```text +POST /internal/sessions +POST /internal/sessions/:sessionId/heartbeat +POST /internal/sessions/:sessionId/revoke +GET /internal/health +``` + +Internal endpoints require a service-to-service secret or mTLS-equivalent +cluster boundary. They are not exposed through the public Caddy/Admin route. + +## Proxy Behavior + +The Gateway must support: + +- all HTTP methods used by the NapCat WebUI; +- JSON and binary payloads; +- static assets; +- redirects with `Location` rewritten back under the gateway session prefix; +- cookies rewritten to a session-scoped path and never exposed across accounts; +- WebSocket upgrade traffic if NapCat WebUI uses it; +- `Cache-Control: no-store` for sensitive proxied responses; +- removal or replacement of upstream frame headers that would block Admin's + iframe, while preserving safe CSP around the gateway route. + +If NapCat WebUI emits absolute `/api` or `/webui` paths, Gateway rewrites the +HTML and response headers so browser requests remain under: + +```text +/napcat-webui/session/:sessionId/webui/ +``` + +The proxy must reject path traversal, encoded upstream URL injection, and any +request that escapes the bound session prefix. + +## Security Model + +Full NapCat WebUI operations are allowed. Authorization is therefore explicit: +an Admin user who can open the route for an account can fully operate that +account's NapCat WebUI. + +Security controls: + +- Admin JWT is checked by API before session creation. +- Gateway session is opaque, short-lived, and bound to Admin user id, account id, + selfId, container id, client metadata, and route lease. +- NapCat WebUI token and credential never leave server-side services. +- Browser-visible URLs contain at most a one-time bootstrap ticket. +- Gateway cookies are HttpOnly, Secure, SameSite, and path-scoped to the + session. +- Public gateway requests cannot select upstream hosts or ports. +- Internal API-to-Gateway calls are authenticated. +- Audit records include user id, account id, selfId, container id, session id, + event type, client IP, user agent, timestamps, and safe error summaries. + +## Data Model + +Active runtime session state lives in Redis so Gateway replicas can share it: + +```text +napcat:webui:session:{sessionId} + status + adminUserId + accountId + selfId + containerId + containerName + upstreamBaseUrl + encryptedWebuiToken or encrypted credential material + createdAt + activeAt + lastSeenAt + expiresAt + revokedAt +``` + +Gateway audit history is persisted in MySQL, either through the API service or a +Gateway-owned table managed from the API repository: + +```text +qqbot_napcat_webui_gateway_audit + id + session_id + admin_user_id + account_id + self_id + container_id + event_type + client_ip + user_agent + detail_json + create_time +``` + +No WebUI token, NapCat credential, QQ password, captcha ticket, QR payload, or +raw proxied response is stored in audit rows. + +## Deployment + +The first implementation keeps the service source in the API repository but +builds and deploys it as a separate service: + +- independent app entry for `kt-napcat-webui-gateway`; +- independent Dockerfile or Docker target; +- Jenkins build and push for the Gateway image; +- K8s Deployment and Service; +- Caddy/Admin domain route such as: + +```text +https://admin.kwitsukasa.top/napcat-webui/* +``` + +The route points to the Gateway service, while the existing API remains under +`/api/*`. Local development should provide an equivalent Vite or Caddy proxy so +Admin can test the iframe route without exposing NapCat container ports. + +## Error Handling + +- Missing account or no active NapCat binding: Admin route shows a clear empty + state with a link back to the account list. +- Container offline or WebUI unavailable: session creation fails before iframe + load and shows the runtime evidence returned by API. +- Gateway cannot authenticate to NapCat WebUI: session fails with a sanitized + error and an audit event. +- Session expired or revoked: iframe receives HTTP 410 and Admin page offers a + one-click session reopen. +- Gateway target proxy timeout: render an error overlay and keep route controls + available. +- API and Gateway clocks must not be used as the only correctness boundary; + Redis TTL and heartbeat timestamps drive cleanup. + +## Testing Strategy + +Backend/API: + +- unit test account authorization and session creation DTOs; +- unit test no secret fields are returned to Admin; +- integration test API creates and revokes Gateway sessions through a mocked + internal Gateway client; +- audit tests for create, active, heartbeat, revoke, expire, and proxy failure. + +Gateway: + +- unit test bootstrap ticket redemption and one-time use; +- unit test session TTL, heartbeat, revoke, and concurrent session revocation; +- proxy tests for path rewriting, cookie scoping, redirect rewriting, and + forbidden upstream URL injection; +- WebSocket upgrade smoke if NapCat WebUI requires it. + +Admin: + +- route test for account-row WebUI action navigation; +- lifecycle test for mount create, heartbeat, unmount revoke; +- dark-theme and layout tests for the remote console page; +- Playwright smoke that opens the route, waits for iframe shell load, leaves the + route, and verifies revoke was called. + +Online: + +- deploy API and Gateway through Jenkins/K8s; +- verify Gateway health and route availability under the Admin domain; +- open the WebUI page for account `1914728559`; +- confirm the iframe loads the original NapCat WebUI without exposing tokens or + container ports in the browser URL; +- perform one safe WebUI operation such as reading login status or opening a + settings page; +- leave the route and verify the session is revoked or expires by heartbeat + timeout. + +## Completion Criteria + +- Admin has a working second-level NapCat WebUI route per QQBot account. +- The Gateway is independently deployed and observable. +- Full NapCat WebUI operation works through the iframe route. +- Browser-visible data contains no NapCat token, credential, container IP, host + port, NAS SSH route, or QQ password. +- Session lifecycle follows the Admin route lifecycle and cleans up on route + leave or heartbeat timeout. +- API, Gateway, Admin tests and online smoke evidence are recorded before the + feature is considered complete. diff --git a/docs/superpowers/specs/2026-06-24-qqbot-napcat-webui-gateway-design.zh-CN.md b/docs/superpowers/specs/2026-06-24-qqbot-napcat-webui-gateway-design.zh-CN.md new file mode 100644 index 0000000..b00a007 --- /dev/null +++ b/docs/superpowers/specs/2026-06-24-qqbot-napcat-webui-gateway-design.zh-CN.md @@ -0,0 +1,321 @@ +# QQBot NapCat WebUI Gateway 设计 + +## 背景 + +QQBot 当前已经由 API 管理 NapCat 容器、WebUI token、登录 session、运行态证据和账号拆分状态。Admin 可以使用定制过的登录弹窗完成扫码、验证码和新设备流程,但还不能从某个 QQBot 账号定点打开原版 NapCat WebUI。 + +本次目标是在 Admin 中打开指定账号对应的原版 NapCat WebUI,并允许完整操作,同时不把 NapCat 容器端口、WebUI token、Credential 或 NAS 拓扑暴露给浏览器。 + +## 目标 + +- 在 QQBot 账号列表的账号连接列或操作区增加 WebUI 入口。 +- 点击入口后进入二级路由页面,而不是抽屉。 +- 使用独立 `kt-napcat-webui-gateway` 微服务承载 NapCat WebUI 代理。 +- Gateway session 跟随二级页面生命周期创建、心跳和销毁。 +- iframe 内允许 NapCat WebUI 完整操作。 +- 浏览器侧不暴露 NapCat token、Credential、容器 IP、宿主端口或 NAS 内网信息。 +- 记录 session 创建、激活、心跳、撤销、过期和代理失败审计证据。 + +## 非目标 + +- 不把 NapCat WebUI 重写成 Admin 原生组件。 +- 第一版不做只读限制。 +- 不直接公开 NapCat 容器端口。 +- 不允许浏览器通过 Gateway 任意代理外部 URL。 +- 不把该 Gateway 合并到现有 NapCat 登录 SSE 状态机。 + +## 页面形态 + +Admin 新增二级路由: + +```text +/qqbot/account/:accountId/napcat-webui +``` + +QQBot 账号列表的 WebUI 按钮跳转到该路由。页面是远程控制台,而不是抽屉。页面结构: + +- 顶部是 Admin 自己的轻量控制栏:返回账号列表、账号信息、容器状态、session 状态、刷新 session、关闭 session。 +- 主体是全高 iframe,加载 Gateway 返回的 NapCat WebUI URL。 +- 未授权、无容器、WebUI 离线、session 过期、Gateway 不可用时显示明确错误态。 +- 页面高度遵循 Vben Layout 内容区,不额外制造 body 滚动。 + +刷新页面会创建新 session;旧 session 通过主动 revoke 或 heartbeat 超时清理。重复从账号列表打开同一账号时创建新 session,不复用旧 session。 + +## 总体架构 + +```text +Admin 账号列表 + -> Admin 二级路由 /qqbot/account/:accountId/napcat-webui + -> API 创建 route-bound Gateway session + -> Gateway 保存活跃 session 与 NapCat 目标信息 + -> Admin iframe 打开 Gateway bootstrap URL + -> Gateway 反代 NapCat WebUI HTML、静态资源、API 和 WebSocket + -> NapCat WebUI 容器 +``` + +API 是 Admin 鉴权和 QQBot 账号绑定的权威。Gateway 是 WebUI 代理 session 和 NapCat WebUI Credential 交换的权威。 + +## 服务边界 + +### API 服务 + +API 负责 Admin 侧鉴权和账号解析: + +- 校验 Admin JWT 与 QQBot 账号权限。 +- 根据账号解析 selfId、主 NapCat 绑定、容器 ID/名称、WebUI base URL 和 WebUI token。 +- 在创建 Gateway session 前拒绝无容器、容器离线、WebUI 不可用或无权限的请求。 +- 使用服务间密钥调用 Gateway 内部接口。 +- 只把 `sessionId`、`iframeUrl` 和安全展示字段返回给 Admin。 +- 代理 Admin 发来的 heartbeat/revoke 到 Gateway 内部接口。 +- 写入或转发审计事件。 + +API 不返回 `webuiToken`、NapCat Credential、容器 IP、宿主端口、Docker 网络或 NAS SSH 信息。 + +### Gateway 服务 + +`kt-napcat-webui-gateway` 独立进程、独立镜像、独立 K8s Deployment。它负责: + +- 活跃 session 存储与 TTL 管理。 +- 一次性 iframe bootstrap ticket。 +- 按 NapCat 现有契约使用 `sha256(token + ".napcat")` 登录 WebUI 并换取 Credential。 +- 反代 HTTP 方法、静态资源、WebUI API、重定向、Cookie 和 WebSocket upgrade。 +- session heartbeat、revoke、expire 和清理。 +- Gateway 侧审计和结构化日志。 + +Gateway 只代理 session 内已绑定的目标容器,不接受浏览器传入的 upstream URL。 + +### Admin 前端 + +Admin 负责路由页面和生命周期: + +- 账号列表 WebUI 按钮跳转到二级路由。 +- 页面 mounted 时创建 session。 +- session 创建成功后再加载 iframe。 +- 页面 mounted 期间持续 heartbeat。 +- 路由离开、显式关闭、组件 unmount、浏览器卸载时尽量 revoke。 +- session 过期或失败时展示可读错误态,避免 iframe 白屏。 + +## Gateway Session 生命周期 + +session 是页面生命周期租约: + +```text +created -> active -> revoked +created -> expired +active -> expired +created/active -> failed +``` + +创建流程: + +1. Admin 二级页面 mounted,调用 `POST /qqbot/napcat/webui/session`。 +2. API 校验当前 Admin 用户并解析目标账号的 NapCat 容器。 +3. API 调用 Gateway 内部 `POST /internal/sessions`,传入安全账号元数据和服务端目标凭据。 +4. Gateway 创建带 TTL 的 session,并生成一次性 bootstrap ticket。 +5. API 返回公开 iframe bootstrap URL。 +6. iframe 打开 bootstrap URL,Gateway 兑换一次性 ticket,设置 HttpOnly、路径隔离的 gateway cookie,并跳转到 session WebUI 根路径。 +7. Gateway 在第一次成功代理 WebUI 响应后将 session 标记为 active。 + +心跳和清理: + +- Admin 页面 mounted 时每 15 到 30 秒发送 heartbeat。 +- Gateway 更新 `lastSeenAt`。 +- 60 到 90 秒无 heartbeat 时 Gateway 自动 expire。 +- 路由离开、显式关闭、组件 unmount 时主动 revoke。 +- `beforeunload` 使用 sendBeacon 尽量 revoke;正确性仍依赖 TTL,因为浏览器卸载不保证执行。 + +并发策略: + +- 同一个 Admin 用户对同一个 QQBot 账号只允许一个活跃 WebUI session。 +- 创建新 session 时撤销旧 session。 +- 不同 Admin 用户需要各自鉴权并创建独立 session。 + +revoked 或 expired 后,Gateway 对 iframe/proxy 请求返回 410,并展示“会话已关闭,请重新打开”的轻量错误页。 + +## 接口契约 + +### API Admin 接口 + +```text +POST /qqbot/napcat/webui/session +Body: { accountId: string } +Result: { + sessionId: string; + iframeUrl: string; + expiresAt: number; + account: { id: string; selfId: string; nickname?: string }; + container: { id: string; name: string; webuiStatus: string }; +} + +POST /qqbot/napcat/webui/session/:sessionId/heartbeat +Result: { sessionId: string; expiresAt: number; status: "active" } + +POST /qqbot/napcat/webui/session/:sessionId/revoke +Result: true +``` + +这些接口只返回展示安全字段。 + +### Gateway 公开接口 + +```text +GET /napcat-webui/session/:sessionId/bootstrap?ticket=... +GET /napcat-webui/session/:sessionId/webui/* +POST /napcat-webui/session/:sessionId/webui/* +WS /napcat-webui/session/:sessionId/webui/* +``` + +bootstrap ticket 短期有效且只能使用一次。ticket 兑换后,浏览器只依赖路径隔离的 gateway cookie,不在地址栏长期保留 bearer token。 + +### Gateway 内部接口 + +```text +POST /internal/sessions +POST /internal/sessions/:sessionId/heartbeat +POST /internal/sessions/:sessionId/revoke +GET /internal/health +``` + +内部接口需要服务间密钥或等价的集群内边界,不通过公开 Admin 域暴露。 + +## 代理行为 + +Gateway 必须支持: + +- NapCat WebUI 使用的全部 HTTP 方法。 +- JSON 和二进制 payload。 +- 静态资源。 +- `Location` 重写到 Gateway session 前缀下。 +- Cookie path 改写到当前 session,避免跨账号串用。 +- WebSocket upgrade。 +- 敏感响应使用 `Cache-Control: no-store`。 +- 处理或替换会阻止 Admin iframe 的 upstream frame header,同时保留 Gateway 自身安全 CSP。 + +如果 NapCat WebUI 输出绝对 `/api` 或 `/webui` 路径,Gateway 需要重写 HTML 和响应头,让浏览器请求始终留在: + +```text +/napcat-webui/session/:sessionId/webui/ +``` + +Gateway 必须拒绝路径穿越、编码 URL 注入和任何逃逸当前 session 前缀的请求。 + +## 安全模型 + +iframe 内允许 NapCat WebUI 完整操作。因此权限语义必须直接明确:能进入该账号 NapCat WebUI 二级页面的 Admin 用户,就拥有该账号 NapCat WebUI 的完整操作权。 + +安全控制: + +- API 创建 session 前校验 Admin JWT。 +- Gateway session 绑定 Admin 用户 ID、账号 ID、selfId、容器 ID、客户端信息和页面租约。 +- NapCat WebUI token 与 Credential 只存在服务端。 +- 浏览器可见 URL 最多包含一次性 bootstrap ticket。 +- Gateway cookie 使用 HttpOnly、Secure、SameSite,并按 session path 隔离。 +- 公开 Gateway 请求不能选择 upstream host 或 port。 +- API 到 Gateway 的内部调用必须鉴权。 +- 审计记录用户、账号、selfId、容器、session、事件类型、IP、UA、时间和安全错误摘要。 + +## 数据模型 + +活跃 session 使用 Redis 保存,便于 Gateway 多副本共享: + +```text +napcat:webui:session:{sessionId} + status + adminUserId + accountId + selfId + containerId + containerName + upstreamBaseUrl + encryptedWebuiToken or encrypted credential material + createdAt + activeAt + lastSeenAt + expiresAt + revokedAt +``` + +审计历史持久化到 MySQL,可由 API 写入,也可以由 Gateway 通过 API 仓库管理的表写入: + +```text +qqbot_napcat_webui_gateway_audit + id + session_id + admin_user_id + account_id + self_id + container_id + event_type + client_ip + user_agent + detail_json + create_time +``` + +审计表不保存 WebUI token、NapCat Credential、QQ 密码、验证码 ticket、二维码内容或原始代理响应。 + +## 部署 + +第一版源码放在 API 仓库,但作为独立服务构建和部署: + +- 独立 `kt-napcat-webui-gateway` app entry。 +- 独立 Dockerfile 或 Docker build target。 +- Jenkins 构建并推送 Gateway 镜像。 +- K8s 独立 Deployment 和 Service。 +- Caddy/Admin 域增加路由,例如: + +```text +https://admin.kwitsukasa.top/napcat-webui/* +``` + +该路由指向 Gateway 服务,现有 API 继续位于 `/api/*`。本地开发需要等价 Vite 或 Caddy proxy,避免为了 iframe 调试而暴露 NapCat 容器端口。 + +## 错误处理 + +- 账号不存在或无有效 NapCat 绑定:页面显示空状态并提供返回账号列表入口。 +- 容器离线或 WebUI 不可用:session 创建前失败,并展示 API 返回的运行态证据。 +- Gateway 无法登录 NapCat WebUI:session failed,展示脱敏错误并写审计。 +- session 过期或撤销:iframe 收到 410,Admin 页面提供重新打开按钮。 +- Gateway 代理目标超时:显示错误覆盖层,保留页面顶部控制栏。 +- API/Gateway 不只依赖本机时钟判断正确性,清理以 Redis TTL 和 heartbeat 时间戳为准。 + +## 验证策略 + +API: + +- 单测账号权限和 session 创建 DTO。 +- 单测 Admin 响应不包含敏感字段。 +- 集成测试 API 通过 mock Gateway client 创建、心跳和撤销 session。 +- 审计测试覆盖 create、active、heartbeat、revoke、expire、proxy failure。 + +Gateway: + +- 单测 bootstrap ticket 兑换和一次性使用。 +- 单测 session TTL、heartbeat、revoke 和同用户同账号并发 session 撤销。 +- 代理测试覆盖路径重写、Cookie 隔离、重定向重写、禁止 upstream URL 注入。 +- 如果 NapCat WebUI 使用 WebSocket,则增加 WebSocket upgrade smoke。 + +Admin: + +- 账号行 WebUI 按钮路由跳转测试。 +- 页面生命周期测试:mounted 创建 session、heartbeat、unmount revoke。 +- 暗色主题和 Layout 高度测试。 +- Playwright smoke:打开二级路由、等待 iframe shell 加载、离开路由、确认 revoke 被调用。 + +线上: + +- Jenkins/K8s 部署 API 和 Gateway。 +- 验证 Gateway health 与 Admin 域 `/napcat-webui/*` 路由。 +- 打开账号 `1914728559` 的 WebUI 页面。 +- 确认 iframe 加载原版 NapCat WebUI,浏览器 URL 不暴露 token、Credential、容器端口。 +- 执行一个安全 WebUI 操作,例如读取登录状态或打开设置页。 +- 离开路由后确认 session revoke 或 heartbeat 超时回收。 + +## 完成标准 + +- Admin 支持按 QQBot 账号打开二级 NapCat WebUI 页面。 +- Gateway 独立部署且可观测。 +- iframe 中完整 NapCat WebUI 操作可用。 +- 浏览器侧不暴露 NapCat token、Credential、容器 IP、宿主端口、NAS SSH 路由或 QQ 密码。 +- session 生命周期跟随 Admin 二级页面,路由离开或 heartbeat 超时后会清理。 +- API、Gateway、Admin 测试和线上 smoke 证据齐全后才算完成。