dsh-auto-flow 0.1.5 → 0.1.6
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/lib/client.js +16 -12
- package/lib/index.js +111 -8
- package/package.json +6 -2
package/lib/client.js
CHANGED
|
@@ -7225,12 +7225,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7225
7225
|
* 写 `'*'` 等于广播给任何来源。
|
|
7226
7226
|
*/
|
|
7227
7227
|
function postHostContext(frame, context, targetOrigin) {
|
|
7228
|
-
frame?.contentWindow
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7228
|
+
const target = frame?.contentWindow;
|
|
7229
|
+
if (target === null || target === void 0) return;
|
|
7230
|
+
try {
|
|
7231
|
+
target.postMessage({
|
|
7232
|
+
type: HOST_CONTEXT_MESSAGE_TYPE,
|
|
7233
|
+
theme: context.theme,
|
|
7234
|
+
sessionId: context.sessionId,
|
|
7235
|
+
hostOrigin: context.hostOrigin
|
|
7236
|
+
}, targetOrigin);
|
|
7237
|
+
} catch {}
|
|
7234
7238
|
}
|
|
7235
7239
|
//#endregion
|
|
7236
7240
|
//#region \0dsh-css:C:\Users\Administrator\Desktop\projects\dsh\dsh-plugins\packages\business\dsh-auto-flow\src\client\features\workflow-overlay\WorkflowOverlay.module.css.mjs
|
|
@@ -7244,15 +7248,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7244
7248
|
document.head.appendChild(tag);
|
|
7245
7249
|
}
|
|
7246
7250
|
var WorkflowOverlay_module_css_default = {
|
|
7247
|
-
"frame": "_3sfATW_frame",
|
|
7248
|
-
"notice": "_3sfATW_notice",
|
|
7249
|
-
"noticeActions": "_3sfATW_noticeActions",
|
|
7250
7251
|
"bar": "_3sfATW_bar",
|
|
7252
|
+
"notice": "_3sfATW_notice",
|
|
7253
|
+
"noticeText": "_3sfATW_noticeText",
|
|
7251
7254
|
"dialog": "_3sfATW_dialog",
|
|
7255
|
+
"noticeActions": "_3sfATW_noticeActions",
|
|
7252
7256
|
"layer": "_3sfATW_layer",
|
|
7253
|
-
"
|
|
7254
|
-
"
|
|
7255
|
-
"
|
|
7257
|
+
"noticeReason": "_3sfATW_noticeReason",
|
|
7258
|
+
"frame": "_3sfATW_frame",
|
|
7259
|
+
"stage": "_3sfATW_stage"
|
|
7256
7260
|
};
|
|
7257
7261
|
//#endregion
|
|
7258
7262
|
//#region src/client/features/workflow-overlay/WorkflowOverlay.tsx
|
package/lib/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { zValidator } from "@hono/zod-validator";
|
|
|
22
22
|
import { Hono } from "hono";
|
|
23
23
|
import { bodyLimit } from "hono/body-limit";
|
|
24
24
|
import { HTTPException } from "hono/http-exception";
|
|
25
|
+
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
25
26
|
//#region ../../shared/workflow/src/approval.ts
|
|
26
27
|
/** 审批节点的默认选项:通过(继续)/ 驳回(中止)。 */
|
|
27
28
|
const DEFAULT_APPROVAL_OPTIONS = [{
|
|
@@ -11598,6 +11599,93 @@ function missingParam(issue) {
|
|
|
11598
11599
|
return name === void 0 ? "查询串结构不符合约定" : `缺少查询参数 ${String(name)}`;
|
|
11599
11600
|
}
|
|
11600
11601
|
//#endregion
|
|
11602
|
+
//#region src/host/features/app-unit/unit-token.ts
|
|
11603
|
+
/**
|
|
11604
|
+
* 单元入口的**自有令牌**与同源判据 —— 桌面端能打开面板的原因。
|
|
11605
|
+
*
|
|
11606
|
+
* 【它解决什么】桌面端顶层文档的 origin 是自定义协议(实测控制台里的 `dsh-app://app`),而单元是
|
|
11607
|
+
* `http://127.0.0.1:<端口>`:两者**跨站**。宿主的门靠一枚 `SameSite=Strict` 的签名 cookie 记住认证
|
|
11608
|
+
* (见 `@deepseek-ai/dsh-host-*` 的 `sessionCookie`),而跨站 iframe 里那个 cookie **永远不会被发送**
|
|
11609
|
+
* —— 于是"令牌换 cookie"那一步永远白做,入口每次都是 401。浏览器形态(`dsh web`,顶层也在
|
|
11610
|
+
* `http://127.0.0.1`)没有这个问题:那里同站,cookie 正常。
|
|
11611
|
+
*
|
|
11612
|
+
* 本模块给出两条不依赖 cookie 的判据(合起来由 `gateOf` 用在宿主门之前):
|
|
11613
|
+
* 1. **地址里的令牌**({@link hasUnitToken}):装配点把令牌拼进单元地址,门在**任意路径**上认它。
|
|
11614
|
+
* 宿主的门只在 `GET /` 上认一次令牌、随后 303 丢掉,所以它必须靠 cookie 续命。
|
|
11615
|
+
* 2. **同源请求**({@link isSameOrigin}):单元自己的页面发起的取数与帧内刷新走这一条。
|
|
11616
|
+
*
|
|
11617
|
+
* 【为什么令牌要绑定 authority】`hmac(secret, host)` 里的 host 取自请求的 `Host` 头(`127.0.0.1:<端口>`):
|
|
11618
|
+
* 一个单元地址上的令牌拿到另一个单元上不成立 —— 单元之间不互相开门。
|
|
11619
|
+
*
|
|
11620
|
+
* 【为什么是 HMAC 而不是随机串比对】随机串得存一份;HMAC 只用"每进程一份的 secret"就能按 authority
|
|
11621
|
+
* 现场算出来,比较还走常数时间。secret 不落盘、不进日志,重启即全部失效 —— 与"地址由宿主现发"一致。
|
|
11622
|
+
*/
|
|
11623
|
+
/** 令牌在单元地址里的查询串键名。 */
|
|
11624
|
+
const UNIT_TOKEN_PARAM = "unitToken";
|
|
11625
|
+
/**
|
|
11626
|
+
* 造一份本进程的签名 secret。
|
|
11627
|
+
*
|
|
11628
|
+
* @returns 32 字节随机数(不落盘、不进日志)。
|
|
11629
|
+
*/
|
|
11630
|
+
function createUnitTokenSecret() {
|
|
11631
|
+
return randomBytes(32);
|
|
11632
|
+
}
|
|
11633
|
+
/**
|
|
11634
|
+
* 算某个 authority 的单元令牌。
|
|
11635
|
+
*
|
|
11636
|
+
* @param secret - {@link createUnitTokenSecret} 的产物。
|
|
11637
|
+
* @param authority - 请求的 `Host` 头取值(例如 `127.0.0.1:5174`)。
|
|
11638
|
+
* @returns base64url 文本(URL 安全,可直接放进查询串)。
|
|
11639
|
+
*/
|
|
11640
|
+
function unitTokenOf(secret, authority) {
|
|
11641
|
+
return createHmac("sha256", secret).update(authority).digest("base64url");
|
|
11642
|
+
}
|
|
11643
|
+
/**
|
|
11644
|
+
* 把令牌拼进单元地址。
|
|
11645
|
+
*
|
|
11646
|
+
* @param url - 宿主给的地址(它自己的令牌已经在里面)。
|
|
11647
|
+
* @param secret - 本进程的 secret。
|
|
11648
|
+
* @returns 追加了 `unitToken` 的地址;authority 与其余查询串保持不变。
|
|
11649
|
+
*/
|
|
11650
|
+
function withUnitToken(url, secret) {
|
|
11651
|
+
const parsed = new URL(url);
|
|
11652
|
+
parsed.searchParams.set(UNIT_TOKEN_PARAM, unitTokenOf(secret, parsed.host));
|
|
11653
|
+
return parsed.href;
|
|
11654
|
+
}
|
|
11655
|
+
/**
|
|
11656
|
+
* 这条请求带的令牌对不对。
|
|
11657
|
+
*
|
|
11658
|
+
* @param secret - 本进程的 secret。
|
|
11659
|
+
* @param request - 请求(读 `Host` 与 `url`)。
|
|
11660
|
+
* @returns 地址里的 `unitToken` 与本 authority 的期望值**完全一致**时为 `true`;缺 `Host`、缺令牌、
|
|
11661
|
+
* 长度不符或值不符都是 `false`。
|
|
11662
|
+
*/
|
|
11663
|
+
function hasUnitToken(secret, request) {
|
|
11664
|
+
const authority = request.headers.host;
|
|
11665
|
+
if (authority === void 0 || authority === "") return false;
|
|
11666
|
+
const presented = new URL(request.url ?? "/", "http://unit.invalid").searchParams.get(UNIT_TOKEN_PARAM);
|
|
11667
|
+
if (presented === null) return false;
|
|
11668
|
+
const actual = Buffer.from(presented, "utf8");
|
|
11669
|
+
const expected = Buffer.from(unitTokenOf(secret, authority), "utf8");
|
|
11670
|
+
return actual.byteLength === expected.byteLength && timingSafeEqual(actual, expected);
|
|
11671
|
+
}
|
|
11672
|
+
/**
|
|
11673
|
+
* 这条请求是不是**单元自己的页面**发起的。
|
|
11674
|
+
*
|
|
11675
|
+
* 【为什么这条可以当判据】`Sec-Fetch-Site` 由浏览器自己写,页面里的 JS 不能伪造它;跨站页面发起的
|
|
11676
|
+
* 请求标 `cross-site`,被这一条挡在外面。它覆盖两件"地址里的令牌"覆盖不到的事:单元前端对自己
|
|
11677
|
+
* `/api` 的取数,以及帧内刷新/深链(带令牌的那一次导航之后,地址栏里就没有令牌了)。
|
|
11678
|
+
*
|
|
11679
|
+
* 【已知代价,明写】非浏览器的**本地进程**可以自己伪造这个请求头,而宿主的签名 cookie 伪造不了 ——
|
|
11680
|
+
* 这是为了让桌面端能用而接受的放宽。浏览器侧的跨站攻击面没有变化(那一档本来就进不来)。
|
|
11681
|
+
*
|
|
11682
|
+
* @param request - 请求。
|
|
11683
|
+
* @returns `Sec-Fetch-Site: same-origin` 时为 `true`(其余取值一律 `false`)。
|
|
11684
|
+
*/
|
|
11685
|
+
function isSameOrigin(request) {
|
|
11686
|
+
return request.headers["sec-fetch-site"] === "same-origin";
|
|
11687
|
+
}
|
|
11688
|
+
//#endregion
|
|
11601
11689
|
//#region src/host/features/app-unit/connection.ts
|
|
11602
11690
|
/**
|
|
11603
11691
|
* 读 `connection` 服务。
|
|
@@ -11613,16 +11701,30 @@ function connectionOf(ctx) {
|
|
|
11613
11701
|
return connection;
|
|
11614
11702
|
}
|
|
11615
11703
|
/**
|
|
11616
|
-
* 把平台的门折成 `app-unit` 的 `gate
|
|
11704
|
+
* 把平台的门折成 `app-unit` 的 `gate` —— 并在它**之前**加上我们自己的两条判据。
|
|
11705
|
+
*
|
|
11706
|
+
* 【为什么要加】宿主的门靠一枚 `SameSite=Strict` 的签名 cookie 记住认证;桌面端顶层文档是自定义协议
|
|
11707
|
+
* (实测控制台里的 `dsh-app://app`)而单元在 `http://127.0.0.1:<端口>`:两者跨站,那枚 cookie 在跨站
|
|
11708
|
+
* iframe 里**永远不会被发送** —— 于是"令牌换 cookie"白做,入口每次 401(现象:"dsh web authentication
|
|
11709
|
+
* required",面板打不开)。我们自己的判据(地址里的令牌 / `Sec-Fetch-Site: same-origin`)不依赖 cookie,
|
|
11710
|
+
* 桌面端与浏览器端都成立;判据与已知代价写在 `./unit-token.ts` 的文件头。
|
|
11617
11711
|
*
|
|
11618
|
-
*
|
|
11619
|
-
*
|
|
11712
|
+
* 【为什么保留宿主那两条兜底】没有我们令牌的地址(例如有人直接拿宿主打印的地址访问)仍然按宿主的规则
|
|
11713
|
+
* 走:浏览器形态下 `authorizeIndex` 照旧走"令牌换 cookie",行为与改动前一致。
|
|
11620
11714
|
*/
|
|
11621
|
-
function gateOf(ctx) {
|
|
11715
|
+
function gateOf(ctx, secret) {
|
|
11622
11716
|
const connection = connectionOf(ctx);
|
|
11717
|
+
/** 我们自己的两条判据:命中就不去打扰宿主的门(宿主那条会写响应或作答 401)。 */
|
|
11718
|
+
const ours = (request) => hasUnitToken(secret, request) || isSameOrigin(request);
|
|
11623
11719
|
return {
|
|
11624
|
-
authorizeIndex: (req, res) =>
|
|
11625
|
-
|
|
11720
|
+
authorizeIndex: (req, res) => {
|
|
11721
|
+
if (ours(req)) return true;
|
|
11722
|
+
return connection.authorizeIndex(req, res);
|
|
11723
|
+
},
|
|
11724
|
+
requestRejection: (req) => {
|
|
11725
|
+
if (ours(req)) return void 0;
|
|
11726
|
+
return connection.requestRejection(req);
|
|
11727
|
+
}
|
|
11626
11728
|
};
|
|
11627
11729
|
}
|
|
11628
11730
|
//#endregion
|
|
@@ -11665,15 +11767,16 @@ const WORKSPACE_LIST_TIMEOUT_MS = 1e4;
|
|
|
11665
11767
|
* @throws 装配接口本身形状不对时同步抛出(那是本文件自己的错误,与"端口被占用"这类环境问题不同)。
|
|
11666
11768
|
*/
|
|
11667
11769
|
function mountUnits(ctx, deps) {
|
|
11770
|
+
const secret = createUnitTokenSecret();
|
|
11668
11771
|
return mountAppUnits(ctx, {
|
|
11669
11772
|
pluginDir: pluginDirOf(),
|
|
11670
11773
|
units: APP_UNITS,
|
|
11671
|
-
gate: gateOf(ctx),
|
|
11774
|
+
gate: gateOf(ctx, secret),
|
|
11672
11775
|
bff: () => createEditorHandler({
|
|
11673
11776
|
...deps,
|
|
11674
11777
|
listWorkspaceDir: workspaceDirLister(ctx)
|
|
11675
11778
|
}),
|
|
11676
|
-
authenticate: (origin) => connectionOf(ctx).authenticatedUrl(`${origin}/`),
|
|
11779
|
+
authenticate: (origin) => withUnitToken(connectionOf(ctx).authenticatedUrl(`${origin}/`), secret),
|
|
11677
11780
|
onNotice: (message) => {
|
|
11678
11781
|
ctx.logger.warn("auto-flow: %s", message);
|
|
11679
11782
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-auto-flow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "可视化工作流编辑器与执行引擎:画布编排、脚本节点、人工审批、运行历史,让 dsh 按确定流程跑长任务。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"lib/*.js",
|
|
13
13
|
"lib/*.d.ts",
|
|
14
|
+
"lib/typert.host.js",
|
|
15
|
+
"lib/typert.host.d.ts",
|
|
16
|
+
"lib/typert.remote-client.js",
|
|
17
|
+
"lib/typert.remote-client.d.ts",
|
|
14
18
|
"units/*/dist/**",
|
|
15
19
|
"units/*/host/**",
|
|
16
20
|
"cordis.patch.yml",
|
|
@@ -91,8 +95,8 @@
|
|
|
91
95
|
"app-unit": "0.1.0",
|
|
92
96
|
"script-contract": "0.1.0",
|
|
93
97
|
"host-signal": "0.1.0",
|
|
94
|
-
"workflow": "0.1.0",
|
|
95
98
|
"test-support": "0.1.0",
|
|
99
|
+
"workflow": "0.1.0",
|
|
96
100
|
"workflow-engine": "0.1.0"
|
|
97
101
|
},
|
|
98
102
|
"peerDependencies": {
|