@yss123/cage-admin 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -3
- package/dist/bootstrap.d.ts +27 -1
- package/dist/bootstrap.js +52 -16
- package/dist/bootstrap.js.map +1 -1
- package/dist/catalog.d.ts +48 -0
- package/dist/catalog.js +96 -0
- package/dist/catalog.js.map +1 -0
- package/dist/diagnostics.d.ts +14 -0
- package/dist/diagnostics.js +163 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/domain/constraints.d.ts +23 -0
- package/dist/domain/constraints.js +27 -0
- package/dist/domain/constraints.js.map +1 -1
- package/dist/domain/index.d.ts +5 -0
- package/dist/domain/index.js +5 -0
- package/dist/domain/index.js.map +1 -1
- package/dist/domain/org.d.ts +230 -149
- package/dist/domain/org.js +98 -4
- package/dist/domain/org.js.map +1 -1
- package/dist/http.d.ts +13 -1
- package/dist/http.js +28 -13
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
- package/ui/dist/app/shell.js +135 -0
- package/ui/dist/app.js +34 -0
- package/ui/dist/modules/audit/audit-page.js +107 -0
- package/ui/dist/modules/audit/index.js +5 -0
- package/ui/dist/modules/auth/index.js +5 -0
- package/ui/dist/modules/auth/login-page.js +38 -0
- package/ui/dist/modules/models/index.js +5 -0
- package/ui/dist/modules/models/models-page.js +60 -0
- package/ui/dist/modules/operations/index.js +6 -0
- package/ui/dist/modules/operations/operations-page.js +110 -0
- package/ui/dist/modules/operations/sync-page.js +60 -0
- package/ui/dist/modules/org/index.js +5 -0
- package/ui/dist/modules/org/org-page.js +243 -0
- package/ui/dist/modules/personalities/index.js +5 -0
- package/ui/dist/modules/personalities/personalities-page.js +161 -0
- package/ui/dist/modules/roles/index.js +5 -0
- package/ui/dist/modules/roles/roles-page.js +348 -0
- package/ui/dist/modules/tasks/index.js +5 -0
- package/ui/dist/modules/tasks/tasks-page.js +68 -0
- package/ui/dist/shared/api.js +166 -0
- package/ui/dist/shared/dom.js +139 -0
- package/ui/dist/shared/labels.js +84 -0
- package/ui/dist/shared/navigation.js +14 -0
- package/ui/dist/shared/types.js +1 -0
- package/ui/index.html +12 -10
- package/ui/style.css +1657 -41
- package/ui/app.js +0 -339
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# @yss123/cage-admin
|
|
2
2
|
|
|
3
|
-
组织模型声明与
|
|
4
|
-
|
|
5
|
-
Cage 是通过 TypeScript 声明业务、由框架统一执行权限和数据规则的中后台框架。此包是框架的一部分。
|
|
3
|
+
组织模型声明与 `/admin/` 系统管理。提供用户、部门、角色、能力授权、数据范围和防提权规则,以及配套的系统管理接口和页面。适合 Cage 应用的基础组织与运营管理;业务审批、文件和 AI 管理入口按实际启用能力提供。
|
|
6
4
|
|
|
7
5
|
## 使用
|
|
8
6
|
|
|
@@ -12,6 +10,67 @@ pnpm add @yss123/cage-admin
|
|
|
12
10
|
|
|
13
11
|
首次使用建议通过脚手架创建完整项目,所需依赖由模板统一配置。需要 Node.js 24 或更新版本。
|
|
14
12
|
|
|
13
|
+
## 注册组织模型并初始化
|
|
14
|
+
|
|
15
|
+
在应用 `packages/domain/src/index.ts` 保留这一行,同时导出自己的业务模块:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
/** 组织声明与业务共同进入注册表;导入本模块不创建账号或连接数据库。 */
|
|
19
|
+
export * from '@yss123/cage-admin/domain';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
标准 API 通过 `@yss123/cage-http` 的 `createServer({ domain })` 自动装配管理接口和静态页面,默认开启 `/admin/`,不需要另写管理路由。API 与 worker 使用同一 domain;前端不要导入本包服务端入口。
|
|
23
|
+
|
|
24
|
+
在脚手架项目中填写已有 PostgreSQL 数据库的本地配置,运行 `pnpm exec cage init`、构建与 `pnpm gen`,审查后执行 `pnpm migrate`。然后显式初始化组织:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
pnpm exec cage bootstrap --credentials-file .cage/admin-credentials.json
|
|
28
|
+
pnpm dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
初始凭据只交付到新的受限文件,交付后移除;不要提交到 Git 或贴入日志。服务启动不会自动重置管理员密码。管理员再配置用户、部门、角色、业务命令能力与数据范围;新增业务能力不会自动授予生产角色。
|
|
32
|
+
|
|
33
|
+
| 公开入口 | 用途 |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `@yss123/cage-admin/domain` | `Dept/User/Role` 聚合及创建、修改、角色分配和授权命令,供 domain 组合 |
|
|
36
|
+
| `bootstrapOrg` | 受控初始化工具调用的组织准备,普通服务启动不调用 |
|
|
37
|
+
| `adminRoutes`、`adminCatalog` | HTTP 装配层的管理接口与展示目录 |
|
|
38
|
+
| `noEscalation`、`noEscalationViaRoles`、`noWiderScope`、`knownCapability` | 组织授权命令使用的防提权与能力目录约束 |
|
|
39
|
+
| `hashPassword` | 服务端密码处理辅助函数,不在浏览器调用或存储明文 |
|
|
40
|
+
|
|
41
|
+
角色选人、菜单显示和中文标签不授予权限。最终写入通过命令执行,授权变更也受当前操作者的能力与范围约束。`grantAll/grantAllToSuperadmin` 是显式管理工具,不应在日常启动或升级中自动执行。
|
|
42
|
+
|
|
43
|
+
## 中文管理界面
|
|
44
|
+
|
|
45
|
+
启动 API 后访问 `/admin/`。页面包含审批待办、用户与部门、角色与授权、操作审计、异步任务和第三方同步。角色页按业务对象分组展示权限,支持中文或代码搜索;操作权限即时保存,数据范围与审批额度需要点击各自的保存按钮。中文显示不改变真实权限标识,也不会自动给角色增加权限。
|
|
46
|
+
|
|
47
|
+
业务聚合和命令应填写中文 `description`。项目根目录 `cage.config.json` 的 `graph.labels` 同时用于图谱和系统管理,无需再维护一份翻译:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"graph": {
|
|
52
|
+
"labels": {
|
|
53
|
+
"aggregate:PurchaseOrder": "采购单",
|
|
54
|
+
"command:PurchaseOrder.submit": "提交审批",
|
|
55
|
+
"capability:PurchaseOrder.export": "导出采购单",
|
|
56
|
+
"approval:poApproval": "采购审批流程",
|
|
57
|
+
"event:OrderSubmitted": "采购单已提交",
|
|
58
|
+
"job:expireStaleOrders": "清理过期草稿"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
以上是合并进已有配置的片段,不要覆盖项目中的 domain、contracts 等设置。修改显示名后重启 API。API 进程应在项目根目录启动;Docker 模板也会复制这份配置到运行时镜像。
|
|
65
|
+
|
|
66
|
+
名称优先级:权限专用 `capability:` → `command:` → 命令 `description` → 常用动作中文;业务对象使用 `aggregate:` → 聚合 `description`。read/export 会自动组合对象中文名,敏感字段明文权限和调用方也会显示中文。框架无法凭代码猜测自定义业务术语,缺少中文时需要在声明或配置中补充。
|
|
67
|
+
|
|
68
|
+
直接集成 HTTP 服务时,也可以通过 `createServer({ domain, adminLabels: { 'aggregate:Document': '业务单据' } })` 提供显示名;它覆盖本地 `graph.labels`。此选项属于 `@yss123/cage-http`,不是领域授权配置。
|
|
69
|
+
|
|
70
|
+
## 维护管理前端
|
|
71
|
+
|
|
72
|
+
`ui/src/app` 负责页面组合,`ui/src/modules` 按管理功能组织,`ui/src/shared` 放请求、标签和 DOM 组件。界面由 TypeScript 编译成原生浏览器模块,不要求业务项目使用特定前端框架。执行 `pnpm build` 同时构建接口与界面;发布时分发 `ui/dist`、HTML 和 CSS,不直接执行 TypeScript 源码。
|
|
73
|
+
|
|
15
74
|
- [完整使用说明](https://github.com/shunshunyue/cage/blob/main/docs/使用说明.md)
|
|
16
75
|
- [框架源码与范例](https://github.com/shunshunyue/cage)
|
|
17
76
|
- [问题反馈](https://github.com/shunshunyue/cage/issues)
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type Kernel } from '@yss123/cage-kernel';
|
|
2
|
+
/**
|
|
3
|
+
* 初始化结果。created=false 表示租户已有用户,本次没有重置组织或密码。
|
|
4
|
+
* 创建成功时才携带初始凭据与组织 ID;密码仅交付给受控调用者,不应写入日志或公开响应。
|
|
5
|
+
*/
|
|
2
6
|
export interface BootstrapResult {
|
|
3
7
|
created: boolean;
|
|
4
8
|
username?: string;
|
|
@@ -6,12 +10,34 @@ export interface BootstrapResult {
|
|
|
6
10
|
rootDeptId?: string;
|
|
7
11
|
superadminRoleId?: string;
|
|
8
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* 首次创建根部门、超级管理员角色及账号,并在同一事务中设置能力、范围和密码。
|
|
15
|
+
* 先加租户锁再检查已有用户;已有用户直接返回 created=false,不覆盖任何密码。
|
|
16
|
+
* 可通过 opts 指定租户与初始名称/凭据;未提供密码时生成随机值。
|
|
17
|
+
* deliverCredentials 在数据库提交前被等待,抛错会回滚组织写入;调用方负责安全保管交付结果。
|
|
18
|
+
* 数据库或命令校验失败会向外传播,成功返回凭据并不代表业务角色和生产授权策略已经配置完毕。
|
|
19
|
+
*/
|
|
9
20
|
export declare function bootstrapOrg(k: Kernel, opts?: {
|
|
10
21
|
tenantId?: string;
|
|
11
22
|
adminUsername?: string;
|
|
12
23
|
adminPassword?: string;
|
|
13
24
|
rootDeptName?: string;
|
|
25
|
+
/** 提交前交付初始凭据;交付失败须抛错,组织和账号随事务一起回滚。 */
|
|
26
|
+
deliverCredentials?: (credentials: {
|
|
27
|
+
username: string;
|
|
28
|
+
password: string;
|
|
29
|
+
}) => void | Promise<void>;
|
|
14
30
|
}): Promise<BootstrapResult>;
|
|
15
|
-
/**
|
|
31
|
+
/** 显式授权维护工具;普通启动和生产角色不应自动调用。 */
|
|
32
|
+
/**
|
|
33
|
+
* 显式补齐指定角色尚未拥有的目录能力,返回本次新增数量。
|
|
34
|
+
* 已有授权参数保持不变;新授出的 approve 能力附带这里的初始化额度。
|
|
35
|
+
* 每次调用通过系统身份执行领域命令,属于高权限维护操作;多次命令并非整批单一事务。
|
|
36
|
+
* 调用方必须确认目标角色与租户,不应作为生产角色自动获取新增业务能力的入口。
|
|
37
|
+
*/
|
|
16
38
|
export declare function grantAll(k: Kernel, roleId: string, tenantId?: string): Promise<number>;
|
|
39
|
+
/**
|
|
40
|
+
* 在指定租户中查找 code 为 superadmin 的角色,再复用 grantAll 补能力。
|
|
41
|
+
* 角色不存在返回 0;该包装不会创建角色,也不会给其他角色授予权限。
|
|
42
|
+
*/
|
|
17
43
|
export declare function grantAllToSuperadmin(k: Kernel, tenantId?: string): Promise<number>;
|
package/dist/bootstrap.js
CHANGED
|
@@ -1,28 +1,60 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 显式初始化组织及维护角色授权的服务端工具。
|
|
3
|
+
* 所有组织变更调用领域命令;首次初始化用同租户咨询锁与事务避免并发创建半套组织。
|
|
4
|
+
* 本模块使用受信任的系统身份,调用者应明确决定何时执行,不能把它暴露为普通业务自助授权接口。
|
|
5
|
+
*/
|
|
6
|
+
// 显式初始化组织:建根部门、超级管理员角色和初始管理员账号。
|
|
2
7
|
// 超级管理员只是一个拥有全部能力、范围为 all 的角色,是数据而不是代码里的特例。
|
|
3
8
|
import { randomBytes } from 'node:crypto';
|
|
4
9
|
import { DEFAULT_TENANT, systemCtx } from '@yss123/cage-kernel';
|
|
10
|
+
import { sql } from 'kysely';
|
|
11
|
+
/**
|
|
12
|
+
* 为初始化命令构造当前租户的系统上下文,能力取自当前注册表目录。
|
|
13
|
+
* 此身份仅服务于受控初始化,不是前端可自行构造的管理员豁免。
|
|
14
|
+
*/
|
|
5
15
|
function bootstrapCtx(k, tenantId) {
|
|
6
16
|
return systemCtx({ kind: 'system', name: 'bootstrap', tenantId, capabilities: k.catalogCaps() });
|
|
7
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 首次创建根部门、超级管理员角色及账号,并在同一事务中设置能力、范围和密码。
|
|
20
|
+
* 先加租户锁再检查已有用户;已有用户直接返回 created=false,不覆盖任何密码。
|
|
21
|
+
* 可通过 opts 指定租户与初始名称/凭据;未提供密码时生成随机值。
|
|
22
|
+
* deliverCredentials 在数据库提交前被等待,抛错会回滚组织写入;调用方负责安全保管交付结果。
|
|
23
|
+
* 数据库或命令校验失败会向外传播,成功返回凭据并不代表业务角色和生产授权策略已经配置完毕。
|
|
24
|
+
*/
|
|
8
25
|
export async function bootstrapOrg(k, opts = {}) {
|
|
9
26
|
const tenantId = opts.tenantId ?? DEFAULT_TENANT;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
return k.app.transaction().execute(async (tx) => {
|
|
28
|
+
// 初始化失败必须整体回滚;同租户的两个初始化进程不能各自留下半套组织。
|
|
29
|
+
await sql `select pg_advisory_xact_lock(hashtext('cage:organization-bootstrap'), hashtext(${tenantId}))`.execute(tx);
|
|
30
|
+
const existing = await tx.selectFrom('sys.user').select((eb) => eb.fn.countAll().as('n')).where('tenant_id', '=', tenantId).executeTakeFirst();
|
|
31
|
+
if (Number(existing?.n ?? 0) > 0)
|
|
32
|
+
return { created: false };
|
|
33
|
+
const ctx = bootstrapCtx(k, tenantId);
|
|
34
|
+
const execute = (command, input) => k.executeIn(tx, command, input, ctx, 0);
|
|
35
|
+
const root = await execute('Dept.create', { name: opts.rootDeptName ?? '总部', code: 'root' });
|
|
36
|
+
const role = await execute('Role.create', { code: 'superadmin', name: '超级管理员' });
|
|
37
|
+
for (const cap of k.catalogCaps()) {
|
|
38
|
+
await execute('Role.grantCapability', { id: role.id, capability: cap, params: cap.endsWith('.approve') ? { ceiling: 1_000_000_000 } : undefined });
|
|
39
|
+
}
|
|
40
|
+
await execute('Role.setScope', { id: role.id, aggregate: '*', combinator: 'all' });
|
|
41
|
+
const username = opts.adminUsername ?? 'admin';
|
|
42
|
+
const password = opts.adminPassword ?? randomBytes(9).toString('base64url');
|
|
43
|
+
const user = await execute('User.create', { username, name: '管理员', deptId: root.id });
|
|
44
|
+
await execute('User.assignRoles', { id: user.id, roleIds: [role.id] });
|
|
45
|
+
await execute('User.setPassword', { id: user.id, password });
|
|
46
|
+
// 先确认调用方已经保管凭据再提交,避免留下无法登录的初始管理员。
|
|
47
|
+
await opts.deliverCredentials?.({ username, password });
|
|
48
|
+
return { created: true, username, password, rootDeptId: root.id, superadminRoleId: role.id };
|
|
49
|
+
});
|
|
24
50
|
}
|
|
25
|
-
/**
|
|
51
|
+
/** 显式授权维护工具;普通启动和生产角色不应自动调用。 */
|
|
52
|
+
/**
|
|
53
|
+
* 显式补齐指定角色尚未拥有的目录能力,返回本次新增数量。
|
|
54
|
+
* 已有授权参数保持不变;新授出的 approve 能力附带这里的初始化额度。
|
|
55
|
+
* 每次调用通过系统身份执行领域命令,属于高权限维护操作;多次命令并非整批单一事务。
|
|
56
|
+
* 调用方必须确认目标角色与租户,不应作为生产角色自动获取新增业务能力的入口。
|
|
57
|
+
*/
|
|
26
58
|
export async function grantAll(k, roleId, tenantId = DEFAULT_TENANT) {
|
|
27
59
|
const ctx = bootstrapCtx(k, tenantId);
|
|
28
60
|
const role = await k.app.selectFrom('sys.role').select('capabilities').where('id', '=', roleId).executeTakeFirst();
|
|
@@ -36,6 +68,10 @@ export async function grantAll(k, roleId, tenantId = DEFAULT_TENANT) {
|
|
|
36
68
|
}
|
|
37
69
|
return n;
|
|
38
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* 在指定租户中查找 code 为 superadmin 的角色,再复用 grantAll 补能力。
|
|
73
|
+
* 角色不存在返回 0;该包装不会创建角色,也不会给其他角色授予权限。
|
|
74
|
+
*/
|
|
39
75
|
export async function grantAllToSuperadmin(k, tenantId = DEFAULT_TENANT) {
|
|
40
76
|
const r = await k.app.selectFrom('sys.role').select('id').where('code', '=', 'superadmin').where('tenant_id', '=', tenantId).executeTakeFirst();
|
|
41
77
|
return r ? grantAll(k, r.id, tenantId) : 0;
|
package/dist/bootstrap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将当前注册表整理为系统管理 UI 使用的能力、对象、审批与任务目录。
|
|
3
|
+
* 调用方可传入图谱中文 labels 覆盖显示名;技术标识保持原值供授权和路由使用。
|
|
4
|
+
* 生成目录是同步内存操作,不读取用户实际授权,也不表示目录中的每项能力已授给当前用户。
|
|
5
|
+
*/
|
|
6
|
+
import { type Registry } from '@yss123/cage-kernel';
|
|
7
|
+
/** 显示名沿用图谱的节点键;只影响文案,不重命名权限、命令、表或业务字段。 */
|
|
8
|
+
export type AdminLabels = Record<string, string>;
|
|
9
|
+
/**
|
|
10
|
+
* 展示目录独立于内核的授权目录。原始字段继续提供给 API 调用者,额外 label 专供界面使用。
|
|
11
|
+
* 自定义业务名称来自项目声明或 labels,框架不猜测客户术语;缺少中文时明确提示需要命名。
|
|
12
|
+
*/
|
|
13
|
+
export declare function adminCatalog(registry: Registry, labels?: AdminLabels): {
|
|
14
|
+
capabilities: {
|
|
15
|
+
label: string;
|
|
16
|
+
capability: string;
|
|
17
|
+
kind: "command" | "read" | "export" | "unmask" | "system";
|
|
18
|
+
aggregate?: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
invokedBy?: readonly string[];
|
|
21
|
+
}[];
|
|
22
|
+
aggregates: {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string | null;
|
|
25
|
+
label: string;
|
|
26
|
+
states: readonly any[];
|
|
27
|
+
scope: {
|
|
28
|
+
deptField: string | null;
|
|
29
|
+
ownerField: string | null;
|
|
30
|
+
};
|
|
31
|
+
sensitive: Record<string, string>;
|
|
32
|
+
}[];
|
|
33
|
+
combinators: string[];
|
|
34
|
+
approvals: {
|
|
35
|
+
name: string;
|
|
36
|
+
process: string;
|
|
37
|
+
aggregate: string;
|
|
38
|
+
routable: readonly string[];
|
|
39
|
+
label: string;
|
|
40
|
+
}[];
|
|
41
|
+
jobs: {
|
|
42
|
+
name: string;
|
|
43
|
+
cron: string;
|
|
44
|
+
description: string | null;
|
|
45
|
+
label: string;
|
|
46
|
+
}[];
|
|
47
|
+
labels: AdminLabels;
|
|
48
|
+
};
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将当前注册表整理为系统管理 UI 使用的能力、对象、审批与任务目录。
|
|
3
|
+
* 调用方可传入图谱中文 labels 覆盖显示名;技术标识保持原值供授权和路由使用。
|
|
4
|
+
* 生成目录是同步内存操作,不读取用户实际授权,也不表示目录中的每项能力已授给当前用户。
|
|
5
|
+
*/
|
|
6
|
+
import { sensitiveFields } from '@yss123/cage-kernel';
|
|
7
|
+
const actions = {
|
|
8
|
+
create: '新建',
|
|
9
|
+
read: '查看',
|
|
10
|
+
export: '导出',
|
|
11
|
+
update: '编辑',
|
|
12
|
+
rename: '修改名称',
|
|
13
|
+
delete: '删除',
|
|
14
|
+
archive: '停用',
|
|
15
|
+
enable: '启用',
|
|
16
|
+
disable: '停用',
|
|
17
|
+
submit: '提交',
|
|
18
|
+
approve: '审批通过',
|
|
19
|
+
reject: '驳回',
|
|
20
|
+
cancel: '取消',
|
|
21
|
+
revoke: '撤回',
|
|
22
|
+
confirm: '确认',
|
|
23
|
+
void: '作废',
|
|
24
|
+
receive: '确认收货',
|
|
25
|
+
markReceived: '确认收货',
|
|
26
|
+
recordErpNo: '回写外部单号',
|
|
27
|
+
acquire: '占用资源',
|
|
28
|
+
release: '释放资源',
|
|
29
|
+
assignRoles: '分配角色',
|
|
30
|
+
setPassword: '重置密码',
|
|
31
|
+
grantCapability: '授予操作权限',
|
|
32
|
+
revokeCapability: '收回操作权限',
|
|
33
|
+
setScope: '设置数据范围',
|
|
34
|
+
};
|
|
35
|
+
const sensitive = {
|
|
36
|
+
phone: '手机号',
|
|
37
|
+
email: '邮箱',
|
|
38
|
+
idCard: '身份证号',
|
|
39
|
+
bankCard: '银行卡号',
|
|
40
|
+
name: '姓名',
|
|
41
|
+
address: '地址',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* 展示目录独立于内核的授权目录。原始字段继续提供给 API 调用者,额外 label 专供界面使用。
|
|
45
|
+
* 自定义业务名称来自项目声明或 labels,框架不猜测客户术语;缺少中文时明确提示需要命名。
|
|
46
|
+
*/
|
|
47
|
+
export function adminCatalog(registry, labels = {}) {
|
|
48
|
+
const aggregates = registry.aggregates.map((a) => {
|
|
49
|
+
const label = labels[`aggregate:${a.name}`] || a.description || a.name;
|
|
50
|
+
return {
|
|
51
|
+
name: a.name,
|
|
52
|
+
description: a.description ?? null,
|
|
53
|
+
label,
|
|
54
|
+
states: a.states.values,
|
|
55
|
+
scope: { deptField: a.scope.deptField ?? null, ownerField: a.scope.ownerField ?? null },
|
|
56
|
+
sensitive: sensitiveFields(a),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
const names = new Map(aggregates.map((a) => [a.name, a.label]));
|
|
60
|
+
const capabilities = registry.capabilityCatalog().map((c) => {
|
|
61
|
+
const subject = names.get(c.aggregate ?? '') ?? '';
|
|
62
|
+
const action = c.capability.split('.').at(-1);
|
|
63
|
+
const override = labels[`capability:${c.capability}`] || labels[`command:${c.capability}`];
|
|
64
|
+
// 内核的 read/export 文案可能含英文聚合名,重组后才能复用项目提供的中文名称。
|
|
65
|
+
const label = override ||
|
|
66
|
+
(c.kind === 'read'
|
|
67
|
+
? `查看${subject}`
|
|
68
|
+
: c.kind === 'export'
|
|
69
|
+
? `导出${subject}`
|
|
70
|
+
: c.kind === 'unmask'
|
|
71
|
+
? `查看${sensitive[action] ?? '敏感字段'}明文`
|
|
72
|
+
: c.description || actions[action] || '自定义操作');
|
|
73
|
+
return { ...c, label };
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
capabilities,
|
|
77
|
+
aggregates,
|
|
78
|
+
combinators: ['self', 'dept', 'deptSubtree', 'deptSet', 'all'],
|
|
79
|
+
approvals: registry.approvals.map(({ a, name }) => ({
|
|
80
|
+
name,
|
|
81
|
+
process: a.process,
|
|
82
|
+
aggregate: a.agg.name,
|
|
83
|
+
routable: a.routable,
|
|
84
|
+
label: labels[`approval:${name}`] || a.process,
|
|
85
|
+
})),
|
|
86
|
+
jobs: registry.jobs.map((j) => ({
|
|
87
|
+
name: j.name,
|
|
88
|
+
cron: j.cron,
|
|
89
|
+
description: j.description ?? null,
|
|
90
|
+
label: labels[`job:${j.name}`] || j.description || j.name,
|
|
91
|
+
})),
|
|
92
|
+
// 事件、状态和连接器也能复用图谱文案,供审计、待办和运维页面解释业务标识。
|
|
93
|
+
labels,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 提供当前进程、授权解释及异步执行时间线的只读诊断。
|
|
3
|
+
* 接口要求登录和 Sys.ops.read;涉及具体对象时继续检查读取能力与数据范围。
|
|
4
|
+
* 时间线只展示已存在的执行记录,不把静态关系当作成功证据;错误内容仅保留可检索代码。
|
|
5
|
+
*/
|
|
6
|
+
import { type Kernel } from '@yss123/cage-kernel';
|
|
7
|
+
import type { FastifyInstance } from 'fastify';
|
|
8
|
+
/**
|
|
9
|
+
* 将三个诊断接口注册到已有 Fastify 实例,复用认证层写入的 cageCtx。
|
|
10
|
+
* 总览比较 API/worker 指纹与心跳;授权解释复用内核判断但不执行 handler;时间线按对象范围筛选后限量。
|
|
11
|
+
* 解释其他用户身份还要求操作者能读取该用户。审计明细额外要求 Sys.audit.read。
|
|
12
|
+
* 返回记录受数量上限限制,truncated 表示尚有未返回证据;本函数不迁移、不重启进程或恢复失败任务。
|
|
13
|
+
*/
|
|
14
|
+
export declare function diagnosticRoutes(app: FastifyInstance, k: Kernel): void;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 提供当前进程、授权解释及异步执行时间线的只读诊断。
|
|
3
|
+
* 接口要求登录和 Sys.ops.read;涉及具体对象时继续检查读取能力与数据范围。
|
|
4
|
+
* 时间线只展示已存在的执行记录,不把静态关系当作成功证据;错误内容仅保留可检索代码。
|
|
5
|
+
*/
|
|
6
|
+
// 诊断只关联实际记录,不从静态图推断某一次操作已经执行;入口复用运维与安全读权限。
|
|
7
|
+
import { cageError, compileScope, loadScoped, registryFingerprint } from '@yss123/cage-kernel';
|
|
8
|
+
import { workerSnapshot } from '@yss123/cage-kernel/worker';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
const request = z.object({ requestId: z.string().min(1).max(200).optional(), outboxId: z.coerce.number().int().positive().optional(), aggregate: z.string().optional(), aggregateId: z.uuid().optional() });
|
|
12
|
+
const authorization = z.object({ command: z.string().min(1), input: z.record(z.string(), z.unknown()).default({}), userId: z.uuid().optional() });
|
|
13
|
+
const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
14
|
+
/**
|
|
15
|
+
* 检查诊断入口所需的登录身份与运维读取能力。
|
|
16
|
+
* 未登录与已登录但无权分别抛 UNAUTHENTICATED / FORBIDDEN,不执行任何授权变更。
|
|
17
|
+
*/
|
|
18
|
+
function requireOps(ctx) {
|
|
19
|
+
if (!ctx.userId)
|
|
20
|
+
throw cageError('UNAUTHENTICATED');
|
|
21
|
+
if (!ctx.capabilities.has('Sys.ops.read'))
|
|
22
|
+
throw cageError('FORBIDDEN');
|
|
23
|
+
}
|
|
24
|
+
/** 错误文本可能含请求载荷或第三方响应,诊断默认只交付可检索的机器码。 */
|
|
25
|
+
function failure(value) {
|
|
26
|
+
if (value === null || value === undefined || value === '')
|
|
27
|
+
return null;
|
|
28
|
+
const code = typeof value === 'string' ? value.match(/\b(CAGE-[A-Z]+-\d{3}|UNAUTHENTICATED|FORBIDDEN|NOT_FOUND|INVALID_INPUT|CONFLICT|INTERNAL|RATE_LIMITED|ETIMEDOUT|ECONNRESET|ECONNREFUSED)\b/)?.[1] : undefined;
|
|
29
|
+
return { code: code ?? 'INTERNAL', detail: '原始错误文本已隐藏;请按错误码和关联记录排查' };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 选取 worker 运行元数据用于诊断,缺记录返回 null。
|
|
33
|
+
* lastError 经 failure 隐去原始文本,避免外部响应或业务载荷进入诊断页面。
|
|
34
|
+
*/
|
|
35
|
+
function workerDetails(raw) {
|
|
36
|
+
if (!raw)
|
|
37
|
+
return null;
|
|
38
|
+
return { id: raw.id, pid: raw.pid, status: raw.status, listenerHealthy: raw.listenerHealthy, schedulerHealthy: raw.schedulerHealthy,
|
|
39
|
+
heartbeatAt: raw.heartbeatAt, inFlight: raw.inFlight, lastError: failure(raw.lastError) };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 将三个诊断接口注册到已有 Fastify 实例,复用认证层写入的 cageCtx。
|
|
43
|
+
* 总览比较 API/worker 指纹与心跳;授权解释复用内核判断但不执行 handler;时间线按对象范围筛选后限量。
|
|
44
|
+
* 解释其他用户身份还要求操作者能读取该用户。审计明细额外要求 Sys.audit.read。
|
|
45
|
+
* 返回记录受数量上限限制,truncated 表示尚有未返回证据;本函数不迁移、不重启进程或恢复失败任务。
|
|
46
|
+
*/
|
|
47
|
+
export function diagnosticRoutes(app, k) {
|
|
48
|
+
const loadedAt = new Date().toISOString();
|
|
49
|
+
app.get('/api/sys/diagnostics', async (req) => {
|
|
50
|
+
const ctx = req.cageCtx;
|
|
51
|
+
requireOps(ctx);
|
|
52
|
+
const [workers, migrations, roles] = await Promise.all([
|
|
53
|
+
k.app.selectFrom('sys.worker_runtime').selectAll().orderBy('heartbeat_at', 'desc').limit(10).execute(),
|
|
54
|
+
k.app.selectFrom('sys.schema_migrations').select('name').execute(),
|
|
55
|
+
k.app.selectFrom('sys.role').select('capabilities').where('tenant_id', '=', ctx.tenantId).where('status', '=', 'active').execute(),
|
|
56
|
+
]);
|
|
57
|
+
const registryHash = registryFingerprint(k.registry);
|
|
58
|
+
const worker = workers[0];
|
|
59
|
+
const lastHeartbeatAt = worker ? new Date(worker.heartbeat_at).toISOString() : null;
|
|
60
|
+
return {
|
|
61
|
+
version, role: 'api', registryHash, loadedAt,
|
|
62
|
+
consumers: worker?.manifest?.consumers,
|
|
63
|
+
worker: worker ? { version: worker.manifest.version, registryHash: worker.registry_fingerprint, lastHeartbeatAt,
|
|
64
|
+
ready: worker.status === 'running' && Date.now() - Date.parse(lastHeartbeatAt) < 30_000 && worker.registry_fingerprint === registryHash,
|
|
65
|
+
details: workerDetails(worker.details), manifest: worker.manifest } : undefined,
|
|
66
|
+
authorization: { configuredCapabilities: [...new Set(roles.flatMap((role) => role.capabilities.map((capability) => capability.capability)))].sort() },
|
|
67
|
+
migrations: { applied: migrations.map((migration) => migration.name).sort() },
|
|
68
|
+
localWorker: workerDetails(workerSnapshot(k)), observedAt: new Date().toISOString(),
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
app.post('/api/sys/diagnostics/authorization', async (req) => {
|
|
72
|
+
const operator = req.cageCtx;
|
|
73
|
+
requireOps(operator);
|
|
74
|
+
const parsed = authorization.safeParse(req.body);
|
|
75
|
+
if (!parsed.success)
|
|
76
|
+
throw cageError('INVALID_INPUT', parsed.error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message })));
|
|
77
|
+
const { command, input, userId } = parsed.data;
|
|
78
|
+
const rc = k.registry.command(command);
|
|
79
|
+
if (!rc)
|
|
80
|
+
throw cageError('NOT_FOUND');
|
|
81
|
+
let ctx = operator;
|
|
82
|
+
if (userId && userId !== operator.userId) {
|
|
83
|
+
const user = k.registry.aggregate('User');
|
|
84
|
+
if (!user || !operator.capabilities.has('User.read') || !await loadScoped(k.app, user, userId, operator, false))
|
|
85
|
+
throw cageError('NOT_FOUND');
|
|
86
|
+
const target = await k.contextFor(userId, operator.tenantId);
|
|
87
|
+
if (!target)
|
|
88
|
+
throw cageError('NOT_FOUND');
|
|
89
|
+
ctx = target;
|
|
90
|
+
}
|
|
91
|
+
const agg = rc.cmd.agg;
|
|
92
|
+
// 即使是运维人员,也不能利用解释接口探测其安全读范围以外的业务对象。
|
|
93
|
+
if (rc.cmd.from !== 'new') {
|
|
94
|
+
if (!operator.capabilities.has(`${agg.name}.read`) || !z.uuid().safeParse(input.id).success || !await loadScoped(k.app, agg, input.id, operator, false))
|
|
95
|
+
throw cageError('NOT_FOUND');
|
|
96
|
+
}
|
|
97
|
+
return k.explain(command, input, ctx);
|
|
98
|
+
});
|
|
99
|
+
app.get('/api/sys/diagnostics/timeline', async (req) => {
|
|
100
|
+
const ctx = req.cageCtx;
|
|
101
|
+
requireOps(ctx);
|
|
102
|
+
const parsed = request.safeParse(req.query);
|
|
103
|
+
if (!parsed.success)
|
|
104
|
+
throw cageError('INVALID_INPUT', parsed.error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message })));
|
|
105
|
+
const q = parsed.data;
|
|
106
|
+
if (!q.requestId && !q.outboxId && !(q.aggregate && q.aggregateId))
|
|
107
|
+
throw cageError('INVALID_INPUT', '提供 requestId、outboxId 或 aggregate + aggregateId');
|
|
108
|
+
let events = k.app.selectFrom('sys.outbox as o').select(['id', 'event', 'aggregate', 'aggregate_id', 'request_id', 'status', 'attempts', 'last_error', 'next_at', 'created_at', 'done_at']).where('o.tenant_id', '=', ctx.tenantId);
|
|
109
|
+
if (q.requestId)
|
|
110
|
+
events = events.where('request_id', '=', q.requestId);
|
|
111
|
+
if (q.outboxId)
|
|
112
|
+
events = events.where('id', '=', q.outboxId);
|
|
113
|
+
if (q.aggregate)
|
|
114
|
+
events = events.where('aggregate', '=', q.aggregate);
|
|
115
|
+
if (q.aggregateId)
|
|
116
|
+
events = events.where('aggregate_id', '=', q.aggregateId);
|
|
117
|
+
const readable = k.registry.aggregates.filter((aggregate) => ctx.capabilities.has(`${aggregate.name}.read`));
|
|
118
|
+
if (!readable.length)
|
|
119
|
+
return { observedAt: new Date().toISOString(), truncated: false, events: [], audit: [] };
|
|
120
|
+
// 在 LIMIT 之前过滤权限,既不会漏掉排在不可见事件之后的有效证据,也不泄露不可见事件的截断数量。
|
|
121
|
+
events = events.where((eb) => eb.or(readable.map((aggregate) => eb.and([
|
|
122
|
+
eb('aggregate', '=', aggregate.name),
|
|
123
|
+
eb.exists(k.app.selectFrom(`${aggregate.table.schema}.${aggregate.table.name} as diagnostic_object`).select('diagnostic_object.id')
|
|
124
|
+
.whereRef('diagnostic_object.id', '=', 'o.aggregate_id').where(compileScope(aggregate.scope, aggregate.name, ctx, 'diagnostic_object'))),
|
|
125
|
+
]))));
|
|
126
|
+
const candidates = await events.orderBy('id').limit(201).execute();
|
|
127
|
+
const visible = candidates.slice(0, 200);
|
|
128
|
+
const ids = visible.map((event) => event.id);
|
|
129
|
+
const [processed, calls, dead] = ids.length ? await Promise.all([
|
|
130
|
+
k.app.selectFrom('sys.processed_event').selectAll().where('event_id', 'in', ids).execute(),
|
|
131
|
+
k.app.selectFrom('sys.connector_execution').select(['outbox_id', 'consumer', 'connector', 'operation', 'status', 'attempts', 'error', 'updated_at']).where('tenant_id', '=', ctx.tenantId).where('outbox_id', 'in', ids).execute(),
|
|
132
|
+
k.app.selectFrom('sys.dead_letter').select(['id', 'outbox_id', 'error', 'attempts', 'created_at', 'handled_at', 'resolution']).where('tenant_id', '=', ctx.tenantId).where('outbox_id', 'in', ids).execute(),
|
|
133
|
+
]) : [[], [], []];
|
|
134
|
+
const audit = ctx.capabilities.has('Sys.audit.read') ? await k.read.tx(ctx, async (tx) => {
|
|
135
|
+
if (!ids.length)
|
|
136
|
+
return [];
|
|
137
|
+
let query = tx.selectFrom('safe.audit_log').select(['id', 'at', 'actor', 'actor_kind', 'kind', 'capability', 'aggregate', 'aggregate_id', 'request_id', 'result']);
|
|
138
|
+
const requestIds = [...new Set(visible.map((event) => event.request_id).filter((id) => !!id))];
|
|
139
|
+
if (!requestIds.length)
|
|
140
|
+
return [];
|
|
141
|
+
return query.where('request_id', 'in', requestIds).where((eb) => eb.or(visible.map((event) => eb.and([
|
|
142
|
+
eb('aggregate', '=', event.aggregate), eb('aggregate_id', '=', event.aggregate_id),
|
|
143
|
+
])))).orderBy('at').limit(200).execute();
|
|
144
|
+
}) : [];
|
|
145
|
+
return {
|
|
146
|
+
observedAt: new Date().toISOString(), truncated: candidates.length > 200,
|
|
147
|
+
events: visible.map((event) => {
|
|
148
|
+
const eventCalls = calls.filter((call) => call.outbox_id === event.id);
|
|
149
|
+
// unknown 是持久化的远端结果隔离;重放 outbox 不会解除它,不能沿用普通死信操作建议。
|
|
150
|
+
const nextAction = eventCalls.some((call) => call.status === 'unknown')
|
|
151
|
+
? '连接器远端结果未知,先人工核对;当前无通用解除 unknown 入口,普通死信重放不会解除。需连接器专用受控恢复或业务补偿'
|
|
152
|
+
: event.status === 'dead' ? '检查死信原因后,经 Sys.ops.manage 重放或丢弃' : event.status === 'done' ? '已完成本次要求的消费者' : '等待 worker 处理;检查心跳、重试时间与错误码';
|
|
153
|
+
return { ...event, last_error: failure(event.last_error), source: `/api/sys/ops/outbox`,
|
|
154
|
+
consumers: processed.filter((entry) => entry.event_id === event.id),
|
|
155
|
+
calls: eventCalls.map((call) => ({ ...call, error: failure(call.error) })),
|
|
156
|
+
deadLetters: dead.filter((entry) => entry.outbox_id === event.id).map((entry) => ({ ...entry, error: failure(entry.error) })),
|
|
157
|
+
nextAction,
|
|
158
|
+
};
|
|
159
|
+
}), audit,
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组织授权命令的硬约束集合,供 Role 和 User 命令声明复用。
|
|
3
|
+
* 检查在内核命令流水线中执行,拒绝通过 violation 返回给调用方;约束不直接改角色或用户。
|
|
4
|
+
* 各函数只实现下方明确条件:不能把能力存在性、参数比较和范围宽度检查混称为任意授权策略证明。
|
|
5
|
+
*/
|
|
1
6
|
import { type Constraint } from '@yss123/cage-kernel';
|
|
2
7
|
/** 只能授出能力目录里存在的能力点 */
|
|
8
|
+
/**
|
|
9
|
+
* 构造目录存在性约束。check 使用内核提供的实时目录核对 input.capability,
|
|
10
|
+
* 未知能力返回 CAGE-GRANT-003;该检查不负责判断操作者是否拥有这个能力。
|
|
11
|
+
*/
|
|
3
12
|
export declare const knownCapability: () => Constraint;
|
|
4
13
|
/** 防提权:不能授出自己没有的能力点;数值参数(额度)不能超过自己的 */
|
|
14
|
+
/**
|
|
15
|
+
* 构造直接授权的防提权约束:操作者必须已有目标能力。
|
|
16
|
+
* 输入与操作者参数在同一键上都为 number 时,新值不能更大;其他参数类型不在这里比较。
|
|
17
|
+
* 检查只返回结果,实际替换角色能力列表由 grantCapability handler 完成。
|
|
18
|
+
*/
|
|
5
19
|
export declare const noEscalation: () => Constraint;
|
|
6
20
|
/** 不能授出比自己更宽的数据范围 */
|
|
21
|
+
/**
|
|
22
|
+
* 构造范围宽度约束,阻止设置比操作者更宽的组合子。
|
|
23
|
+
* combinator 为空表示删除已有范围,允许继续;deptSet 的具体部门集合不由本函数逐项比较。
|
|
24
|
+
*/
|
|
7
25
|
export declare const noWiderScope: () => Constraint;
|
|
8
26
|
/** 分配角色也是授权:被分配角色里的每个能力点和范围,操作人自己都必须有 */
|
|
27
|
+
/**
|
|
28
|
+
* 构造新增角色分配的防提权约束。对本次新增角色加载其能力与范围,逐项核对操作者。
|
|
29
|
+
* 已有角色跳过重复检查;不存在的角色留给 handler 返回 ROLE_NOT_FOUND。
|
|
30
|
+
* 这里核对能力标识与范围宽度,不比较每个能力的数值参数,也不实际修改用户角色。
|
|
31
|
+
*/
|
|
9
32
|
export declare const noEscalationViaRoles: () => Constraint;
|