From 802851a69fb6fd25088bdf9aecc62dfd9b0ab294 Mon Sep 17 00:00:00 2001 From: liuyun Date: Sat, 10 Oct 2026 23:20:02 +0800 Subject: [PATCH] chore(openspec): track close-phase-0 implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sysmgr-nest 收口:RBAC 权限落地 + 审计/登录日志 + seed 脚本。 openspec change close-phase-0 任务全部完成,待归档。 Co-Authored-By: Claude Opus 4.8 --- openspec/changes/close-phase-0/.openspec.yaml | 2 + openspec/changes/close-phase-0/design.md | 87 +++++++++++++++++++ openspec/changes/close-phase-0/proposal.md | 35 ++++++++ .../close-phase-0/specs/backend/audit/spec.md | 38 ++++++++ .../close-phase-0/specs/backend/auth/spec.md | 42 +++++++++ .../close-phase-0/specs/backend/login/spec.md | 23 +++++ .../close-phase-0/specs/backend/res/spec.md | 30 +++++++ openspec/changes/close-phase-0/tasks.md | 44 ++++++++++ sources/sysmgr-nest | 2 +- 9 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 openspec/changes/close-phase-0/.openspec.yaml create mode 100644 openspec/changes/close-phase-0/design.md create mode 100644 openspec/changes/close-phase-0/proposal.md create mode 100644 openspec/changes/close-phase-0/specs/backend/audit/spec.md create mode 100644 openspec/changes/close-phase-0/specs/backend/auth/spec.md create mode 100644 openspec/changes/close-phase-0/specs/backend/login/spec.md create mode 100644 openspec/changes/close-phase-0/specs/backend/res/spec.md create mode 100644 openspec/changes/close-phase-0/tasks.md diff --git a/openspec/changes/close-phase-0/.openspec.yaml b/openspec/changes/close-phase-0/.openspec.yaml new file mode 100644 index 0000000..53f62d7 --- /dev/null +++ b/openspec/changes/close-phase-0/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-10 diff --git a/openspec/changes/close-phase-0/design.md b/openspec/changes/close-phase-0/design.md new file mode 100644 index 0000000..92cbb95 --- /dev/null +++ b/openspec/changes/close-phase-0/design.md @@ -0,0 +1,87 @@ +## Context + +Phase 0 后端已具备:Prisma 6 + MySQL(14 张 `sys_` 表)、登录/刷新/登出(JWT access 2h + refresh 7d 旋转)、多租户/软删/data_scope 过滤(Prisma `$extends` + AsyncLocalStorage 上下文)、10 个域 CRUD、pino 日志、Swagger。`@RequirePermissions` / `@Public` 装饰器与 `PermissionsGuard` 已定义但未接线;`sys_audit_log` / `sys_login_log` 表已存在但无写入点;无 seed 脚本。动机见 proposal.md。 + +关键现状约束: +- 权限/角色/资源/组织/租户用 `code` 关联,用户用 `id`(见 shared/data-model)。 +- `PrismaService` 对 create 不注入 `tenant_code`(避免 `$extends` 误注入 where),故写审计/登录日志需显式传 `tenantCode`。 +- 无 Redis,仅 MySQL;权限集载入方案受此约束。 + +## Goals / Non-Goals + +**Goals:** +- 三级鉴权落地:`@Public` / 无标记(仅登录)/ `@RequirePermissions`(登录+权限码)。 +- 权限码数据驱动:登录时从 role_resource 聚合 `type ∈ {button, api}` 的 `resource.code`。 +- `super_admin` 角色绕过权限码校验、data_scope 看全量。 +- 审计日志、登录日志自动落库(异步、不阻塞业务)。 +- 幂等 seed 脚本,一键初始化完整可登录的租户。 + +**Non-Goals:** +- 不做权限变更的即时生效(如每次请求查库、强制下线)。权限变更在下次登录/刷新后生效。 +- 不做「在线会话管理 / 踢人下线」的 UI(SESSION 域后置)。 +- 不改 DB schema、不改 shared 三层主 spec、不动前端。 + +## Decisions + +### D1. 权限集与超管标志存 Redis(而非 JWT / 每请求查库) +登录成功时聚合权限码(`user_role → role_resource → resource`,取 `type ∈ {button, api}` 的 code)与 `super` 标志,写入 Redis;`PermissionsGuard` 每请求从 Redis 读取做集合判断。 +- **key**:`{REDIS_KEY_PREFIX}perm:{tenantCode}:{userId}`;**value**:`{ super: boolean, permissions: string[] }` +- **TTL** 随会话(默认 7d,与 refresh 一致),刷新时续期,登出时删除。 +- **备选**:JWT payload 承载(token 随资源数线性膨胀、权限变更延迟 ≤2h);每请求查库聚合(无缓存层、每请求多次 JOIN)。均放弃。 +- **取舍**:引入 Redis 依赖,换来 token 最小、权限实时、多实例共享一致。 + +### D2. 权限实时失效(授权变更删缓存) +修改 `role_resource` / `user_role`(授权变更)后,删除该租户全部用户的权限缓存(SCAN `{prefix}perm:{tenantCode}:*` 后 DEL);下一次请求守卫重新聚合并回填。 +- **简化**:租户级失效,不精确到受影响用户;脚手架规模可接受。 +- 移除某人的 `super_admin` 角色同样经此路径即时生效(无需等 token 过期)。 + +### D3. 权限码语义:一个 code = 一个权限点,按钮与接口分用 +`resource.code` 全局唯一,细粒度 `domain:action`。约定: +- **api 资源**:每个受控接口一个,code 即接口权限码(如 `user:create`),`@RequirePermissions` 引用它。 +- **button 资源**:仅用于「纯前端按钮」(无独立受控接口的 UI 动作,如 `user:export`)。前端按钮若触发受控接口,直接复用该接口的 code 判定可见性,不重复登记 button 资源。 +- **menu 资源**:导航菜单,code 不进入接口权限集。 +- 避免「创建用户」按钮与「POST /users」接口争抢同一 code 的唯一键冲突。 + +### D4. 审计写入:`@AuditLog(module, action)` 装饰器 + 拦截器,异步快照 +装饰器 `SetMetadata` 记录 `module`/`action`;拦截器在 `next.handle()` 的 `tap` 中: +- 请求进入时快照 `tenantCode`/`userId`/`username`/`ip`/`userAgent`/`method`/`path`/脱敏后的 `body` 与起始时间(避免异步阶段 AsyncLocalStorage 失效丢租户上下文); +- 完成后异步 fire-and-forget 写 `sys_audit_log`;失败仅记告警日志,不影响响应。 +- 入参复用 BodyLoggingInterceptor 的递归脱敏(password/token/secret 等 → `***`)。 +- 成功 `status=success`,异常 `status=fail` + `error_message`。 + +### D5. 登录日志写入:auth.service 内联记录 +登录成功/失败在 `auth.service.login` 的对应分支直接写 `sys_login_log`(成功 status=success;失败 status=fail + message 原因:密码错误/账号停用/锁定/租户不存在)。同步写失败仅告警,不改变登录结果。 + +### D6. seed 数据建模与幂等 +`prisma/seed.ts` 用裸 `PrismaClient`(不经 `$extends`),全部按唯一键 upsert,可重复执行: +- 租户 `touyun`;根组织 `touyun`。 +- 角色 `super_admin`(`data_scope=all`)、可选 `admin`(`data_scope=all`)。 +- 用户 `touyun`(`bcryptjs` 哈希 `Touyun@123`),绑定 `super_admin`。 +- 资源树:菜单(工作台 + 各域导航)、api(每个受控接口一个,code=`domain:action`)、button(纯前端按钮)。 +- `role_resource`:`super_admin` 关联全部资源 code。 +- 密码哈希用 `bcryptjs`,与运行时代码同库。 + +### D7. 权限码域清单(domain:action) +`user` / `role` / `resource` / `org` / `dict` / `config` / `tenant` / `audit` / `login-log`;动作 `list`/`create`/`update`/`delete`/`reset-pwd`/`assign`/`assign-resource` 等,按各域实际接口对齐。 + +### D8. Redis 接入方式 +客户端用 `ioredis`;连接与 key 前缀均来自 env:`REDIS_HOST`/`REDIS_PORT`/`REDIS_DB`/`REDIS_PASSWORD`(默认 127.0.0.1:6379)、`REDIS_KEY_PREFIX`(默认 `sysmgr:`)。多 fork 项目共用一个 Redis 实例时按前缀隔离,互不干扰。 + +## Risks / Trade-offs + +- [新增 Redis 依赖] → Redis 已在 Docker 常驻,key 前缀隔离多项目;若某 fork 项目无 Redis,需自行提供回退(点查)能力,属后续可选。 +- [权限缓存与 DB 短暂不一致(删 key 窗口)] → 删 key 后下一次请求即回填,窗口毫秒级。 +- [异步审计可能丢日志(D4)] → 进程崩溃窗口极小;失败告警日志兜底,审计定位问题足够。 +- [审计 params 含敏感数据] → 复用脱敏,落库前过滤 password/token/secret 等。 +- [seed 覆盖既有同名数据] → upsert 语义「更新已有、补齐缺失」;生产慎跑,仅初始化用。 +- [接口漏挂 `@RequirePermissions` 导致裸奔] → 默认仅登录(宽松),收口只覆盖核心管理域;后续可升级为「默认拒绝」需全量盘点。 + +## Migration Plan + +- 无 DB schema 变更;表已存在。 +- 部署步骤:① 合并代码 ② 配置 `REDIS_HOST` / `REDIS_KEY_PREFIX` 等 env(指向常驻 Redis)③ `pnpm prisma:seed`(幂等,可重复)④ 重启服务。 +- 回滚:`git revert`;seed 数据无需回滚(幂等);Redis 缓存可整体清空(登录时自动回填)。 + +## Open Questions + +- 权限变更后是否需强制失效既有 access token(属 SESSION 域「在线会话/踢人」,后置)。 diff --git a/openspec/changes/close-phase-0/proposal.md b/openspec/changes/close-phase-0/proposal.md new file mode 100644 index 0000000..221b28e --- /dev/null +++ b/openspec/changes/close-phase-0/proposal.md @@ -0,0 +1,35 @@ +## Why + +Phase 0 后端已实现认证、10 个域 CRUD、多租户/软删/data_scope 过滤,但有三处「声明未生效」:① `@RequirePermissions` 已定义却零接口使用,任何登录用户都能增删改任意资源;② `sys_audit_log` / `sys_login_log` 只有查询接口、无任何写入点;③ 无 seed 脚本,新 fork 无法一键初始化。这三点是「脚手架」区别于普通 CRUD 的核心价值,fork 出去即是安全漏洞 + 排障盲区。本次收口。 + +## What Changes + +- 三级鉴权真正落地:`@Public` 不登录 → 无标记仅登录 → `@RequirePermissions('domain:action')` 登录+权限码;无标记接口默认仅登录。 +- 权限码数据驱动:权限码 = 资源表 `api`/`button` 型资源的 `code`(细粒度 `domain:action`),登录时聚合进请求上下文,守卫做集合判断。 +- 超管绕过:角色 `code=super_admin` 绕过权限码校验、data_scope 看全量。 +- 审计日志落库:`@AuditLog(domain, action)` 装饰器 + 拦截器自动记录写操作(增/删/改)与登录/登出,异步写入,失败不阻塞业务。 +- 登录日志落库:登录成功与失败均记录(失败附原因),含 IP/UA。 +- 新增 seed 脚本:一键初始化租户 `touyun`、用户 `touyun`(密码 `Touyun@123`)、`super_admin` 角色、全量资源树(菜单+按钮+api)并全授 super_admin。 + +## Capabilities + +### New Capabilities +- `backend/auth`: 三级鉴权执行语义、super_admin 绕过、登录时权限集入上下文 +- `backend/res`: 资源表作为 RBAC 授权锚点的运行时语义(权限码 = 资源 code,细粒度 `domain:action`) +- `backend/audit`: 审计日志写入(写操作 + 登录/登出,装饰器+拦截器,异步) +- `backend/login`: 登录日志写入(成功+失败,IP/UA/失败原因) + +### Modified Capabilities +(无 —— 本变更不修改 shared 三层主 spec,均为后端新能力) + +## Impact + +- 后端 `sources/sysmgr-nest`: + - 新增 Redis 依赖(ioredis)+ RedisService,权限集存 Redis(key 前缀经 `REDIS_KEY_PREFIX` 隔离多 fork 项目) + - `auth/`:permissions.guard 从 Redis 读权限集与 super 标志做判断;auth.service 登录/刷新时聚合权限码写 Redis + - `resources/ users/ roles/ orgs/ dicts/ configs/ tenants/`:写接口挂 `@RequirePermissions` + `@AuditLog`;授权变更后删权限缓存 + - 新增审计/登录日志写入服务(统一拦截器) + - `prisma/seed.ts` + package.json seed 脚本 +- 无 DB schema 变更(表已存在);无 shared spec 变更。 +- 新增基础设施依赖:Redis(Docker 常驻,key 前缀隔离多项目)。 +- 前端不受影响:权限码由资源表下发,前端菜单/按钮后续按此渲染。 diff --git a/openspec/changes/close-phase-0/specs/backend/audit/spec.md b/openspec/changes/close-phase-0/specs/backend/audit/spec.md new file mode 100644 index 0000000..17e3820 --- /dev/null +++ b/openspec/changes/close-phase-0/specs/backend/audit/spec.md @@ -0,0 +1,38 @@ +## Purpose + +定义操作日志(审计)的自动落库行为:写操作与登录/登出由装饰器与拦截器统一记录,异步写入不阻塞业务。 + +## ADDED Requirements + +### Requirement: 写操作自动审计 +系统 SHALL 对标注 `@AuditLog(module, action)` 的写接口(增/删/改)在请求完成后异步写入 sys_audit_log,记录操作人、模块、动作、请求方法、路径、入参、结果状态、IP、UA 与耗时。 + +#### Scenario: 写操作成功 +- **WHEN** 标注 `@AuditLog` 的写接口执行成功 +- **THEN** 系统写入一条 audit_log,status=success,含 module/action/method/path/user_id/username/ip/user_agent/duration_ms + +#### Scenario: 写操作失败 +- **WHEN** 标注 `@AuditLog` 的写接口执行失败 +- **THEN** 系统写入一条 audit_log,status=fail,并记录 error_message + +#### Scenario: 查询不审计 +- **WHEN** 查询类(读)接口执行 +- **THEN** 系统不写入审计日志 + +### Requirement: 登录登出审计 +系统 SHALL 记录登录成功与登出事件到 audit_log(module=auth,action=login/logout)。 + +#### Scenario: 登录成功审计 +- **WHEN** 用户登录成功 +- **THEN** 系统写入 audit_log(module=auth, action=login, status=success) + +#### Scenario: 登出审计 +- **WHEN** 用户登出 +- **THEN** 系统写入 audit_log(module=auth, action=logout) + +### Requirement: 审计异步不阻塞 +审计日志 SHALL 异步写入;写入失败仅告警,不得影响业务请求结果。 + +#### Scenario: 审计写入失败 +- **WHEN** 审计日志写库失败 +- **THEN** 系统记录告警日志,业务请求照常返回成功结果 diff --git a/openspec/changes/close-phase-0/specs/backend/auth/spec.md b/openspec/changes/close-phase-0/specs/backend/auth/spec.md new file mode 100644 index 0000000..16f14fb --- /dev/null +++ b/openspec/changes/close-phase-0/specs/backend/auth/spec.md @@ -0,0 +1,42 @@ +## Purpose + +定义后端三级鉴权的执行语义与超级管理员绕过规则,作为所有接口访问控制的统一契约。 + +## ADDED Requirements + +### Requirement: 三级鉴权 +系统 SHALL 对每个接口按三级放行:标注 `@Public` 的接口无需登录即可访问;未标注任何权限装饰器的接口仅校验有效登录态;标注 `@RequirePermissions` 的接口要求登录态且当前用户拥有对应权限码。 + +#### Scenario: 公开接口匿名访问 +- **WHEN** 请求标注 `@Public` 的接口(如登录、刷新 token、接口文档) +- **THEN** 系统无需 token 即放行 + +#### Scenario: 未标注接口仅校验登录 +- **WHEN** 未登录请求未标注权限装饰器的接口 +- **THEN** 系统返回 401 未登录 + +#### Scenario: 未标注接口登录后放行 +- **WHEN** 已登录用户请求未标注权限装饰器的接口 +- **THEN** 系统放行 + +#### Scenario: 需权限接口无权限码 +- **WHEN** 已登录用户请求 `@RequirePermissions('user:create')` 的接口,但其权限集不含 `user:create` +- **THEN** 系统返回 403 无权限 + +### Requirement: 权限集入上下文 +登录成功 SHALL 将用户的权限码集合(其全部角色经资源关联聚合得到的 api/button 资源 code)载入请求上下文,供权限守卫校验。 + +#### Scenario: 登录聚合权限 +- **WHEN** 用户登录成功 +- **THEN** 系统按用户全部角色的 role_resource 关联聚合出权限码集合并载入上下文 + +### Requirement: 超级管理员绕过 +拥有 `code=super_admin` 角色的用户 SHALL 绕过权限码校验;其列表查询的数据权限按「全部」处理,不受 data_scope 过滤。 + +#### Scenario: 超管访问任意需权限接口 +- **WHEN** 拥有 `super_admin` 角色的用户请求任意 `@RequirePermissions` 接口 +- **THEN** 系统无需校验其是否拥有该权限码即放行 + +#### Scenario: 超管查看全量数据 +- **WHEN** 超管执行列表查询 +- **THEN** 系统不做 data_scope 部门过滤,返回全量数据 diff --git a/openspec/changes/close-phase-0/specs/backend/login/spec.md b/openspec/changes/close-phase-0/specs/backend/login/spec.md new file mode 100644 index 0000000..b005a15 --- /dev/null +++ b/openspec/changes/close-phase-0/specs/backend/login/spec.md @@ -0,0 +1,23 @@ +## Purpose + +定义登录日志的自动落库行为:每次登录成功与失败均记录,失败附原因与来源信息。 + +## ADDED Requirements + +### Requirement: 登录成功记录 +系统 SHALL 在登录成功时写入 sys_login_log,记录 username、ip、user_agent、status=success。 + +#### Scenario: 登录成功 +- **WHEN** 用户登录成功 +- **THEN** 系统写入 login_log(username/ip/user_agent/status=success) + +### Requirement: 登录失败记录 +系统 SHALL 在登录失败时写入 sys_login_log,记录 username、ip、user_agent、status=fail 与失败原因 message。 + +#### Scenario: 密码错误 +- **WHEN** 用户以错误密码登录失败 +- **THEN** 系统写入 login_log,status=fail,message 说明密码错误 + +#### Scenario: 账号停用或锁定 +- **WHEN** 停用或锁定账号尝试登录失败 +- **THEN** 系统写入 login_log,status=fail,message 说明账号状态原因 diff --git a/openspec/changes/close-phase-0/specs/backend/res/spec.md b/openspec/changes/close-phase-0/specs/backend/res/spec.md new file mode 100644 index 0000000..8c57559 --- /dev/null +++ b/openspec/changes/close-phase-0/specs/backend/res/spec.md @@ -0,0 +1,30 @@ +## Purpose + +定义资源表作为 RBAC 授权锚点的运行时语义:权限码即资源 code,采用细粒度 domain:action 命名。 + +## ADDED Requirements + +### Requirement: 权限码即资源 code +系统 SHALL 以资源表的 `code` 作为接口与按钮的权限标识;`@RequirePermissions` 的参数 SHALL 为该 code 字符串。 + +#### Scenario: 接口声明权限码 +- **WHEN** 接口声明 `@RequirePermissions('user:create')` +- **THEN** 系统校验当前用户权限集是否包含字符串 `user:create` + +### Requirement: 权限码格式 +权限码 SHALL 采用细粒度 `domain:action` 格式(domain 为域,action 为动作,如 `user:list`、`user:create`、`role:assign`)。 + +#### Scenario: 细粒度权限码 +- **WHEN** 为「创建用户」接口登记权限码 +- **THEN** 该接口资源 code 为 `user:create` + +### Requirement: 权限码聚合来源 +用户权限集 SHALL 由其全部角色的 role_resource 关联(role_code + resource_code),取其中 `type ∈ {button, api}` 的 resource.code 并集聚合得到。 + +#### Scenario: 多角色权限并集 +- **WHEN** 用户拥有两个角色,各自关联了部分资源 +- **THEN** 该用户权限集为两角色资源 code 的并集 + +#### Scenario: 菜单不计入接口权限集 +- **WHEN** 用户角色关联了 `type=menu` 的资源 +- **THEN** 该 menu 的 code 不进入接口权限集(菜单用于导航渲染,不用于接口鉴权) diff --git a/openspec/changes/close-phase-0/tasks.md b/openspec/changes/close-phase-0/tasks.md new file mode 100644 index 0000000..1a9b939 --- /dev/null +++ b/openspec/changes/close-phase-0/tasks.md @@ -0,0 +1,44 @@ +## 1. Redis 接入与权限模型(auth 域) + +- [x] 1.1 引入 ioredis 依赖,封装 RedisService(get/set/del/按前缀 SCAN 删除),连接用 env(REDIS_HOST/PORT/DB/PASSWORD、REDIS_KEY_PREFIX,默认 127.0.0.1:6379、`sysmgr:`) +- [x] 1.2 auth.service 登录/刷新时聚合权限码集(user_role → role_resource → resource,取 `type ∈ {button, api}` 的 code)与 `super` 标志(是否绑定 `code=super_admin`),写入 Redis `{prefix}perm:{tenantCode}:{userId}`(TTL 随会话,刷新续期);登出删除 +- [x] 1.3 jwt-auth.guard 校验通过后将 userId/tenantCode/username 装入 `request.user`(JWT 不放权限集) +- [x] 1.4 permissions.guard 从 Redis 读 `{super, permissions}`:`super=true` 直接放行;否则校验权限集是否包含全部所需权限码,缺一返回 403 +- [x] 1.5 data_scope 过滤对 `super=true` 按「全部」处理(resolveDataScope 或各列表查询入口统一短路) +- [x] 1.6 授权变更失效:roles 模块改 role_resource / user_role 后删除该租户权限缓存(SCAN `{prefix}perm:{tenantCode}:*`) + +## 2. 权限码挂接口 + +- [x] 2.1 定义各域权限码常量清单(`domain:action`):tenant/user/role/resource/org/dict/config/audit/login-log 的 list/create/update/delete 等 +- [x] 2.2 tenants、users、roles、resources、orgs、dicts、configs 各 controller 的全部受控接口挂 `@RequirePermissions(...)`(写操作强制;list 按 domain:list 挂) +- [x] 2.3 audit-logs、login-logs 查询接口挂 `audit:list` / `login-log:list` +- [x] 2.4 登录/刷新/登出及 `/docs` 保持 `@Public`,无需权限码 + +## 3. 审计日志落库 + +- [x] 3.1 新增 `@AuditLog(module, action)` 装饰器(SetMetadata 记录 module/action) +- [x] 3.2 新增 AuditLogInterceptor:请求进入时快照 tenantCode/userId/username/ip/userAgent/method/path/脱敏 body 与起始时间;完成后异步 fire-and-forget 写入;异常时记告警不阻塞 +- [x] 3.3 新增 AuditLogService:写 sys_audit_log(显式传 tenantCode,复用 BodyLogging 脱敏) +- [x] 3.4 注册 AuditLogInterceptor(全局);给各写接口 controller 挂 `@AuditLog(domain, action)` +- [x] 3.5 登录成功/登出写 audit_log(module=auth,action=login/logout) + +## 4. 登录日志落库 + +- [x] 4.1 新增 LoginLogService:写 sys_login_log(username/ip/user_agent/status/message) +- [x] 4.2 auth.service.login 成功分支写 status=success;失败分支写 status=fail + message(密码错误/账号停用/锁定/租户不存在),并附 IP/UA + +## 5. seed 脚本 + +- [x] 5.1 新增 prisma/seed.ts:裸 PrismaClient,按唯一键 upsert,可重复执行 +- [x] 5.2 生成租户 `touyun`、根组织、角色 `super_admin`(data_scope=all)、用户 `touyun`(bcryptjs 哈希 `Touyun@123`)并绑定 super_admin +- [x] 5.3 生成全量资源树:菜单(工作台 + 各域导航)、api(每个受控接口一个,code=`domain:action`,含 method/api_path)、button(纯前端按钮) +- [x] 5.4 `role_resource` 将 super_admin 关联全部资源 code +- [x] 5.5 package.json 增加 seed 脚本(prisma db seed 配置)并跑通 + +## 6. 验证 + +- [x] 6.1 跑 seed 后以 `touyun` / `Touyun@123` 登录成功,Redis 中生成 `{prefix}perm:touyun:{userId}` 缓存(含 permissions/super) +- [x] 6.2 超管访问受控接口放行、列表查询全量;新建一个无权限的普通用户访问受控接口返回 403 +- [x] 6.3 修改某角色授权(role_resource)后,该租户权限缓存被删除,受影响用户下一次请求按新权限生效 +- [x] 6.4 写操作后 sys_audit_log 有记录(status/params 脱敏);登录成功/失败后 sys_login_log 有记录(失败含原因) +- [x] 6.5 回归:登录/刷新/登出、现有 CRUD、日志输出均正常;测试后停止服务 diff --git a/sources/sysmgr-nest b/sources/sysmgr-nest index c680fb9..7340658 160000 --- a/sources/sysmgr-nest +++ b/sources/sysmgr-nest @@ -1 +1 @@ -Subproject commit c680fb9dc7b5b39c03e029f72ffc2776d2c731d5 +Subproject commit 7340658305de30b234bc87e0b5490e4c34a49b9a