diff --git a/openspec/changes/platform-tenant-switch/.openspec.yaml b/openspec/changes/platform-tenant-switch/.openspec.yaml new file mode 100644 index 0000000..a369219 --- /dev/null +++ b/openspec/changes/platform-tenant-switch/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-11 diff --git a/openspec/changes/platform-tenant-switch/design.md b/openspec/changes/platform-tenant-switch/design.md new file mode 100644 index 0000000..d6f7690 --- /dev/null +++ b/openspec/changes/platform-tenant-switch/design.md @@ -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 + +无。 diff --git a/openspec/changes/platform-tenant-switch/proposal.md b/openspec/changes/platform-tenant-switch/proposal.md new file mode 100644 index 0000000..35daa17 --- /dev/null +++ b/openspec/changes/platform-tenant-switch/proposal.md @@ -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、切换后重置菜单/字典/权限缓存。 +- 无数据变更。 diff --git a/openspec/changes/platform-tenant-switch/specs/backend/auth/spec.md b/openspec/changes/platform-tenant-switch/specs/backend/auth/spec.md new file mode 100644 index 0000000..71f2a3a --- /dev/null +++ b/openspec/changes/platform-tenant-switch/specs/backend/auth/spec.md @@ -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** 返回目标租户的数据 diff --git a/openspec/changes/platform-tenant-switch/specs/frontend/AD/layout/spec.md b/openspec/changes/platform-tenant-switch/specs/frontend/AD/layout/spec.md new file mode 100644 index 0000000..d8a6f5b --- /dev/null +++ b/openspec/changes/platform-tenant-switch/specs/frontend/AD/layout/spec.md @@ -0,0 +1,8 @@ +## ADDED Requirements + +### Requirement: 顶栏租户切换 +前端 SHALL 在顶栏为平台超管提供「切换租户」入口:列出全部租户,切换后替换 token、重置菜单/字典/权限等缓存并刷新。 + +#### Scenario: 切换租户 +- **WHEN** 平台超管选择某租户并切换 +- **THEN** 前端更新 token、重置缓存,按目标租户渲染菜单与数据 diff --git a/openspec/changes/platform-tenant-switch/specs/shared/api-contract/spec.md b/openspec/changes/platform-tenant-switch/specs/shared/api-contract/spec.md new file mode 100644 index 0000000..12d3802 --- /dev/null +++ b/openspec/changes/platform-tenant-switch/specs/shared/api-contract/spec.md @@ -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,后续请求按目标租户上下文执行 diff --git a/openspec/changes/platform-tenant-switch/tasks.md b/openspec/changes/platform-tenant-switch/tasks.md new file mode 100644 index 0000000..90eef61 --- /dev/null +++ b/openspec/changes/platform-tenant-switch/tasks.md @@ -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 diff --git a/sources/sysmgr-admin b/sources/sysmgr-admin index 348221d..da7a5b7 160000 --- a/sources/sysmgr-admin +++ b/sources/sysmgr-admin @@ -1 +1 @@ -Subproject commit 348221d198f017ff4fe3de39d53e6a62b9324d5f +Subproject commit da7a5b703e7c0413c6e2baf96c7e49e5db5e5871 diff --git a/sources/sysmgr-nest b/sources/sysmgr-nest index 477f352..6618137 160000 --- a/sources/sysmgr-nest +++ b/sources/sysmgr-nest @@ -1 +1 @@ -Subproject commit 477f35293e5e87ec904bcc7bceb8d1e37d19cffe +Subproject commit 6618137da557843b0c30ebaebf06f8180a303392