weaver-work-cli 0.1.25 → 0.1.26
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 +30 -30
- package/dist/cmd/root.js +1 -0
- package/dist/core/runtime-agent.js +19 -0
- package/dist/internal/e10/auth/commands.js +47 -27
- package/dist/internal/e10/auth/xiaoe.js +62 -43
- package/dist/internal/e10/context.js +5 -27
- package/dist/shortcuts/archive-oa/constants.js +66 -0
- package/dist/shortcuts/archive-oa/continuation.js +70 -0
- package/dist/shortcuts/archive-oa/errors.js +53 -0
- package/dist/shortcuts/archive-oa/host.js +157 -0
- package/dist/shortcuts/archive-oa/index.js +225 -0
- package/dist/shortcuts/archive-oa/manifest.js +62 -0
- package/dist/shortcuts/archive-oa/operations/borrow-car.js +159 -0
- package/dist/shortcuts/archive-oa/operations/borrow-url.js +108 -0
- package/dist/shortcuts/archive-oa/operations/confirm.js +48 -0
- package/dist/shortcuts/archive-oa/operations/contract.js +120 -0
- package/dist/shortcuts/archive-oa/operations/locate.js +50 -0
- package/dist/shortcuts/archive-oa/operations/registry.js +40 -0
- package/dist/shortcuts/archive-oa/operations/shared.js +181 -0
- package/dist/shortcuts/archive-oa/operations/types.js +45 -0
- package/dist/shortcuts/archive-oa/operations.js +13 -0
- package/dist/shortcuts/index.js +2 -0
- package/dist/shortcuts/invoice/host.js +2 -1
- package/dist/shortcuts/invoice/manifest.js +111 -0
- package/dist/shortcuts/invoice/operations/issuing-qys.js +361 -0
- package/dist/shortcuts/invoice/operations/issuing.js +5 -5
- package/dist/shortcuts/invoice/operations/registry.js +4 -0
- package/dist/shortcuts/workflow/endpoints.js +11 -0
- package/dist/shortcuts/workflow/host.js +2 -2
- package/dist/shortcuts/workflow/index.js +6 -36
- package/dist/shortcuts/workflow/operations/chatscene.js +270 -0
- package/dist/shortcuts/workflow/operations/form.js +899 -158
- package/dist/shortcuts/workflow/operations/read.js +111 -6
- package/dist/shortcuts/workflow/operations/sign-input.js +106 -0
- package/dist/shortcuts/workflow/operations/write.js +9 -1
- package/dist/shortcuts/workflow/stdout-compact.js +9 -1
- package/docs/archive-oa.md +79 -0
- package/docs/e10-auth.md +7 -6
- package/package.json +1 -1
- package/skill-template/business-info.json +16 -0
- package/skill-template/domains/archive-oa.md +19 -0
- package/skill-template/domains/yepiaotong.md +2 -2
- package/skills/weaver-e10-archive-oa/SKILL.md +159 -0
- package/skills/weaver-e10-archive-oa/references/archive-locate.md +49 -0
- package/skills/weaver-e10-archive-oa/references/borrow-car.md +76 -0
- package/skills/weaver-e10-archive-oa/references/borrow-flow.md +53 -0
- package/skills/weaver-e10-archive-oa/references/contract-search.md +63 -0
- package/skills/weaver-e10-archive-oa/references/disabled-capabilities.md +29 -0
- package/skills/weaver-e10-archive-oa/references/qys-contract-search.md +47 -0
- package/skills/weaver-e10-archive-oa/references/source-manifest.json +400 -0
- package/skills/weaver-e10-builder/SKILL.md +22 -0
- package/skills/weaver-e10-caizhilian/SKILL.md +37 -15
- package/skills/weaver-e10-caizhilian/references/agent-entry.md +7 -3
- package/skills/weaver-e10-caizhilian/references/collaboration.md +5 -3
- package/skills/weaver-e10-caizhilian/references/document.md +33 -11
- package/skills/weaver-e10-caizhilian/references/file-collection.md +4 -3
- package/skills/weaver-e10-caizhilian/references/file-upload.md +2 -3
- package/skills/weaver-e10-caizhilian/references/files.md +9 -3
- package/skills/weaver-e10-caizhilian/references/folder.md +48 -16
- package/skills/weaver-e10-caizhilian/references/setting-attach.md +13 -39
- package/skills/weaver-e10-caizhilian/references/setting-backup.md +13 -51
- package/skills/weaver-e10-caizhilian/references/setting-encrypt.md +20 -51
- package/skills/weaver-e10-caizhilian/references/setting-storage.md +82 -147
- package/skills/weaver-e10-caizhilian/references/withheld.md +20 -44
- package/skills/weaver-e10-shared/SKILL.md +22 -20
- package/skills/weaver-e10-shared/references/e10-auth-and-session.md +14 -15
- package/skills/weaver-e10-shared/references/non-interactive-environment.md +10 -10
- package/skills/weaver-e10-workflow/references/source-manifest.json +32 -22
- package/skills/weaver-e10-workflow/references/workflow-create.md +68 -28
- package/skills/weaver-e10-yepiaotong/SKILL.md +10 -7
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-examples.md +49 -1
- package/skills/weaver-e10-yepiaotong/references/invoice-issuing-qys-ledger.md +66 -0
- package/skills/weaver-e10-yepiaotong/references/invoice-reim.md +59 -36
- package/skills/weaver-e10-yepiaotong/references/reim-api-reference.md +54 -509
- package/skills/weaver-e10-yepiaotong/references/reim-examples.md +182 -345
- package/skills/weaver-e10-yepiaotong/references/reim-expense-subject-rules.md +4 -5
- package/skills/weaver-e10-yepiaotong/references/reim-form-fill-rules.md +153 -345
- package/skills/weaver-e10-yepiaotong/references/reim-phone-subsidy-rules.md +5 -5
- package/skills/weaver-e10-yepiaotong/references/reim-workflow.md +182 -154
- package/skills/weaver-e10-yepiaotong/references/source-manifest.json +96 -0
- package/skills/weaver-e10-yepiaotong/references/reim-fast-path.md +0 -119
package/README.md
CHANGED
|
@@ -211,41 +211,41 @@ weaver-work-cli auth login --base-url "https://weapp.xxx.cn"
|
|
|
211
211
|
weaver-work-cli auth oidc --base-url "https://weapp.xxx.cn"
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
`xiaoe_env.py` / `tokens.py` 相同的换票接口,并校验业务码和 `data.eteamId`;它保留
|
|
218
|
-
系统 CA 校验,且只允许同源重定向或同主机 HTTP→HTTPS 升级。平台凭证不可用时,
|
|
219
|
-
CLI 会先在内部调用 npm 包自带的 `scripts/xiaoe_env.py` 兼容路径,再按顺序尝试
|
|
220
|
-
宿主 `xiaoeUserData/tokens.py`、桌面 XiaoE 客户端当前会话
|
|
221
|
-
(优先读取宿主注入的 `XIAOE_USER_SETTINGS_PATH`,其次查找
|
|
222
|
-
`XIAOE_ROBOT_DATA_ROOT` / `XIAOE_ROBOT_ROOT`,最后回退
|
|
223
|
-
`XiaoE/Robots/<id>/user_settings.json`)和 `WEAVER_XIAOE_*` 环境映射。只要会话文件中的
|
|
224
|
-
`session_id` 与 `erpa` 有效,Node 路径即可直接换票,不要求宿主存在 `tokens.py`。
|
|
225
|
-
`xiaoe_env.py` 由 CLI 独占执行并解析,不放进 shared Skill,也不把 stdout 凭证暴露给
|
|
226
|
-
Agent;Skill 只负责调用 `weaver-work-cli auth xiaoe`。CLI 自带脚本仅依赖 Python
|
|
227
|
-
标准库,仍保持 TLS 证书校验,不会采用关闭证书校验的回退。
|
|
228
|
-
|
|
229
|
-
XiaoE 桌面客户端不一定注入上述 Token 对;它会注入 `XIAOE_DATA_ROOT`、
|
|
230
|
-
`XIAOE_ROBOT_DATA_ROOT`、`XIAOE_USER_SETTINGS_PATH`、`XIAOE_ROBOT_ROOT` 等精确的
|
|
231
|
-
运行时路径标记,CLI 也把这些标记识别为当前 XiaoE 桌面宿主。普通 `XIAOE_SKILL_*`、
|
|
232
|
-
`WEAVER_XIAOE_*` 配置或仅在磁盘上发现客户端文件仍不构成宿主身份。
|
|
233
|
-
|
|
234
|
-
E10 目标地址按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*` E10 配置/映射 →
|
|
235
|
-
当前 profile → `https://www.e-cology.com.cn`”解析,绝不会把 `XIAOE_DOMAIN`
|
|
236
|
-
当作 E10 地址。匹配成功时直接保存登录态,并固定使用 `agentType=XiaoE`。业务命令
|
|
237
|
-
在已确认的 XiaoE 智能体环境中且未显式指定 `--auth` / `--profile` 时,也会自动优先尝试 XiaoE 统一登录。
|
|
238
|
-
`auth xiaoe` 失败后仅在 macOS/Windows 桌面宿主自动降级浏览器 OAuth;Web/Linux
|
|
239
|
-
环境会返回结构化的 `authentication/xiaoe_web_credential_unavailable` 错误,不再
|
|
240
|
-
启动无法回调的 localhost 浏览器登录。确需浏览器授权时,应在可接收本地回调的宿主
|
|
241
|
-
显式运行 `auth login`。也可以显式使用:
|
|
214
|
+
XiaoE 环境保留两种识别入口:桌面客户端由 Agent 根据宿主/任务的明确上下文判断,并在
|
|
215
|
+
业务命令上传 `--runtime-agent xiaoe`;网页端沿用原有环境变量规则,成对的
|
|
216
|
+
`XIAOE_USER_TOKEN` + `XIAOE_DOMAIN` 会被识别为 XiaoE。判定为 XiaoE 后,Agent 显式执行:
|
|
242
217
|
|
|
243
218
|
```text
|
|
244
219
|
weaver-work-cli auth xiaoe
|
|
245
220
|
```
|
|
246
221
|
|
|
247
|
-
`auth
|
|
248
|
-
|
|
222
|
+
`auth xiaoe` 本身就是认证方式的显式选择。`hasXiaoEEnv` 通过桌面端显式参数或网页端原有
|
|
223
|
+
环境变量规则判断;工作目录、客户端文件或历史登录仍不作为依据。CLI 按顺序消费平台凭据、CLI 自带兼容脚本、宿主 `tokens.py`、
|
|
224
|
+
桌面 XiaoE 客户端会话和 `WEAVER_XIAOE_*` 映射。`XIAOE_DOMAIN` 是 XiaoE/erpa 换票
|
|
225
|
+
服务地址,不是 E10 地址。E10 目标按“显式 `--base-url` → 专用 `WEAVER_XIAOE_*`
|
|
226
|
+
E10 配置/映射 → 当前 profile → `https://www.e-cology.com.cn`”解析。
|
|
227
|
+
|
|
228
|
+
统一登录失败时,所有操作系统都返回结构化的
|
|
229
|
+
`authentication/xiaoe_credential_unavailable`,不会自动打开浏览器或降级到
|
|
230
|
+
`auth login`。确需浏览器授权时,调用方必须另行显式执行 `auth login`。
|
|
231
|
+
|
|
232
|
+
业务命令不会自动建立 XiaoE 登录态。桌面客户端需要 XiaoE 专属业务行为时,把全局参数
|
|
233
|
+
放在业务子命令之前,例如:
|
|
234
|
+
|
|
235
|
+
```text
|
|
236
|
+
weaver-work-cli --runtime-agent xiaoe workflow ...
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
网页端不要求补这个参数,仍由环境变量规则识别。`--runtime-agent` 只表示当前运行时类型,
|
|
240
|
+
不携带也不选择凭据。`auth login` / `auth oidc`
|
|
241
|
+
始终是纯浏览器 OAuth 入口,不会读取 XiaoE 凭据。
|
|
242
|
+
|
|
243
|
+
`auth xiaoe` 会先输出不含凭证的 preflight 摘要,显示 `authMode=xiaoe`、
|
|
244
|
+
`credentialSourceDetected`、`attempted` 及各凭据源检测结果;命中 `tokens.py` 或内置
|
|
245
|
+
Node 客户端会话路径时还会输出明确的尝试日志。统一登录成功后,登录结果以及后续
|
|
246
|
+
`auth status` 会显示 `credentialSourceStep`、`credentialSourceType`、
|
|
247
|
+
`credentialSourceLabel`,可据此确认实际凭证来源;`3 / xiaoe_client / XiaoE client settings`
|
|
248
|
+
表示未依赖 `tokens.py`,由 Node 直接换票。
|
|
249
249
|
|
|
250
250
|
默认本地回调地址为 `http://127.0.0.1:19800/callback`。如果端口被占用:
|
|
251
251
|
|
package/dist/cmd/root.js
CHANGED
|
@@ -29,6 +29,7 @@ export function createProgram() {
|
|
|
29
29
|
.option('--config <path>', 'use a custom weaver-work-cli config file')
|
|
30
30
|
.option('--auth <path>', 'use a specific E10 auth file')
|
|
31
31
|
.option('--profile <name>', 'use a specific E10 profile')
|
|
32
|
+
.option('--runtime-agent <name>', 'identify the current agent runtime (for example: xiaoe)')
|
|
32
33
|
.option('--is-connector [value]', 'run as the WorkBuddy connector: use the fixed E10 profile managed by the connector')
|
|
33
34
|
.option('--base-url <url>', 'override E10 base URL')
|
|
34
35
|
.option('--passport-url <url>', 'override E10 passport URL');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const XIAOE_RUNTIME_AGENT_ALIASES = new Set([
|
|
2
|
+
'xiaoe',
|
|
3
|
+
'xiaoeclient',
|
|
4
|
+
'小e',
|
|
5
|
+
'小e客户端',
|
|
6
|
+
'智能小e',
|
|
7
|
+
'智能小e客户端',
|
|
8
|
+
]);
|
|
9
|
+
export function normalizeRuntimeAgent(value) {
|
|
10
|
+
if (typeof value !== 'string')
|
|
11
|
+
return '';
|
|
12
|
+
return value.toLowerCase().replace(/[^a-z0-9\p{Script=Han}]+/gu, '');
|
|
13
|
+
}
|
|
14
|
+
export function isXiaoERuntimeAgent(value) {
|
|
15
|
+
return XIAOE_RUNTIME_AGENT_ALIASES.has(normalizeRuntimeAgent(value));
|
|
16
|
+
}
|
|
17
|
+
export function isXiaoERuntime(options) {
|
|
18
|
+
return isXiaoERuntimeAgent(options?.runtimeAgent);
|
|
19
|
+
}
|
|
@@ -5,8 +5,8 @@ import { CliError, ExitCode } from '../../../core/errors.js';
|
|
|
5
5
|
import { clearInvoiceTokenForSession } from '../../../shortcuts/invoice/host.js';
|
|
6
6
|
import { buildE10CookieHeader, E10AuthSession, clearProfileAuthFile, extractRawSetCookie, extractSessionTicket, findAuthFile, getActiveProfile, getProfileAuthPath, getProfileDir, listProfiles, profileNameFromUrl, readProfileConfig, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, switchActiveProfile, } from './session.js';
|
|
7
7
|
import { getE10AuthRoot } from './paths.js';
|
|
8
|
-
import { XIAOE_AGENT_TYPE, createXiaoEUnifiedLoginUnavailableError,
|
|
9
|
-
export { DEFAULT_E10_BASE_URL, XIAOE_AGENT_TYPE, hasXiaoEEnv, hasXiaoETokenScript, resolveXiaoEDefaultBaseUrl, resolveXiaoEPreferredBaseUrl, resolveXiaoEUnifiedLoginCredential,
|
|
8
|
+
import { XIAOE_AGENT_TYPE, createXiaoEUnifiedLoginUnavailableError, hasXiaoEEnv, inspectXiaoEUnifiedLoginSources, resolveXiaoEPreferredBaseUrl, tryXiaoEUnifiedLogin as tryXiaoEUnifiedSessionLogin, } from './xiaoe.js';
|
|
9
|
+
export { DEFAULT_E10_BASE_URL, XIAOE_AGENT_TYPE, hasXiaoEEnv, hasXiaoETokenScript, hasXiaoEUnifiedLoginSource, inspectXiaoEUnifiedLoginSources, resolveXiaoEDefaultBaseUrl, resolveXiaoEPreferredBaseUrl, resolveXiaoEUnifiedLoginCredential, } from './xiaoe.js';
|
|
10
10
|
const DEFAULT_AGENT_TYPE = 'weaver-work-cli';
|
|
11
11
|
const EXPLICIT_AGENT_TYPE_ENV_KEYS = [
|
|
12
12
|
'WORKBUDDY_AGENT_TYPE',
|
|
@@ -480,13 +480,16 @@ async function oidcExchange(code, redirectUri, baseUrl, tenantKeyFallback, agent
|
|
|
480
480
|
version,
|
|
481
481
|
};
|
|
482
482
|
}
|
|
483
|
-
function xiaoEUnifiedLoginUnavailableMessage(baseUrl,
|
|
483
|
+
function xiaoEUnifiedLoginUnavailableMessage(baseUrl, attempted) {
|
|
484
484
|
return [
|
|
485
|
-
|
|
486
|
-
? '
|
|
487
|
-
: '
|
|
485
|
+
attempted
|
|
486
|
+
? 'XiaoE 统一登录已尝试,但未获取到可用会话。'
|
|
487
|
+
: '未检测到可用的 XiaoE 统一登录凭据源,本次未尝试统一登录。',
|
|
488
|
+
'`auth xiaoe` 不会降级到浏览器 OAuth。',
|
|
488
489
|
baseUrl ? `当前 E10 baseUrl: ${baseUrl}。` : '未从 XiaoE 凭证中解析到 E10 baseUrl。',
|
|
489
|
-
|
|
490
|
+
attempted
|
|
491
|
+
? 'CLI 已依次尝试平台注入凭证的 Node 换票、CLI 内置 xiaoe_env.py 兼容换票、宿主 xiaoeUserData/tokens.py、桌面 XiaoE 客户端凭证(XiaoE/Robots/*/user_settings.json)和 WEAVER_XIAOE_* 环境映射。'
|
|
492
|
+
: 'CLI 已检查平台注入凭证、宿主 xiaoeUserData/tokens.py、桌面 XiaoE 客户端凭证(XiaoE/Robots/*/user_settings.json)和 WEAVER_XIAOE_* 环境映射。',
|
|
490
493
|
'CLI 会优先使用 XiaoE 客户端内置 Python 执行 tokens.py;自动发现不可用时,可用 WEAVER_XIAOE_TOKEN_SCRIPT_PYTHON 指定兼容解释器。',
|
|
491
494
|
].join(' ');
|
|
492
495
|
}
|
|
@@ -496,30 +499,49 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
|
|
|
496
499
|
const profileFromGlobal = globalOptions.profile;
|
|
497
500
|
const cfg = readProfileConfig(profileFromGlobal || getActiveProfile());
|
|
498
501
|
const explicitBaseUrl = optionString(commandOpts, 'baseUrl') || globalOptions.baseUrl || '';
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
502
|
+
const preferredXiaoEBaseUrl = resolveXiaoEPreferredBaseUrl({
|
|
503
|
+
explicitBaseUrl,
|
|
504
|
+
profileBaseUrl: cfg.baseUrl,
|
|
505
|
+
env: ctx.env,
|
|
506
|
+
});
|
|
507
|
+
const xiaoESources = behavior.tryXiaoEUnifiedLogin === true
|
|
508
|
+
? inspectXiaoEUnifiedLoginSources({
|
|
505
509
|
env: ctx.env,
|
|
510
|
+
baseUrl: preferredXiaoEBaseUrl,
|
|
511
|
+
profileBaseUrl: cfg.baseUrl,
|
|
512
|
+
allowTokenScript: behavior.allowXiaoETokenScript === true,
|
|
506
513
|
})
|
|
514
|
+
: {
|
|
515
|
+
platformCredential: false,
|
|
516
|
+
tokenScript: false,
|
|
517
|
+
clientCredential: false,
|
|
518
|
+
environmentCredential: false,
|
|
519
|
+
any: false,
|
|
520
|
+
};
|
|
521
|
+
const useXiaoELogin = behavior.tryXiaoEUnifiedLogin === true && xiaoESources.any;
|
|
522
|
+
const baseUrl = behavior.tryXiaoEUnifiedLogin
|
|
523
|
+
? preferredXiaoEBaseUrl
|
|
507
524
|
: normalizeUrl(explicitBaseUrl || cfg.baseUrl || '');
|
|
508
525
|
const tenantKey = optionString(commandOpts, 'tenantKey');
|
|
509
526
|
const portText = optionString(commandOpts, 'port') || '19800';
|
|
510
527
|
const port = Number.parseInt(portText, 10);
|
|
511
528
|
const explicitAgentType = optionString(commandOpts, 'agentType', 'agent_type');
|
|
512
|
-
const agentType =
|
|
529
|
+
const agentType = useXiaoELogin
|
|
530
|
+
? optionString(commandOpts, 'agentType', 'agent_type') || XIAOE_AGENT_TYPE
|
|
531
|
+
: resolveAgentType(commandOpts, ctx.env);
|
|
513
532
|
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
514
533
|
throw new CliError('Invalid --port value', ExitCode.UsageError);
|
|
515
534
|
}
|
|
516
535
|
if (behavior.tryXiaoEUnifiedLogin) {
|
|
517
536
|
ctx.logger.info([
|
|
518
537
|
'XiaoE unified login preflight:',
|
|
519
|
-
|
|
538
|
+
'authMode=xiaoe',
|
|
539
|
+
`credentialSourceDetected=${String(xiaoESources.any)}`,
|
|
520
540
|
`attempted=${String(useXiaoELogin)}`,
|
|
521
|
-
`
|
|
522
|
-
`
|
|
541
|
+
`platformCredentialDetected=${String(xiaoESources.platformCredential)}`,
|
|
542
|
+
`tokenScriptDetected=${String(xiaoESources.tokenScript)}`,
|
|
543
|
+
`clientSettingsDetected=${String(xiaoESources.clientCredential)}`,
|
|
544
|
+
`environmentCredentialDetected=${String(xiaoESources.environmentCredential)}`,
|
|
523
545
|
`baseUrl=${baseUrl || '(unresolved)'}`,
|
|
524
546
|
].join(' '));
|
|
525
547
|
}
|
|
@@ -528,11 +550,9 @@ async function runBrowserLogin(ctx, options, behavior = {}) {
|
|
|
528
550
|
if (xiaoEResult)
|
|
529
551
|
return xiaoEResult;
|
|
530
552
|
}
|
|
531
|
-
const willOpenBrowser =
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (useXiaoELogin && behavior.explainXiaoEFallback) {
|
|
535
|
-
ctx.logger.warn(xiaoEUnifiedLoginUnavailableMessage(baseUrl, willOpenBrowser));
|
|
553
|
+
const willOpenBrowser = behavior.browserFallback !== 'never';
|
|
554
|
+
if (behavior.tryXiaoEUnifiedLogin && behavior.explainXiaoEFallback) {
|
|
555
|
+
ctx.logger.warn(xiaoEUnifiedLoginUnavailableMessage(baseUrl, useXiaoELogin));
|
|
536
556
|
}
|
|
537
557
|
if (!willOpenBrowser) {
|
|
538
558
|
throw createXiaoEUnifiedLoginUnavailableError(baseUrl);
|
|
@@ -714,15 +734,15 @@ function formatWhoami(title, data) {
|
|
|
714
734
|
].join('\n');
|
|
715
735
|
}
|
|
716
736
|
function registerBrowserLoginCommand(auth, action, name, description, behavior = {}) {
|
|
717
|
-
auth.command(name)
|
|
737
|
+
const command = auth.command(name)
|
|
718
738
|
.description(description)
|
|
719
739
|
.option('--base-url <url>', 'E10 base URL')
|
|
720
740
|
.option('--tenant-key <key>', 'Tenant key fallback after login')
|
|
721
741
|
.option('--port <n>', 'Local OAuth callback port', '19800')
|
|
722
742
|
.option('--agent_type <value>', 'Agent type sent to E10 authorize endpoint')
|
|
723
743
|
.option('--agent-type <value>', 'Alias of --agent_type')
|
|
724
|
-
.option('--agent-return-url <url>', 'Desktop application URL linked by the login success page')
|
|
725
|
-
|
|
744
|
+
.option('--agent-return-url <url>', 'Desktop application URL linked by the login success page');
|
|
745
|
+
command.action(action(async (ctx, options) => {
|
|
726
746
|
const result = await runBrowserLogin(ctx, options, behavior);
|
|
727
747
|
ctx.output.write(result, { text: formatLoginResult(result) });
|
|
728
748
|
}));
|
|
@@ -808,11 +828,11 @@ export function registerE10AuthCommands(parent, action) {
|
|
|
808
828
|
}));
|
|
809
829
|
registerBrowserLoginCommand(auth, action, 'login', 'Open browser OAuth2.0 login and save E10 auth');
|
|
810
830
|
registerBrowserLoginCommand(auth, action, 'oidc', 'Alias of auth login for OAuth2.0 browser login');
|
|
811
|
-
registerBrowserLoginCommand(auth, action, 'xiaoe', '
|
|
831
|
+
registerBrowserLoginCommand(auth, action, 'xiaoe', 'Use XiaoE unified login without browser OAuth fallback', {
|
|
812
832
|
tryXiaoEUnifiedLogin: true,
|
|
813
833
|
allowXiaoETokenScript: true,
|
|
814
834
|
explainXiaoEFallback: true,
|
|
815
|
-
browserFallback: '
|
|
835
|
+
browserFallback: 'never',
|
|
816
836
|
});
|
|
817
837
|
auth.command('logout')
|
|
818
838
|
.alias('clear')
|
|
@@ -4,6 +4,7 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { CliError, ExitCode } from '../../../core/errors.js';
|
|
7
|
+
import { isXiaoERuntime, isXiaoERuntimeAgent } from '../../../core/runtime-agent.js';
|
|
7
8
|
import { buildE10CookieHeader, E10AuthSession, extractRawSetCookie, extractSessionTicket, profileNameFromUrl, readTeamsCheckUserInfo, saveAuthFile, saveProfileConfig, setActiveProfile, } from './session.js';
|
|
8
9
|
export const DEFAULT_E10_BASE_URL = 'https://www.e-cology.com.cn';
|
|
9
10
|
export const XIAOE_AGENT_TYPE = 'XiaoE';
|
|
@@ -52,12 +53,6 @@ const XIAOE_CLIENT_SETTINGS_ROOT_ENV_KEYS = [
|
|
|
52
53
|
'XIAOE_ROBOT_DATA_ROOT',
|
|
53
54
|
'XIAOE_ROBOT_ROOT',
|
|
54
55
|
];
|
|
55
|
-
const XIAOE_DESKTOP_HOST_ENV_KEYS = [
|
|
56
|
-
'XIAOE_DATA_ROOT',
|
|
57
|
-
'XIAOE_ROBOT_DATA_ROOT',
|
|
58
|
-
'XIAOE_USER_SETTINGS_PATH',
|
|
59
|
-
'XIAOE_ROBOT_ROOT',
|
|
60
|
-
];
|
|
61
56
|
export const XIAOE_SKILL_ETEAMID_API_PATH = '/spec-apps/api/app/rpachat/skill/extend/eteamId';
|
|
62
57
|
export const XIAOE_CLIENT_SETTINGS_SOURCE = 'XiaoE/Robots/user_settings.json';
|
|
63
58
|
export const XIAOE_USER_TOKEN_SOURCE = 'XIAOE_USER_TOKEN';
|
|
@@ -70,36 +65,29 @@ function envString(env, ...keys) {
|
|
|
70
65
|
}
|
|
71
66
|
return '';
|
|
72
67
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// launches the CLI through a shared agent runtime.
|
|
81
|
-
const explicitHostAgentType = envString(env, 'WORKBUDDY_AGENT_TYPE', 'CODEX_AGENT_TYPE');
|
|
82
|
-
if (explicitHostAgentType)
|
|
83
|
-
return isXiaoEAgentType(explicitHostAgentType);
|
|
84
|
-
// XiaoE injects this pair into the command process for the current run. A
|
|
85
|
-
// tokens.py path, client installation, XIAOE_SKILL_* variable, or
|
|
86
|
-
// WEAVER_XIAOE_* configuration alone is credential availability, not proof
|
|
87
|
-
// that the current agent is XiaoE.
|
|
88
|
-
if (hasXiaoEPlatformCredential(env))
|
|
68
|
+
/**
|
|
69
|
+
* Identifies the XiaoE runtime without inspecting credential artifacts:
|
|
70
|
+
* desktop callers may pass --runtime-agent, and any injected XIAOE_* variable
|
|
71
|
+
* is treated as XiaoE host evidence.
|
|
72
|
+
*/
|
|
73
|
+
export function hasXiaoEEnv(env = process.env, options = {}) {
|
|
74
|
+
if (isXiaoERuntime(options))
|
|
89
75
|
return true;
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
// and merely discovering local XiaoE files still do not.
|
|
94
|
-
if (XIAOE_DESKTOP_HOST_ENV_KEYS.some((key) => Boolean(envString(env, key))))
|
|
76
|
+
// XiaoE reserves this prefix for host-injected runtime variables. Presence
|
|
77
|
+
// of any matching key is sufficient; the value may legitimately be empty.
|
|
78
|
+
if (Object.keys(env).some((key) => key.startsWith('XIAOE_')))
|
|
95
79
|
return true;
|
|
96
|
-
//
|
|
97
|
-
|
|
80
|
+
// An explicitly named host remains authoritative over credential signals.
|
|
81
|
+
const explicitHostAgentType = envString(env, 'WORKBUDDY_AGENT_TYPE', 'CODEX_AGENT_TYPE');
|
|
82
|
+
if (explicitHostAgentType)
|
|
83
|
+
return isXiaoERuntimeAgent(explicitHostAgentType);
|
|
84
|
+
// Generic framework markers can coexist with stale profile metadata, so they
|
|
85
|
+
// take precedence over WEAVER_AGENT_TYPE when no XIAOE_* variable is present.
|
|
98
86
|
if (Object.keys(env).some((key) => key.startsWith('WORKBUDDY_') || key.startsWith('CODEX_'))) {
|
|
99
87
|
return false;
|
|
100
88
|
}
|
|
101
89
|
const explicitAgentType = envString(env, 'WEAVER_AGENT_TYPE');
|
|
102
|
-
return explicitAgentType ?
|
|
90
|
+
return explicitAgentType ? isXiaoERuntimeAgent(explicitAgentType) : false;
|
|
103
91
|
}
|
|
104
92
|
export function describeXiaoECredentialSource(source) {
|
|
105
93
|
// Step 1 is the platform-injected credential (XIAOE_USER_TOKEN + XIAOE_DOMAIN):
|
|
@@ -311,7 +299,7 @@ export function resolveXiaoEDefaultBaseUrl(env = process.env) {
|
|
|
311
299
|
const configured = resolveXiaoEConfiguredBaseUrl(env);
|
|
312
300
|
if (configured)
|
|
313
301
|
return configured;
|
|
314
|
-
return
|
|
302
|
+
return DEFAULT_E10_BASE_URL;
|
|
315
303
|
}
|
|
316
304
|
function uniqueStrings(values) {
|
|
317
305
|
const seen = new Set();
|
|
@@ -350,10 +338,12 @@ export function xiaoEBundledEnvScriptPath() {
|
|
|
350
338
|
const scriptPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../scripts/xiaoe_env.py');
|
|
351
339
|
return existsSync(scriptPath) ? scriptPath : '';
|
|
352
340
|
}
|
|
353
|
-
export function hasXiaoETokenScript(env = process.env) {
|
|
341
|
+
export function hasXiaoETokenScript(env = process.env, allowLocalDiscovery = true) {
|
|
354
342
|
const explicit = envString(env, XIAOE_TOKEN_SCRIPT_ENV_KEY, XIAOE_TOKEN_SCRIPT_LEGACY_ENV_KEY);
|
|
355
343
|
if (explicit)
|
|
356
344
|
return existsSync(explicit);
|
|
345
|
+
if (!allowLocalDiscovery)
|
|
346
|
+
return false;
|
|
357
347
|
const scriptPath = xiaoETokenScriptPath(env);
|
|
358
348
|
return Boolean(scriptPath && existsSync(scriptPath));
|
|
359
349
|
}
|
|
@@ -620,7 +610,7 @@ function sortedXiaoEClientSettingsFiles(root) {
|
|
|
620
610
|
const files = listXiaoEClientSettingsFiles(root);
|
|
621
611
|
return files.sort((left, right) => statSync(right).mtimeMs - statSync(left).mtimeMs);
|
|
622
612
|
}
|
|
623
|
-
function xiaoEClientSettingsCandidateFiles(env) {
|
|
613
|
+
function xiaoEClientSettingsCandidateFiles(env, allowLocalDiscovery = true) {
|
|
624
614
|
const candidates = [];
|
|
625
615
|
const explicitSettingsPath = envString(env, XIAOE_USER_SETTINGS_PATH_ENV_KEY);
|
|
626
616
|
if (isFile(explicitSettingsPath))
|
|
@@ -629,7 +619,9 @@ function xiaoEClientSettingsCandidateFiles(env) {
|
|
|
629
619
|
const injectedRoot = envString(env, key);
|
|
630
620
|
candidates.push(...sortedXiaoEClientSettingsFiles(injectedRoot));
|
|
631
621
|
}
|
|
632
|
-
|
|
622
|
+
if (allowLocalDiscovery) {
|
|
623
|
+
candidates.push(...sortedXiaoEClientSettingsFiles(xiaoEClientRobotsRoot(env)));
|
|
624
|
+
}
|
|
633
625
|
return uniqueStrings(candidates);
|
|
634
626
|
}
|
|
635
627
|
export function findXiaoEClientSettingsFile(env = process.env) {
|
|
@@ -674,8 +666,38 @@ export function readXiaoEClientCredential(env = process.env) {
|
|
|
674
666
|
}
|
|
675
667
|
return null;
|
|
676
668
|
}
|
|
677
|
-
export function hasXiaoEClientCredential(env = process.env) {
|
|
678
|
-
return Boolean(
|
|
669
|
+
export function hasXiaoEClientCredential(env = process.env, allowLocalDiscovery = true) {
|
|
670
|
+
return Boolean(xiaoEClientSettingsCandidateFiles(env, allowLocalDiscovery)[0]);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Reports usable unified-login credential sources independently from host
|
|
674
|
+
* identity. Installing the XiaoE client is not proof that the current process
|
|
675
|
+
* was launched by XiaoE, but an explicit `auth xiaoe` command may still consume
|
|
676
|
+
* any credential source that is available.
|
|
677
|
+
*/
|
|
678
|
+
export function inspectXiaoEUnifiedLoginSources(options = {}) {
|
|
679
|
+
const env = options.env || process.env;
|
|
680
|
+
const baseUrl = resolveXiaoEPreferredBaseUrl({
|
|
681
|
+
explicitBaseUrl: options.baseUrl,
|
|
682
|
+
profileBaseUrl: options.profileBaseUrl,
|
|
683
|
+
env,
|
|
684
|
+
});
|
|
685
|
+
const platformCredential = hasXiaoEPlatformCredential(env);
|
|
686
|
+
const allowLocalDiscovery = options.allowLocalDiscovery !== false;
|
|
687
|
+
const tokenScript = options.allowTokenScript !== false
|
|
688
|
+
&& hasXiaoETokenScript(env, allowLocalDiscovery);
|
|
689
|
+
const clientCredential = hasXiaoEClientCredential(env, allowLocalDiscovery);
|
|
690
|
+
const environmentCredential = Boolean(baseUrl && resolveXiaoEUnifiedLoginCredential(baseUrl, env));
|
|
691
|
+
return {
|
|
692
|
+
platformCredential,
|
|
693
|
+
tokenScript,
|
|
694
|
+
clientCredential,
|
|
695
|
+
environmentCredential,
|
|
696
|
+
any: platformCredential || tokenScript || clientCredential || environmentCredential,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
export function hasXiaoEUnifiedLoginSource(options = {}) {
|
|
700
|
+
return inspectXiaoEUnifiedLoginSources(options).any;
|
|
679
701
|
}
|
|
680
702
|
async function fetchXiaoEClientEteamId(credential, domain) {
|
|
681
703
|
const controller = new AbortController();
|
|
@@ -769,22 +791,19 @@ export function resolveXiaoEPreferredBaseUrl(options) {
|
|
|
769
791
|
const profileBaseUrl = normalizeUrl(options.profileBaseUrl || '');
|
|
770
792
|
if (profileBaseUrl)
|
|
771
793
|
return profileBaseUrl;
|
|
772
|
-
return
|
|
773
|
-
}
|
|
774
|
-
export function shouldUseXiaoEBrowserFallback(platform = process.platform) {
|
|
775
|
-
return platform === 'darwin' || platform === 'win32';
|
|
794
|
+
return DEFAULT_E10_BASE_URL;
|
|
776
795
|
}
|
|
777
796
|
export function createXiaoEUnifiedLoginUnavailableError(baseUrl = '') {
|
|
778
797
|
const target = normalizeUrl(baseUrl);
|
|
779
798
|
const message = [
|
|
780
799
|
'XiaoE unified login did not return a usable E10 session.',
|
|
781
800
|
target ? `Resolved E10 baseUrl: ${target}.` : 'No E10 baseUrl could be resolved.',
|
|
782
|
-
'
|
|
801
|
+
'The auth xiaoe command never falls back to browser OAuth.',
|
|
783
802
|
'Refresh the XiaoE web session or restore host credential injection before retrying.',
|
|
784
803
|
].join(' ');
|
|
785
804
|
return Object.assign(new CliError(message, ExitCode.AuthError), {
|
|
786
805
|
type: 'authentication',
|
|
787
|
-
subtype: '
|
|
806
|
+
subtype: 'xiaoe_credential_unavailable',
|
|
788
807
|
retryable: false,
|
|
789
808
|
stage: 'xiaoe_unified_login',
|
|
790
809
|
});
|
|
@@ -865,7 +884,7 @@ export async function tryXiaoEUnifiedLogin(options = {}) {
|
|
|
865
884
|
profileBaseUrl: options.profileBaseUrl,
|
|
866
885
|
env,
|
|
867
886
|
});
|
|
868
|
-
const mayUseTokenScript = options.allowTokenScript
|
|
887
|
+
const mayUseTokenScript = options.allowTokenScript === true;
|
|
869
888
|
if (!baseUrl && !mayUseTokenScript)
|
|
870
889
|
return null;
|
|
871
890
|
let credential = null;
|
|
@@ -1,41 +1,19 @@
|
|
|
1
1
|
import { CliError, ExitCode } from '../../core/errors.js';
|
|
2
|
-
import { E10AuthSession
|
|
3
|
-
import {
|
|
2
|
+
import { E10AuthSession } from './auth/session.js';
|
|
3
|
+
import { hasXiaoEEnv } from './auth/xiaoe.js';
|
|
4
4
|
import { E10ApiClient } from './client.js';
|
|
5
|
-
function shouldPreferXiaoEUnifiedLogin(options, env) {
|
|
6
|
-
return !options.auth && !options.profile && hasXiaoEEnv(env);
|
|
7
|
-
}
|
|
8
5
|
export async function getE10Session(ctx, load = {}) {
|
|
9
6
|
const options = ctx.getGlobalOptions();
|
|
10
|
-
if (shouldPreferXiaoEUnifiedLogin(options, ctx.env)) {
|
|
11
|
-
const profileBaseUrl = readProfileConfig(getActiveProfile()).baseUrl;
|
|
12
|
-
const xiaoELogin = await tryXiaoEUnifiedLogin({
|
|
13
|
-
env: ctx.env,
|
|
14
|
-
baseUrl: options.baseUrl,
|
|
15
|
-
profileBaseUrl,
|
|
16
|
-
allowBundledEnvScript: true,
|
|
17
|
-
allowTokenScript: true,
|
|
18
|
-
logger: ctx.logger,
|
|
19
|
-
});
|
|
20
|
-
if (xiaoELogin)
|
|
21
|
-
return xiaoELogin.session;
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
7
|
return E10AuthSession.fromOptions(options, ctx.cwd, load);
|
|
25
8
|
}
|
|
26
9
|
export async function requireE10Session(ctx, load = {}) {
|
|
27
10
|
const session = await getE10Session(ctx, load);
|
|
28
11
|
if (!session) {
|
|
29
12
|
const options = ctx.getGlobalOptions();
|
|
30
|
-
if (hasXiaoEEnv(ctx.env
|
|
31
|
-
|
|
32
|
-
explicitBaseUrl: options.baseUrl,
|
|
33
|
-
profileBaseUrl: readProfileConfig(getActiveProfile()).baseUrl,
|
|
34
|
-
env: ctx.env,
|
|
35
|
-
});
|
|
36
|
-
throw createXiaoEUnifiedLoginUnavailableError(baseUrl);
|
|
13
|
+
if (hasXiaoEEnv(ctx.env, options)) {
|
|
14
|
+
throw new CliError('E10 auth not found. The current runtime is XiaoE; run weaver-work-cli auth xiaoe first.', ExitCode.AuthError);
|
|
37
15
|
}
|
|
38
|
-
throw new CliError('E10 auth not found.
|
|
16
|
+
throw new CliError('E10 auth not found. Ask the user for the E10 login domain (base URL), then run: weaver-work-cli auth login --base-url <url> --agent_type <agent_type>.', ExitCode.AuthError);
|
|
39
17
|
}
|
|
40
18
|
return session;
|
|
41
19
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* archive-oa 固定业务参数。
|
|
3
|
+
*
|
|
4
|
+
* 泛微商务合同与契约锁协作采纳是两套独立的应用/列表,参数值不同,禁止交叉混用。
|
|
5
|
+
* 来源:`source/weaver-e10-archive-oa.zip` 内 10 篇接口文档(catalog.md 登记)。
|
|
6
|
+
*/
|
|
7
|
+
/** 加入借阅车 / 发起借阅的单次条数上限(接口侧限制)。 */
|
|
8
|
+
export const MAX_BORROW_ITEMS = 6;
|
|
9
|
+
/** 发起借阅流程创建页的流程 ID(前端 `flowpage/fullCreate/<flowId>`)。 */
|
|
10
|
+
export const BORROW_FLOW_ID = '100003460000002090';
|
|
11
|
+
/** 借阅车记录 `fb` 即界面「分部」列:0 = 泛微、1 = 契约锁。 */
|
|
12
|
+
export const FLOW_FB_LABELS = {
|
|
13
|
+
'0': '泛微',
|
|
14
|
+
'1': '契约锁',
|
|
15
|
+
};
|
|
16
|
+
/** 档案系统:按合同号定位档案的自定义接口标识。 */
|
|
17
|
+
export const ARCHIVE_LOCATE_INTERFACE_PK = 'getArcIdByContractNo';
|
|
18
|
+
export const ARCHIVE_LOCATE_PATH = `/api/ebuilder/form/formdata/v2/getFormDataList/${ARCHIVE_LOCATE_INTERFACE_PK}`;
|
|
19
|
+
/** 泛微商务合同(案例台账)。 */
|
|
20
|
+
export const WEAVER_CONTRACT = {
|
|
21
|
+
source: 'weaver',
|
|
22
|
+
passId: '850741972510941185',
|
|
23
|
+
listId: '850741985437786120',
|
|
24
|
+
filterListId: '850741985437786120',
|
|
25
|
+
cusMenuId: '5408862605522555751',
|
|
26
|
+
urlPageTitle: '5rOb5b6u5qGI5L6L',
|
|
27
|
+
};
|
|
28
|
+
export const WEAVER_BORROW_CAR = {
|
|
29
|
+
source: 'weaver',
|
|
30
|
+
passId: '882393476189437952',
|
|
31
|
+
listId: '882393480417296390',
|
|
32
|
+
cusMenuId: '5408862605522555751',
|
|
33
|
+
urlPageTitle: '5rOb5b6u5qGI5L6L',
|
|
34
|
+
};
|
|
35
|
+
export const WEAVER_ESB_ADD = {
|
|
36
|
+
source: 'weaver',
|
|
37
|
+
esbActionIds: '882395911377174530',
|
|
38
|
+
objId: '850741972510941185',
|
|
39
|
+
listId: '850741985437786120',
|
|
40
|
+
successText: '【加入借阅车】执行成功!',
|
|
41
|
+
};
|
|
42
|
+
/** 契约锁协作采纳(用户常称「案例」)。 */
|
|
43
|
+
export const QYS_CONTRACT = {
|
|
44
|
+
source: 'qys',
|
|
45
|
+
passId: '850742853046345728',
|
|
46
|
+
listId: '1190611964371984450',
|
|
47
|
+
cusMenuId: '5408862605522555752',
|
|
48
|
+
urlPageTitle: '5aWR57qm6ZSB5qGI5L6L',
|
|
49
|
+
};
|
|
50
|
+
export const QYS_BORROW_CAR = {
|
|
51
|
+
source: 'qys',
|
|
52
|
+
passId: '882393476189437952',
|
|
53
|
+
listId: '883819516961857839',
|
|
54
|
+
cusMenuId: '5408862605522555752',
|
|
55
|
+
urlPageTitle: '5aWR57qm6ZSB5qGI5L6L',
|
|
56
|
+
};
|
|
57
|
+
export const QYS_ESB_ADD = {
|
|
58
|
+
source: 'qys',
|
|
59
|
+
esbActionIds: '892706856573648911',
|
|
60
|
+
objId: '850742853046345728',
|
|
61
|
+
listId: '1190611964371984450',
|
|
62
|
+
successText: '【契约锁加入借阅车】执行成功!',
|
|
63
|
+
};
|
|
64
|
+
/** 契约锁默认过滤「处理进度 = 已归档」。 */
|
|
65
|
+
export const QYS_DEFAULT_ARCHIVED_FIELD = 'cljd';
|
|
66
|
+
export const QYS_DEFAULT_ARCHIVED_VALUE = '已归档';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { archiveOaValidationError } from './errors.js';
|
|
7
|
+
function stateRoot(ctx) {
|
|
8
|
+
const configured = ctx?.config?.path();
|
|
9
|
+
if (configured)
|
|
10
|
+
return resolve(configured, '..');
|
|
11
|
+
return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
|
|
12
|
+
}
|
|
13
|
+
async function loadKey(ctx) {
|
|
14
|
+
const keysDir = resolve(stateRoot(ctx), 'keys');
|
|
15
|
+
const filename = resolve(keysDir, 'archive-oa-continuation.key');
|
|
16
|
+
await mkdir(keysDir, { recursive: true, mode: 0o700 });
|
|
17
|
+
if (existsSync(filename))
|
|
18
|
+
return readFile(filename);
|
|
19
|
+
const key = randomBytes(32);
|
|
20
|
+
try {
|
|
21
|
+
await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
|
|
22
|
+
if (process.platform !== 'win32')
|
|
23
|
+
await chmod(filename, 0o600).catch(() => undefined);
|
|
24
|
+
return key;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return readFile(filename);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function encode(value) {
|
|
31
|
+
return Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
32
|
+
}
|
|
33
|
+
function signature(payload, key) {
|
|
34
|
+
return createHmac('sha256', key).update(payload).digest('base64url');
|
|
35
|
+
}
|
|
36
|
+
export function createArchiveOaContinuationSigner(ctx) {
|
|
37
|
+
return {
|
|
38
|
+
async issue(payload, ttlMs = 10 * 60_000) {
|
|
39
|
+
const issuedAt = Date.now();
|
|
40
|
+
const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
|
|
41
|
+
const sig = signature(body, await loadKey(ctx));
|
|
42
|
+
return `${body}.${sig}`;
|
|
43
|
+
},
|
|
44
|
+
async verify(token, expectedOperation) {
|
|
45
|
+
if (typeof token !== 'string' || !token.includes('.')) {
|
|
46
|
+
throw archiveOaValidationError('continuation_invalid', 'continuation 无效');
|
|
47
|
+
}
|
|
48
|
+
const [body, provided] = token.split('.');
|
|
49
|
+
const expected = signature(body, await loadKey(ctx));
|
|
50
|
+
const valid = provided.length === expected.length
|
|
51
|
+
&& timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
|
|
52
|
+
if (!valid)
|
|
53
|
+
throw archiveOaValidationError('continuation_invalid', 'continuation 签名校验失败');
|
|
54
|
+
let value;
|
|
55
|
+
try {
|
|
56
|
+
value = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw archiveOaValidationError('continuation_invalid', 'continuation 内容无法解析');
|
|
60
|
+
}
|
|
61
|
+
if (value.operation !== expectedOperation) {
|
|
62
|
+
throw archiveOaValidationError('continuation_mismatch', 'continuation 不属于当前操作');
|
|
63
|
+
}
|
|
64
|
+
if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt < Date.now()) {
|
|
65
|
+
throw archiveOaValidationError('continuation_expired', 'continuation 已过期');
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|