feat(auth): 平台超管租户切换(子模块 + openspec change)
Some checks are pending
CI / backend (push) Waiting to run
CI / frontend (push) Waiting to run

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
liuyun 2026-10-12 00:02:52 +08:00
parent eaee5e1692
commit d793d34cfc
9 changed files with 128 additions and 2 deletions

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-11

View File

@ -0,0 +1,42 @@
## Context
JWT 载荷为 `{ sub, tenantCode, username, ver, sid }`;`RequestContext` 由 token 派生,`resolveAuth`/`buildContext`/`getMenus` 在**当前租户**内解析角色与 `isSuper`;`checkUserActive` 在**当前租户**内查用户。多租户隔离使平台超管(`touyun`)看不到 `demo` 租户数据。动机见 proposal.md - Why。
## Goals / Non-Goals
**Goals:**
- 平台超管切入任意租户,数据按目标租户过滤、权限超管绕过、审计归属保留真实超管。
**Non-Goals:**
- 不做普通用户/业务租户管理员的跨租户切换。
- 不做「切回上一个租户」的历史记录(重新选择即可)。
## Decisions
### D1 — 身份保留 + `super` 标志
JWT 增加 `super?: boolean`(仅切换接口签发 true)。`sub` 始终是平台超管真实 userId,`tenantCode` 为切入租户。切回 = 再次切换(切到 `touyun`)。
- **为什么不用「冒充目标租户管理员」**:会丢失超管真实身份,切回需重新登录,审计归属也错。
### D2 — `POST /auth/switch-tenant`
仅登录态;校验 `isPlatformSuperAdmin(userId)`(查 `touyun` 的 `user_role` 是否含 `super_admin`)+ 目标租户存在且 enabled;签发 `super:true` 新 token(沿用超管 `ver`,新 `sid`);记审计 `auth/switch-tenant`。
### D3 — `isSuper` 解析改为「平台租户为准」
`isPlatformSuperAdmin(userId)` = 平台租户 `user_role` 含 `super_admin`。`resolveAuth`/`buildContext`/`getMenus` 的 `isSuper = 当前租户含 super_admin || isPlatformSuperAdmin(userId)`。`checkUserActive` 对 `super` token 在**平台租户**查用户(因超管 userId 在 `touyun` 而非目标租户)。
### D4 — 前端顶栏切换
`permissionStore.super` 为真时显示「切换租户」下拉(列 `GET /tenants`);切换后 `auth` store 换 token、`reset` 菜单/字典/权限/页签,刷新进入目标租户上下文。
## Risks / Trade-offs
- **[super 标志被篡改]** → JWT 签名防篡改,且仅 switch 接口签发 `super:true`。
- **[切换后超管自身待改密]** → super token 仍走 `checkUserActive`,超管待改密时需先改密(可接受)。
## Migration Plan
无数据迁移;纯后端 + 前端。回滚即撤销。
## Open Questions
无。

View File

@ -0,0 +1,24 @@
## Why
平台超级管理员(`super_admin`,位于 `touyun` 租户)登录后只能看到自己所在租户的业务数据,无法切入其他租户(如 `demo`)管理其用户/角色——当前多租户隔离把业务数据锁死在登录租户内,但平台超管理应有「切入任意租户」的能力。
## What Changes
- JWT 增加 `super` 标志(仅切换接口签发);`sub` 始终是平台超管真实 userId,`tenantCode` 随切换变化。
- 新增 `POST /auth/switch-tenant`:平台超管切入目标租户,签发新 token。
- `isSuper` 判定改为「当前租户含 super_admin 或 平台租户(touyun)含 super_admin」,切换后仍按超管绕过权限、数据按目标租户过滤。
- 前端顶栏为超管提供「切换租户」入口。
## Capabilities
### Modified Capabilities
- `backend/auth`: 平台超管跨租户切换(super 标志 + 切换接口 + isSuper 解析)。
- `shared/api-contract`: 新增 `POST /auth/switch-tenant`。
- `frontend/AD/layout`: 顶栏租户切换入口。
## Impact
- **后端**:`auth`(JWT super 标志、switch-tenant 接口、`checkUserActive`/`resolveAuth`/`buildContext`/`getMenus` 的 isSuper 解析)。
- **前端**:顶栏切换下拉、auth store 更新 token、切换后重置菜单/字典/权限缓存。
- 无数据变更。

