Files
sub2api-add/plugins/subscription-admin/README.md
T
Qiufeng d3ff9be315
Business Plugins CI / check (plugin-admin) (push) Successful in 1m37s
Business Plugins CI / check (subscription-admin) (push) Successful in 1m31s
release: harden plugin deployment and recovery
2026-08-30 13:14:22 +08:00

116 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sub2API Subscription Admin Business Plugin V1
这是一个可独立运行后端的管理员只读订阅业务模块,不是现有 `.s2plugin` transport 插件,也不是插件管理控制面。它不导入 Sub2API `internal` 包,不连接 Core 数据库,也不修改 Core Go/Vue、迁移、路由或 `.s2plugin` ABI。浏览器前端由 `plugin-admin` 统一 TDesign 控制面承载,订阅模块不提供第二个登录页或 Cookie。
生产/集成环境由通用 `plugins/plugin-admin` 控制面安装、启用和升级本插件;本插件不会预装,也不会成为控制面首页。只有健康检查通过并由管理员执行菜单预览/应用后,Core 管理员菜单才会出现“订阅管理”入口。直接运行本目录仅用于本地开发和契约测试。
## 本地启动
```sh
CORE_BASE_URL=http://127.0.0.1:8080 \
PLUGIN_HOST=127.0.0.1 \
PLUGIN_PORT=8091 \
go run .
```
本地后端可打开 `http://127.0.0.1:8091/healthz` 进行服务契约调试;生产浏览器入口应从 Plugin Admin 的订阅模块路由进入。生产环境应通过 HTTPS 反向代理,并设置 `PLUGIN_COOKIE_SECURE=true`。默认情况下本服务只暴露健康检查、就绪检查和交接页,不暴露第二套登录、Cookie 或 Core 数据 API。`PLUGIN_STANDALONE_AUTH=true` 仅在 `PLUGIN_ENV=development` 且监听地址为 loopback 时生效;该开发兼容模式会创建模块 Cookie,生产必须保持关闭。
## V1 范围
- 由 Plugin Admin 统一完成 Core 管理员账号登录和 Core 2FA;普通账号统一拒绝。
- 订阅模块复用控制面的 HttpOnly、SameSite 会话和写请求 CSRF 校验,不创建模块级登录会话。
- Core token 只保存在插件服务端内存会话中,不进入浏览器、URL、HTML、LocalStorage、响应或日志。
- 只读套餐、订阅列表、订阅详情和插件操作记录。
- Core access token 失效时最多刷新一次;刷新失败会销毁插件会话。
- 页面刷新会从插件会话恢复,并重新取得短期 CSRF token;服务端不会把 Core token 返回浏览器。
- 登录会读取 Core 公开验证码配置并透传 Turnstile、腾讯、阿里云或 GeeTest 的验证结果;验证码本身仍由 Core 校验。
- 余额购买、续费、撤销、退款和外部支付不在 V1,页面不渲染提交按钮。
## Core API allowlist
插件服务端在 `PLUGIN_STANDALONE_AUTH=true`、`PLUGIN_ENV=development` 且 loopback 的本地兼容模式下仅调用这些明确路径;生产数据访问由 Plugin Admin 同源 BFF 完成:
```text
POST /api/v1/auth/login
POST /api/v1/auth/login/2fa
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
GET /api/v1/auth/me
GET /api/v1/settings/public
GET /api/v1/admin/payment/plans
GET /api/v1/admin/subscriptions
GET /api/v1/admin/subscriptions/{id}
GET /api/v1/admin/users/{id}
GET /api/v1/admin/users/{id}/subscriptions
```
列表请求只接受 `page`、`page_size`、`limit`、`user_id`、`group_id`、`status`、`platform`、`sort_by`、`sort_order` 参数。插件不会代理任意 URL,也不会调用尚不存在的 `/api/v1/plugin-host/*`。
## Core 菜单与反向代理
控制面会依据清单中的菜单声明生成下面的 `custom_menu_items` 项;部署时无需手工写入订阅菜单:
```json
{
"id": "qiu.subscription-admin",
"label": "订阅管理",
"url": "https://CORE_ORIGIN/extensions/qiu.plugin-admin/admin/#/modules/subscription/overview",
"visibility": "admin",
"sort_order": 200
}
```
Core 自定义页面的 sandbox iframe 不会继承 Core `localStorage` 登录态,因此菜单应指向 Plugin Admin 的统一控制面入口;订阅模块本身不显示登录页、不创建 Cookie。不要把 JWT 放进 URL。
## 测试
```sh
go test ./... -count=1
node --check ui/app.js
```
`main_test.go` 覆盖 Core 路径 allowlist、查询参数过滤、管理员角色拒绝、插件 Cookie、Core token 不泄露、会话过期、请求 ID、登录限流和 2FA pending 一次性消费。浏览器验收脚本位于 `test/browser-check.mjs`,可使用本地 Mock Core 验证直连、子路径反代和三种视口。
## 生成可安装包
```sh
./package.sh
```
脚本生成 `dist/qiu.subscription-admin.s2plugin`,包内根文件名为
`manifest.json`,并包含清单声明哈希的 UI 文件。该模块采用外部服务模式;
归档只携带清单和 UI 资源,不会替部署者启动后端。安装后先独立启动
`subscription-admin`(或由 systemd 持续运行),再在 `plugin-admin` 的配置中填写
`service_url`(插件 loopback 地址),并为 Plugin Admin 设置
`PLUGIN_PUBLIC_URL`(例如 `https://CORE_ORIGIN/extensions/qiu.plugin-admin`)。
然后执行启用、健康检查和菜单应用。浏览器前端由 `plugin-admin` 统一挂载并共享
控制面会话,菜单固定跳转到 `#/modules/subscription/overview`,不会跳转到本服务
的登录页。生产环境必须把签名文件通过
`SIGNATURE_FILE=/path/to/signature.json ./package.sh` 放入包内,并将对应公钥
加入控制面受信发布者配置;未签名包仅限 development + loopback。
## 清单和发布
`business-plugin-manifest.v1.json` 是部署层清单,不由 Core 读取。生产发布应由独立 CI 签名并校验清单、版本、健康路径和兼容的 Core 版本;不要把发布私钥放入仓库或插件包。插件版本独立于 `backend/cmd/server/VERSION`。
发布构建必须显式启用签名门禁:
```sh
RELEASE_BUILD=true SIGNATURE_FILE=/secure/signature.json ./package.sh
```
脚本会把 `signature.json` 放入归档并生成同名 `.sha256` 校验文件;未签名包
仅用于 development + loopback。
## 已知限制
- 模块后端是常驻服务,不需要每次使用后重启。Core access token 过期时,后端会
按需 refresh 并继续当前请求;只有 refresh 失效、Core 撤销管理员、会话
空闲超过 30 分钟或达到 8 小时绝对上限时才需要重新登录。
- V1 控制面使用内存会话,控制面服务重启会要求重新登录一次;订阅模块不会
单独要求登录。多实例或跨重启免登录需将控制面会话存储替换为插件自有的
加密 Redis/共享会话服务。
- 现有 Core 自定义 iframe 没有 token handoff,V1 不提供无感 SSO;真正 SSO 需要单独的 V1.1 Core 交接接口。
- Core 当前套餐响应中的 `features` 可能是 JSON 字符串,UI 会兼容字符串和数组。
- Core 开启验证码时,管理员必须先完成对应提供商的挑战并将结果填入登录表单;插件不保存验证码票据。