68 lines
7.5 KiB
Markdown
68 lines
7.5 KiB
Markdown
# 外管后台
|
||
|
||
## 系统边界
|
||
|
||
外管后台是独立于主后台、Databi 和 Finance 的浏览器入口:
|
||
|
||
- 主后台 `/operations/external-admin-users` 继续使用主后台 JWT、RBAC 和全局 App 选择器,负责查找 App 用户、签发外管账号、配置权限、启停账号和重置密码。
|
||
- 外管入口 `/external-admin/` 使用独立账号、服务端 opaque session、CSRF token 和首次改密流程,不读取主后台 token 或 localStorage。
|
||
- 外管账号、会话、登录日志和操作日志分别保存在 `external_admin_accounts`、`external_admin_sessions`、`external_admin_login_logs`、`external_admin_operation_logs`。
|
||
- 登录只提交外管账号和密码,不提交或选择 App。账号名全局唯一,服务端通过账号行自动确定 `app_code` 并固化到会话;Fami 账号只能访问 Fami 数据,请求头缺省时由会话补齐,伪造其他 App 时直接拒绝。
|
||
|
||
## 账号签发和岗位边界
|
||
|
||
创建账号时必须先在当前 App 内用长 ID、短 ID 或靓号精确匹配用户,再提交服务端返回的稳定 `user_id`。昵称不参与绑定,避免重名用户拿到外管凭据。
|
||
|
||
- `platform-admin`:查看、创建、配置权限、启停和重置密码。创建账号必须同时具备 `external-admin-user:create` 和 `external-admin-user:permissions`,避免只有凭据创建权限的调用方通过省略权限字段签发默认全权限账号。
|
||
- `ops-admin`、`operations-specialist`:查看、启停。运营岗位不能通过创建或重置账号间接获得自身权限矩阵中没有的 VIP 发放等能力。
|
||
- 外管登录账号名跨 App 全局唯一;绑定 App 用户仍按 `(app_code, linked_app_user_id)` 唯一。创建服务先做全局账号名检查,数据库唯一索引负责收敛并发创建竞态。
|
||
- 创建、重置后的临时密码必须首次修改;停用或重置会撤销该账号的全部活跃会话。
|
||
|
||
## 权限配置
|
||
|
||
- `GET /api/v1/admin/external-admin-users/permission-catalog` 返回固定的 20 项业务权限目录;服务端只接受目录内 code,不接受通配符或主后台 RBAC code。
|
||
- `GET /api/v1/admin/external-admin-users/:id/permissions` 按当前 App 返回权限快照和 `revision`;`PUT` 必须提交 `permissions` 与 `expectedRevision`。版本不一致返回 409,防止两个管理员互相覆盖。
|
||
- 创建请求省略 `permissions` 时保留兼容行为,使用完整默认目录;显式 `[]` 表示零业务权限。无论是否省略字段,调用方都必须具备权限委派能力。
|
||
- 权限实际变化与撤销该账号全部活跃会话在同一事务提交,并递增 `permission_revision`;相同快照重复提交不递增版本、不重复撤销会话。
|
||
- 特权道具、用户称号和房间背景图在 owner 资源模型可可靠分类前按一个原子权限包校验,不能只选择其中一项。外管资源与 VIP 发放使用 `manager_center` 来源,由 Wallet 在写事务内再次检查经理可发放规则;主后台仍使用原有来源。
|
||
|
||
## 外管能力
|
||
|
||
外管只注册显式白名单路由,不提供主后台的通用代理能力:
|
||
|
||
- 用户:用户列表、资料编辑、封禁列表、封禁和解封。
|
||
- 组织:主播、公会、BD、BD Manager、Super Admin 列表和我的团队。
|
||
- 房间:房间列表和房间编辑。
|
||
- 发放:特权道具、靓号、用户称号、房间背景图和财富/VIP 等级。
|
||
- 运营:Banner 创建。
|
||
|
||
底层业务写入仍由各 owner service 或既有后台 Handler 负责;外管模块只做独立认证、App 约束、权限白名单和审计,不复制用户、房间或钱包业务事实。
|
||
|
||
## 登录安全
|
||
|
||
- Session cookie 为 HttpOnly,作用域仅 `/api/v1/external`;写请求同时校验 CSRF cookie、header 和服务端 hash。
|
||
- Session 绝对有效期默认 12 小时。每次请求都会确认账号和 App 仍为 active;App 停用后旧会话不能绕过 `/auth/me` 直接调用业务接口。
|
||
- 同一账号连续失败 5 次后锁定 15 分钟;账号、密码或账号所属 App 不可用均对外返回相同的 HTTP 401 / 业务码 40100,前端按当前语言渲染本地化文案。
|
||
- Redis 在账号查询、bcrypt 和登录日志之前,先按系统全局 300、单真实 IP 30、全局账号名 10 执行 60 秒固定窗口限流;命中唯一账号后,再按服务端解析出的 App 执行单 App 120 限流。两个阶段不会重复消耗系统/IP/账号计数,请求中的旧版 `appCode` 字段即使存在也会被忽略。
|
||
- 登录请求体上限为 8 KiB;限流 Redis 在 200 ms 内不可用时 fail-closed 返回 503,不降级为可绕过的单机计数。
|
||
- 认证接口统一返回 `Cache-Control: no-store`。
|
||
|
||
## 上线步骤
|
||
|
||
1. 通过 admin-server 迁移器依次应用 `migrations/098_external_admin_portal.sql`、`099_external_admin_credential_delegation.sql`、`100_external_admin_global_username.sql` 和 `101_external_admin_permission_assignment.sql`。发布前先执行 `EXPLAIN SELECT username, COUNT(*) FROM external_admin_accounts GROUP BY username HAVING COUNT(*) > 1` 评估访问计划,再执行同一查询确认结果为空。该检查和 100 的唯一索引构建只扫描小型凭据表,不访问用户、房间或钱包表;100、101 使用 `INPLACE + LOCK=NONE`,但仍建议避开凭据集中创建时段。101 只为小型凭据表增加定长版本列并写入一项平台权限,不扫描或更新业务数据。若 100 检测到重复,DDL 会 fail-closed,不会自动改名或删除账号;先由业务确认保留的登录名并处理冲突,再清理迁移器 dirty 记录并重跑。
|
||
2. 确认 Redis 已启用且 `/readyz` 为成功。生产配置缺少 Redis 时 admin-server 会拒绝启动,运行中限流 Redis 故障会阻断新登录但不破坏已建立会话。
|
||
3. 配置 `trusted_proxies` 为真实入口链路的精确 IP/CIDR。默认只信任 loopback;不要信任 `0.0.0.0/0`、`::/0` 或整段未知私网。
|
||
4. 构建并发布前端 `dist/external-admin/`,保留 Nginx 对 `/external-admin` 的 301 和所有 `/external-admin/*` 深链回退到独立 HTML。
|
||
5. 在 EdgeOne/WAF 对精确路径 `POST /api/v1/external/auth/login` 配置按真实客户端 IP 的 token-bucket/挑战规则,建议基线 30 次/分钟、burst 10;同时限制源站直连。WAF 负责 Redis 前的大包和连接洪峰,应用层 Redis 负责系统、App 和账号维度的正确性。
|
||
|
||
## 上线验收
|
||
|
||
- 登录请求不带 App 即可进入账号所属租户;即使旧客户端伪造其他 `appCode`,也必须登录到账号行所属 App。Fami 账号登录后请求 Lalu 的 `X-App-Code` 必须返回 403,省略 header 时仍只能看到 Fami 数据。
|
||
- 外管账号或 App 停用后,已有会话直接访问任一业务接口必须返回 401。
|
||
- 临时密码登录后,除会话、退出和修改密码外的业务接口必须返回 403;改密后才可进入授权页面。
|
||
- 用旧 `revision` 更新权限必须返回 409;成功减少权限后旧会话必须立即返回 401,原权限接口必须返回 403。重复提交相同快照时版本保持不变。
|
||
- 从同一真实 IP 连发超过阈值的登录请求必须返回 429 和 `Retry-After`;伪造 `X-Forwarded-For` 不能改变限流身份。
|
||
- Redis 临时不可用时新登录应在约 200 ms 后返回 503;恢复后不需要重启服务。
|
||
- `/external-admin/`、一个桌面深链和一个移动端深链均返回外管独立 HTML,不能回退到主后台入口。
|
||
- 停用、密码重置、封禁、房间编辑、资源/VIP 发放和 Banner 创建均应在外管操作日志中留下 App、外管账号、请求 ID、资源 ID 和结果。
|