View File

@ -0,0 +1,16 @@
## ADDED Requirements
### Requirement: 平台超管租户切换
系统 SHALL 支持平台超管(`super_admin`)切入任意租户:`POST /auth/switch-tenant` 校验调用者为平台超管后签发 `super=true` 的新 token,其 `tenantCode` 为目标租户、`sub` 为超管真实 userId;切换后数据按目标租户过滤、权限按超管绕过。
#### Scenario: 切换租户
- **WHEN** 平台超管请求切入目标租户
- **THEN** 返回新 token(tenantCode=目标租户、super=true)
#### Scenario: 非超管切换被拒
- **WHEN** 非平台超管请求切换租户
- **THEN** 系统返回 403
#### Scenario: 切换后数据隔离
- **WHEN** 切换后的超管查询业务数据
- **THEN** 返回目标租户的数据

View File

@ -0,0 +1,8 @@
## ADDED Requirements
### Requirement: 顶栏租户切换
前端 SHALL 在顶栏为平台超管提供「切换租户」入口:列出全部租户,切换后替换 token、重置菜单/字典/权限等缓存并刷新。
#### Scenario: 切换租户
- **WHEN** 平台超管选择某租户并切换
- **THEN** 前端更新 token、重置缓存,按目标租户渲染菜单与数据

View File

@ -0,0 +1,8 @@
## ADDED Requirements
### Requirement: 租户切换接口
系统 SHALL 提供 `POST /auth/switch-tenant`(body 含 tenantCode,仅登录态),由平台超管调用,返回切换后的新 access/refresh token 与目标租户信息。
#### Scenario: 切换租户
- **WHEN** 平台超管请求 POST /auth/switch-tenant
- **THEN** 返回新 token,后续请求按目标租户上下文执行

View File

@ -0,0 +1,26 @@
## 1. 后端 JWT super 标志
- [x] 1.1 `issue()` 增加可选 `super` 参数写入 access token 载荷
- [x] 1.2 `checkUserActive` 增加 super 处理:super token 在平台租户查用户
## 2. switch-tenant 接口
- [x] 2.1 `auth/dto.ts` 加 `SwitchTenantDto`;`auth.service` 加 `switchTenant`(校验平台超管 + 目标租户存在,签发 super token + 审计)
- [x] 2.2 `auth.controller` 加 `POST /auth/switch-tenant`
- [x] 2.3 `jwt-auth.guard` 把 `super` 传给 `checkUserActive` 并写入 `request.user`
## 3. isSuper 解析
- [x] 3.1 新增 `isPlatformSuperAdmin(userId)` 辅助(查平台租户 user_role)
- [x] 3.2 `resolveAuth`/`buildContext`/`getMenus` 的 isSuper 判定加平台租户检查
## 4. 前端切换入口
- [x] 4.1 `api/auth.ts` 加 `switchTenant`;auth store 加切换动作(换 token)
- [x] 4.2 `ty-header` 顶栏「切换租户」下拉(仅 super 显示),切换后重置菜单/字典/权限/页签并刷新
## 5. 验证
- [x] 5.1 后端 build + 单测 + e2e 通过
- [x] 5.2 前端 type-check 通过
- [x] 5.3 冒烟:超管切到 demo → 列出 demo 用户 → 切回 touyun

@ -1 +1 @@
Subproject commit 348221d198f017ff4fe3de39d53e6a62b9324d5f Subproject commit da7a5b703e7c0413c6e2baf96c7e49e5db5e5871

@ -1 +1 @@
Subproject commit 477f35293e5e87ec904bcc7bceb8d1e37d19cffe Subproject commit 6618137da557843b0c30ebaebf06f8180a303392