liangzimixin 0.3.1 → 0.3.3
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/dist/index.cjs +154 -333
- package/dist/index.d.cts +63 -188
- package/dist/setup-entry.cjs +154 -333
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2233,7 +2233,7 @@ var require_websocket = __commonJS({
|
|
|
2233
2233
|
var http = require("http");
|
|
2234
2234
|
var net = require("net");
|
|
2235
2235
|
var tls = require("tls");
|
|
2236
|
-
var { randomBytes:
|
|
2236
|
+
var { randomBytes: randomBytes3, createHash } = require("crypto");
|
|
2237
2237
|
var { Duplex, Readable } = require("stream");
|
|
2238
2238
|
var { URL: URL2 } = require("url");
|
|
2239
2239
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -2763,7 +2763,7 @@ var require_websocket = __commonJS({
|
|
|
2763
2763
|
}
|
|
2764
2764
|
}
|
|
2765
2765
|
const defaultPort = isSecure ? 443 : 80;
|
|
2766
|
-
const key =
|
|
2766
|
+
const key = randomBytes3(16).toString("base64");
|
|
2767
2767
|
const request = isSecure ? https.request : http.request;
|
|
2768
2768
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
2769
2769
|
let perMessageDeflate;
|
|
@@ -17483,10 +17483,7 @@ var DEFAULT_INTERNAL_CONFIG = {
|
|
|
17483
17483
|
}
|
|
17484
17484
|
},
|
|
17485
17485
|
auth: {
|
|
17486
|
-
serverUrl: process.env.LZMX_AUTH_URL || "https://
|
|
17487
|
-
clientName: "liangzimixin",
|
|
17488
|
-
scopes: ["openid", "im_sdk", "data_operate", "offline_access"],
|
|
17489
|
-
autoRegister: true,
|
|
17486
|
+
serverUrl: process.env.LZMX_AUTH_URL || "https://imtwo.zdxlz.com/open-apis/v1",
|
|
17490
17487
|
refreshAheadMs: 3e5
|
|
17491
17488
|
},
|
|
17492
17489
|
crypto: {
|
|
@@ -18812,54 +18809,104 @@ var quantumImPlugin = {
|
|
|
18812
18809
|
}
|
|
18813
18810
|
};
|
|
18814
18811
|
|
|
18815
|
-
// src/crypto/quantun-plug-
|
|
18816
|
-
var
|
|
18817
|
-
var
|
|
18818
|
-
var
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
18822
|
-
|
|
18823
|
-
|
|
18812
|
+
// src/crypto/quantun-plug-mock.ts
|
|
18813
|
+
var import_node_crypto = require("crypto");
|
|
18814
|
+
var log14 = createLogger("crypto/quantun-plug-mock");
|
|
18815
|
+
var MOCK_KEY = Buffer.alloc(32, 0);
|
|
18816
|
+
MOCK_KEY.write("quantum-mock-key-2026", 0);
|
|
18817
|
+
var MOCK_KEY_ID = "mock-key-v1";
|
|
18818
|
+
var initialized = false;
|
|
18819
|
+
var quantunPlugMock = {
|
|
18820
|
+
/**
|
|
18821
|
+
* 初始化 Mock SDK
|
|
18822
|
+
* 幂等操作 — 重复调用不会报错
|
|
18823
|
+
*/
|
|
18824
|
+
async init() {
|
|
18825
|
+
if (initialized) return;
|
|
18826
|
+
initialized = true;
|
|
18827
|
+
log14.warn("\u26A0\uFE0F MOCK MODE \u2014 \u4F7F\u7528 AES-256-GCM \u6A21\u62DF\u91CF\u5B50\u52A0\u5BC6");
|
|
18828
|
+
},
|
|
18829
|
+
/**
|
|
18830
|
+
* 加密明文 → 密文 + 密钥标识
|
|
18831
|
+
*
|
|
18832
|
+
* 内部流程:
|
|
18833
|
+
* 1. 生成 12 字节随机 IV (确保每次加密结果不同)
|
|
18834
|
+
* 2. AES-256-GCM 加密明文
|
|
18835
|
+
* 3. 获取 16 字节 AuthTag (认证标签,防篡改)
|
|
18836
|
+
* 4. 拼接 [IV(12) | Tag(16) | Ciphertext] → Base64 编码
|
|
18837
|
+
*/
|
|
18838
|
+
async encrypt(plaintext, mode) {
|
|
18839
|
+
if (!initialized) {
|
|
18840
|
+
throw new Error("quantunPlug not initialized \u2014 call init() first");
|
|
18841
|
+
}
|
|
18842
|
+
const iv = (0, import_node_crypto.randomBytes)(12);
|
|
18843
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", MOCK_KEY, iv);
|
|
18844
|
+
const encrypted = Buffer.concat([
|
|
18845
|
+
cipher.update(plaintext, "utf-8"),
|
|
18846
|
+
cipher.final()
|
|
18847
|
+
]);
|
|
18848
|
+
const tag = cipher.getAuthTag();
|
|
18849
|
+
const combined = Buffer.concat([iv, tag, encrypted]);
|
|
18850
|
+
return {
|
|
18851
|
+
ciphertext: combined.toString("base64"),
|
|
18852
|
+
keyId: MOCK_KEY_ID
|
|
18853
|
+
};
|
|
18854
|
+
},
|
|
18855
|
+
/**
|
|
18856
|
+
* 解密密文 → 明文
|
|
18857
|
+
*
|
|
18858
|
+
* 内部流程:
|
|
18859
|
+
* 1. Base64 解码
|
|
18860
|
+
* 2. 拆分 IV(12) + Tag(16) + Ciphertext
|
|
18861
|
+
* 3. AES-256-GCM 解密 + AuthTag 验证
|
|
18862
|
+
* 4. 返回 UTF-8 明文
|
|
18863
|
+
*
|
|
18864
|
+
* @throws AuthTag 验证失败时抛出异常 (数据被篡改)
|
|
18865
|
+
*/
|
|
18866
|
+
async decrypt(ciphertext, keyId, mode) {
|
|
18867
|
+
if (!initialized) {
|
|
18868
|
+
throw new Error("quantunPlug not initialized \u2014 call init() first");
|
|
18869
|
+
}
|
|
18870
|
+
const combined = Buffer.from(ciphertext, "base64");
|
|
18871
|
+
const iv = combined.subarray(0, 12);
|
|
18872
|
+
const tag = combined.subarray(12, 28);
|
|
18873
|
+
const encrypted = combined.subarray(28);
|
|
18874
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", MOCK_KEY, iv);
|
|
18875
|
+
decipher.setAuthTag(tag);
|
|
18876
|
+
const decrypted = Buffer.concat([
|
|
18877
|
+
decipher.update(encrypted),
|
|
18878
|
+
decipher.final()
|
|
18879
|
+
// 此处 Tag 验证失败会抛出异常
|
|
18880
|
+
]);
|
|
18881
|
+
return {
|
|
18882
|
+
plaintext: decrypted.toString("utf-8")
|
|
18883
|
+
};
|
|
18884
|
+
}
|
|
18824
18885
|
};
|
|
18825
|
-
var
|
|
18826
|
-
var require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
18827
|
-
var quantunPlug = null;
|
|
18828
|
-
try {
|
|
18829
|
-
quantunPlug = require2("./sdk/index.min.cjs");
|
|
18830
|
-
log14.info("Quantum SDK loaded from sdk/index.min.cjs");
|
|
18831
|
-
} catch (err) {
|
|
18832
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
18833
|
-
log14.warn("Quantum SDK missing, only passthrough mode will work:", msg);
|
|
18834
|
-
}
|
|
18835
|
-
var quantun_plug_adapter_default = quantunPlug;
|
|
18886
|
+
var quantun_plug_mock_default = quantunPlugMock;
|
|
18836
18887
|
|
|
18837
18888
|
// src/crypto/crypto-engine.ts
|
|
18838
18889
|
var log15 = createLogger("crypto/crypto-engine");
|
|
18839
18890
|
var PASSTHROUGH_KEY_ID = "passthrough";
|
|
18840
|
-
var FILE_CHUNK_SIZE = 10 * 1024 * 1024;
|
|
18841
|
-
var FILE_DECRYPT_CHUNK_SIZE = FILE_CHUNK_SIZE + 16;
|
|
18842
18891
|
var CryptoEngine = class _CryptoEngine {
|
|
18843
|
-
/** 底层量子加密 SDK
|
|
18892
|
+
/** 底层量子加密 SDK 实例 (Mock 或真实), 透传模式下为 null */
|
|
18844
18893
|
plug;
|
|
18845
18894
|
/** SDK 是否已完成初始化 */
|
|
18846
|
-
initialized;
|
|
18895
|
+
initialized = false;
|
|
18847
18896
|
/** 是否为透传模式 (不加密, 明文直接透传) */
|
|
18848
18897
|
passthrough;
|
|
18849
18898
|
/**
|
|
18850
|
-
* 构造加密引擎
|
|
18899
|
+
* 构造加密引擎 (加密模式)
|
|
18851
18900
|
*
|
|
18852
|
-
*
|
|
18901
|
+
* 当前默认使用 Mock SDK。
|
|
18902
|
+
* 待真实 SDK 交付后,此处替换为 require('./quantunPlug.ejs')。
|
|
18853
18903
|
*
|
|
18854
|
-
*
|
|
18855
|
-
* - new CryptoEngine() → 加密模式, 使用量子 SDK
|
|
18856
|
-
* - CryptoEngine.createPassthrough() → 透传模式, 无需 init()
|
|
18904
|
+
* 如需透传模式,请使用静态方法 CryptoEngine.createPassthrough()。
|
|
18857
18905
|
*/
|
|
18858
|
-
constructor(
|
|
18859
|
-
this.
|
|
18860
|
-
this.
|
|
18861
|
-
|
|
18862
|
-
log15.info(`CryptoEngine created (${passthrough ? "passthrough mode \u2014 crypto disabled" : "quantum SDK mode"})`);
|
|
18906
|
+
constructor() {
|
|
18907
|
+
this.plug = quantun_plug_mock_default;
|
|
18908
|
+
this.passthrough = false;
|
|
18909
|
+
log15.info("CryptoEngine created (mock mode)");
|
|
18863
18910
|
}
|
|
18864
18911
|
/**
|
|
18865
18912
|
* 创建透传模式的加密引擎 (静态工厂方法)
|
|
@@ -18873,12 +18920,17 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18873
18920
|
* 上层模块 (TokenStore, MessagePipe) 无需感知加密是否开启。
|
|
18874
18921
|
*/
|
|
18875
18922
|
static createPassthrough() {
|
|
18876
|
-
|
|
18923
|
+
const instance = Object.create(_CryptoEngine.prototype);
|
|
18924
|
+
instance.plug = null;
|
|
18925
|
+
instance.passthrough = true;
|
|
18926
|
+
instance.initialized = true;
|
|
18927
|
+
log15.info("CryptoEngine created (passthrough mode \u2014 crypto disabled)");
|
|
18928
|
+
return instance;
|
|
18877
18929
|
}
|
|
18878
18930
|
/**
|
|
18879
18931
|
* 初始化加密引擎
|
|
18880
18932
|
*
|
|
18881
|
-
* 加密模式: 调用 SDK 的 init()
|
|
18933
|
+
* 加密模式: 调用 SDK 的 init() 方法完成密钥协商等初始化操作。
|
|
18882
18934
|
* 透传模式: 空操作 (already initialized)。
|
|
18883
18935
|
* 幂等操作 — 重复调用安全。
|
|
18884
18936
|
*/
|
|
@@ -18888,9 +18940,6 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18888
18940
|
this.initialized = true;
|
|
18889
18941
|
return;
|
|
18890
18942
|
}
|
|
18891
|
-
if (!this.plug) {
|
|
18892
|
-
throw new Error("Quantum encryption SDK is missing. Cannot initialize in encryption mode (check dist/index.min.cjs).");
|
|
18893
|
-
}
|
|
18894
18943
|
await this.plug.init();
|
|
18895
18944
|
this.initialized = true;
|
|
18896
18945
|
log15.info("CryptoEngine initialized \u2713");
|
|
@@ -18898,24 +18947,22 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18898
18947
|
/**
|
|
18899
18948
|
* 加密明文
|
|
18900
18949
|
*
|
|
18901
|
-
* 加密模式: 调用底层 SDK
|
|
18950
|
+
* 加密模式: 调用底层 SDK 加密, 返回 Base64 密文 + keyId
|
|
18902
18951
|
* 透传模式: 直接返回明文作为 "密文", keyId = 'passthrough'
|
|
18903
18952
|
*
|
|
18904
|
-
* 注意: SDK 返回驼峰 cipherText, 此方法对外统一为小写 ciphertext
|
|
18905
|
-
*
|
|
18906
18953
|
* @param plaintext - 要加密的明文字符串
|
|
18907
18954
|
* @returns { ciphertext: 密文字符串, keyId: 密钥标识 }
|
|
18908
18955
|
* @throws 加密模式下未初始化时抛出错误
|
|
18909
18956
|
*/
|
|
18910
18957
|
async encrypt(plaintext) {
|
|
18958
|
+
this.ensureInitialized();
|
|
18911
18959
|
if (this.passthrough) {
|
|
18912
18960
|
log15.debug("Encrypt (passthrough)", { length: plaintext.length });
|
|
18913
18961
|
return { ciphertext: plaintext, keyId: PASSTHROUGH_KEY_ID };
|
|
18914
18962
|
}
|
|
18915
|
-
const
|
|
18916
|
-
const result = await plug.encrypt(plaintext, DEFAULT_ENCRYPT_MODE);
|
|
18963
|
+
const result = await this.plug.encrypt(plaintext, 1);
|
|
18917
18964
|
log15.debug("Encrypted", { length: plaintext.length, keyId: result.keyId });
|
|
18918
|
-
return
|
|
18965
|
+
return result;
|
|
18919
18966
|
}
|
|
18920
18967
|
/**
|
|
18921
18968
|
* 解密密文
|
|
@@ -18929,100 +18976,14 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
18929
18976
|
* @throws 加密模式下未初始化、密文损坏或 keyId 不匹配时抛出错误
|
|
18930
18977
|
*/
|
|
18931
18978
|
async decrypt(ciphertext, keyId) {
|
|
18979
|
+
this.ensureInitialized();
|
|
18932
18980
|
if (this.passthrough) {
|
|
18933
18981
|
log15.debug("Decrypt (passthrough)", { keyId });
|
|
18934
18982
|
return ciphertext;
|
|
18935
18983
|
}
|
|
18936
|
-
const
|
|
18937
|
-
const result = await plug.decrypt(ciphertext, keyId, DEFAULT_ENCRYPT_MODE);
|
|
18984
|
+
const result = await this.plug.decrypt(ciphertext, keyId, 1);
|
|
18938
18985
|
log15.debug("Decrypted", { keyId });
|
|
18939
|
-
return result.
|
|
18940
|
-
}
|
|
18941
|
-
/**
|
|
18942
|
-
* 加密文件数据
|
|
18943
|
-
*
|
|
18944
|
-
* 自动处理 10MB 分片编排:
|
|
18945
|
-
* - ≤10MB: 单次调用 SDK encryptFile
|
|
18946
|
-
* - >10MB: 按 10MB 分片, 首片无需传密钥参数, 后续片传入前一片返回的 { keyId, sessionKey, fillKey }
|
|
18947
|
-
*
|
|
18948
|
-
* 透传模式: 直接返回原数据
|
|
18949
|
-
*
|
|
18950
|
-
* @param fileData - 文件数据 Buffer
|
|
18951
|
-
* @returns { fileBuffer: 加密后的文件 Buffer, keyId: 密钥标识 }
|
|
18952
|
-
*/
|
|
18953
|
-
async encryptFile(fileData) {
|
|
18954
|
-
if (this.passthrough) {
|
|
18955
|
-
log15.debug("EncryptFile (passthrough)", { size: fileData.length });
|
|
18956
|
-
return { fileBuffer: fileData, keyId: PASSTHROUGH_KEY_ID };
|
|
18957
|
-
}
|
|
18958
|
-
if (fileData.length === 0) {
|
|
18959
|
-
return { fileBuffer: Buffer.alloc(0), keyId: PASSTHROUGH_KEY_ID };
|
|
18960
|
-
}
|
|
18961
|
-
const chunks = [];
|
|
18962
|
-
const total = Math.ceil(fileData.length / FILE_CHUNK_SIZE);
|
|
18963
|
-
let keyId;
|
|
18964
|
-
let sessionKey;
|
|
18965
|
-
let fillKey;
|
|
18966
|
-
const plug = this.requirePlug();
|
|
18967
|
-
log15.debug("EncryptFile", { size: fileData.length, chunks: total });
|
|
18968
|
-
for (let i = 0; i < total; i++) {
|
|
18969
|
-
const start = i * FILE_CHUNK_SIZE;
|
|
18970
|
-
const end = Math.min(start + FILE_CHUNK_SIZE, fileData.length);
|
|
18971
|
-
const chunk = fileData.subarray(start, end);
|
|
18972
|
-
const result = await plug.encryptFile(chunk, DEFAULT_ENCRYPT_MODE, {
|
|
18973
|
-
keyId,
|
|
18974
|
-
sessionKey,
|
|
18975
|
-
fillKey
|
|
18976
|
-
});
|
|
18977
|
-
chunks.push(result.fileBuffer);
|
|
18978
|
-
keyId = result.keyId;
|
|
18979
|
-
sessionKey = result.sessionKey;
|
|
18980
|
-
fillKey = result.fillKey;
|
|
18981
|
-
}
|
|
18982
|
-
log15.debug("EncryptFile done", { keyId, chunks: total });
|
|
18983
|
-
return { fileBuffer: Buffer.concat(chunks), keyId: keyId ?? "" };
|
|
18984
|
-
}
|
|
18985
|
-
/**
|
|
18986
|
-
* 解密文件数据
|
|
18987
|
-
*
|
|
18988
|
-
* 自动处理 (10MB + 16) 分片编排:
|
|
18989
|
-
* - ≤(10MB+16): 单次调用 SDK decryptFile
|
|
18990
|
-
* - >(10MB+16): 按 (10MB+16) 分片, 首片传 keyId, 后续片传入前一片返回的 { sessionKey, fillKey }
|
|
18991
|
-
*
|
|
18992
|
-
* 透传模式: 直接返回原数据
|
|
18993
|
-
*
|
|
18994
|
-
* @param fileData - 加密后的文件 Buffer
|
|
18995
|
-
* @param keyId - 加密时返回的密钥标识
|
|
18996
|
-
* @returns 解密后的文件 Buffer
|
|
18997
|
-
*/
|
|
18998
|
-
async decryptFile(fileData, keyId) {
|
|
18999
|
-
if (this.passthrough) {
|
|
19000
|
-
log15.debug("DecryptFile (passthrough)", { keyId });
|
|
19001
|
-
return fileData;
|
|
19002
|
-
}
|
|
19003
|
-
if (fileData.length === 0) {
|
|
19004
|
-
return Buffer.alloc(0);
|
|
19005
|
-
}
|
|
19006
|
-
const plug = this.requirePlug();
|
|
19007
|
-
const chunks = [];
|
|
19008
|
-
const total = Math.ceil(fileData.length / FILE_DECRYPT_CHUNK_SIZE);
|
|
19009
|
-
let sessionKey;
|
|
19010
|
-
let fillKey;
|
|
19011
|
-
log15.debug("DecryptFile", { size: fileData.length, chunks: total, keyId });
|
|
19012
|
-
for (let i = 0; i < total; i++) {
|
|
19013
|
-
const start = i * FILE_DECRYPT_CHUNK_SIZE;
|
|
19014
|
-
const end = Math.min(start + FILE_DECRYPT_CHUNK_SIZE, fileData.length);
|
|
19015
|
-
const chunk = fileData.subarray(start, end);
|
|
19016
|
-
const result = await plug.decryptFile(chunk, keyId, DEFAULT_ENCRYPT_MODE, {
|
|
19017
|
-
sessionKey,
|
|
19018
|
-
fillKey
|
|
19019
|
-
});
|
|
19020
|
-
chunks.push(result.fileBuffer);
|
|
19021
|
-
sessionKey = result.sessionKey;
|
|
19022
|
-
fillKey = result.fillKey;
|
|
19023
|
-
}
|
|
19024
|
-
log15.debug("DecryptFile done", { keyId, chunks: total });
|
|
19025
|
-
return Buffer.concat(chunks);
|
|
18986
|
+
return result.plaintext;
|
|
19026
18987
|
}
|
|
19027
18988
|
/**
|
|
19028
18989
|
* 查询当前是否为透传模式
|
|
@@ -19032,26 +18993,25 @@ var CryptoEngine = class _CryptoEngine {
|
|
|
19032
18993
|
return this.passthrough;
|
|
19033
18994
|
}
|
|
19034
18995
|
/**
|
|
19035
|
-
*
|
|
19036
|
-
*
|
|
18996
|
+
* 确保引擎已初始化
|
|
18997
|
+
* 未初始化时抛出明确错误,避免难以排查的 SDK 内部异常
|
|
19037
18998
|
*/
|
|
19038
|
-
|
|
18999
|
+
ensureInitialized() {
|
|
19039
19000
|
if (!this.initialized) {
|
|
19040
19001
|
throw new Error("CryptoEngine not initialized \u2014 call init() first");
|
|
19041
19002
|
}
|
|
19042
|
-
return this.plug;
|
|
19043
19003
|
}
|
|
19044
19004
|
};
|
|
19045
19005
|
|
|
19046
19006
|
// src/auth/oauth-client.ts
|
|
19047
19007
|
var log16 = createLogger("auth/oauth-client");
|
|
19048
|
-
var
|
|
19008
|
+
var ApiError = class extends Error {
|
|
19049
19009
|
constructor(status, body, endpoint) {
|
|
19050
|
-
super(`
|
|
19010
|
+
super(`API error: ${status} on ${endpoint}`);
|
|
19051
19011
|
this.status = status;
|
|
19052
19012
|
this.body = body;
|
|
19053
19013
|
this.endpoint = endpoint;
|
|
19054
|
-
this.name = "
|
|
19014
|
+
this.name = "ApiError";
|
|
19055
19015
|
}
|
|
19056
19016
|
};
|
|
19057
19017
|
var TokenAcquireError = class extends Error {
|
|
@@ -19068,28 +19028,21 @@ function sleep(ms) {
|
|
|
19068
19028
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
19069
19029
|
}
|
|
19070
19030
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
19071
|
-
var
|
|
19031
|
+
var GRANT_TYPE = "client_credentials";
|
|
19032
|
+
var SCOPE = "client_credentials refresh_token";
|
|
19033
|
+
var OAuthClient = class {
|
|
19072
19034
|
baseUrl;
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
clientName;
|
|
19076
|
-
scopes;
|
|
19035
|
+
appId;
|
|
19036
|
+
appSecret;
|
|
19077
19037
|
constructor(config2) {
|
|
19078
|
-
this.baseUrl = config2.
|
|
19079
|
-
this.
|
|
19080
|
-
this.
|
|
19081
|
-
this.
|
|
19082
|
-
this.scopes = config2.scopes;
|
|
19083
|
-
log16.info("SealClient initialized", { serverUrl: this.baseUrl, clientName: config2.clientName });
|
|
19038
|
+
this.baseUrl = config2.baseUrl.replace(/\/+$/, "");
|
|
19039
|
+
this.appId = config2.appId;
|
|
19040
|
+
this.appSecret = config2.appSecret;
|
|
19041
|
+
log16.info("OAuthClient initialized", { baseUrl: this.baseUrl, appId: sanitize(this.appId) });
|
|
19084
19042
|
}
|
|
19085
19043
|
// ── 通用 HTTP 请求 ──
|
|
19086
19044
|
/**
|
|
19087
19045
|
* 统一 HTTP 请求封装 — 含超时、日志脱敏和错误处理
|
|
19088
|
-
* @param method - HTTP 方法
|
|
19089
|
-
* @param path - 请求路径 (如 '/seal/client/register')
|
|
19090
|
-
* @param options - 请求选项
|
|
19091
|
-
* @returns 解析后的 JSON 响应
|
|
19092
|
-
* @throws SealApiError — HTTP 非 2xx 响应
|
|
19093
19046
|
*/
|
|
19094
19047
|
async request(method, path2, options = {}) {
|
|
19095
19048
|
const url2 = `${this.baseUrl}${path2}`;
|
|
@@ -19121,109 +19074,38 @@ var SealClient = class {
|
|
|
19121
19074
|
parsedBody = responseBody;
|
|
19122
19075
|
}
|
|
19123
19076
|
log16.error("HTTP error", { method, url: url2, status: response.status });
|
|
19124
|
-
throw new
|
|
19077
|
+
throw new ApiError(response.status, parsedBody, path2);
|
|
19125
19078
|
}
|
|
19126
19079
|
log16.debug("HTTP response", { method, url: url2, status: response.status });
|
|
19127
19080
|
try {
|
|
19128
19081
|
return JSON.parse(responseBody);
|
|
19129
19082
|
} catch {
|
|
19130
|
-
throw new
|
|
19083
|
+
throw new ApiError(response.status, responseBody, path2);
|
|
19131
19084
|
}
|
|
19132
19085
|
}
|
|
19133
19086
|
// ── 公共方法 ──
|
|
19134
|
-
/** 判断是否已注册 (有 clientId + clientSecret) */
|
|
19135
|
-
isRegistered() {
|
|
19136
|
-
return !!(this.clientId && this.clientSecret);
|
|
19137
|
-
}
|
|
19138
|
-
/** 更新 clientId / clientSecret (注册成功后或从持久化加载后调用) */
|
|
19139
|
-
setCredentials(clientId, clientSecret) {
|
|
19140
|
-
this.clientId = clientId;
|
|
19141
|
-
this.clientSecret = clientSecret;
|
|
19142
|
-
log16.info("Credentials updated", { clientId: sanitize(clientId) });
|
|
19143
|
-
}
|
|
19144
|
-
/**
|
|
19145
|
-
* 动态注册客户端 → POST /seal/client/register
|
|
19146
|
-
*
|
|
19147
|
-
* 首次运行时自动调用,获取 clientId 和 clientSecret。
|
|
19148
|
-
* 注册成功后自动更新内部凭据。
|
|
19149
|
-
*/
|
|
19150
|
-
async register(params) {
|
|
19151
|
-
const body = {
|
|
19152
|
-
client_name: params.clientName,
|
|
19153
|
-
scopes: params.scopes,
|
|
19154
|
-
authentication_methods: params.authMethods ?? ["client_secret_post"],
|
|
19155
|
-
grant_types: params.grantTypes ?? ["authorization_code", "client_credentials", "refresh_token"],
|
|
19156
|
-
redirect_uris: params.redirectUris ?? ["https://placeholder.local"],
|
|
19157
|
-
logout_redirect_uris: [],
|
|
19158
|
-
client_settings: {
|
|
19159
|
-
"settings.client.require-authorization-consent": true
|
|
19160
|
-
}
|
|
19161
|
-
};
|
|
19162
|
-
if (params.tokenSettings) {
|
|
19163
|
-
const ts = {};
|
|
19164
|
-
if (params.tokenSettings.accessTokenTtl) {
|
|
19165
|
-
ts["settings.token.access-token-time-to-live"] = params.tokenSettings.accessTokenTtl;
|
|
19166
|
-
}
|
|
19167
|
-
if (params.tokenSettings.refreshTokenTtl) {
|
|
19168
|
-
ts["settings.token.refresh-token-time-to-live"] = params.tokenSettings.refreshTokenTtl;
|
|
19169
|
-
}
|
|
19170
|
-
if (params.tokenSettings.accessTokenFormat) {
|
|
19171
|
-
ts["settings.token.access-token-format"] = params.tokenSettings.accessTokenFormat;
|
|
19172
|
-
}
|
|
19173
|
-
if (params.tokenSettings.reuseRefreshTokens !== void 0) {
|
|
19174
|
-
ts["settings.token.reuse-refresh-tokens"] = params.tokenSettings.reuseRefreshTokens;
|
|
19175
|
-
}
|
|
19176
|
-
ts["settings.token.authorization-code-time-to-live"] = "300";
|
|
19177
|
-
body.token_settings = ts;
|
|
19178
|
-
}
|
|
19179
|
-
log16.info("Registering OAuth client", { clientName: params.clientName });
|
|
19180
|
-
const response = await this.request(
|
|
19181
|
-
"POST",
|
|
19182
|
-
"/seal/client/register",
|
|
19183
|
-
{ body, contentType: "json" }
|
|
19184
|
-
);
|
|
19185
|
-
const registration = {
|
|
19186
|
-
clientId: response.client_id,
|
|
19187
|
-
clientSecret: response.client_secret,
|
|
19188
|
-
clientSecretExpiresAt: response.client_secret_expires_at ?? "",
|
|
19189
|
-
clientIdIssuedAt: response.client_id_issued_at ?? "",
|
|
19190
|
-
scopes: response.scopes ?? params.scopes
|
|
19191
|
-
};
|
|
19192
|
-
this.setCredentials(registration.clientId, registration.clientSecret);
|
|
19193
|
-
log16.info("OAuth client registered", {
|
|
19194
|
-
clientId: sanitize(registration.clientId),
|
|
19195
|
-
clientSecret: sanitize(registration.clientSecret),
|
|
19196
|
-
scopes: registration.scopes,
|
|
19197
|
-
expiresAt: registration.clientSecretExpiresAt
|
|
19198
|
-
});
|
|
19199
|
-
return registration;
|
|
19200
|
-
}
|
|
19201
19087
|
/**
|
|
19202
|
-
* 获取访问令牌 → POST /
|
|
19088
|
+
* 获取访问令牌 → POST /auth/token (client_credentials)
|
|
19203
19089
|
*
|
|
19204
|
-
*
|
|
19205
|
-
*
|
|
19090
|
+
* 请求参数 (x-www-form-urlencoded):
|
|
19091
|
+
* - grant_type: client_credentials (写死)
|
|
19092
|
+
* - client_id: appId
|
|
19093
|
+
* - client_secret: appSecret
|
|
19094
|
+
* - scope: client_credentials refresh_token (写死)
|
|
19206
19095
|
*
|
|
19207
|
-
* @param scope - 请求的权限范围 (空格分隔),不传则使用注册时的全部 scope
|
|
19208
19096
|
* @returns TokenData 包含 access_token、过期时间等
|
|
19209
|
-
* @throws
|
|
19210
|
-
* @throws SealApiError — HTTP 错误
|
|
19097
|
+
* @throws ApiError — HTTP 错误
|
|
19211
19098
|
*/
|
|
19212
|
-
async getToken(
|
|
19213
|
-
if (!this.clientId || !this.clientSecret) {
|
|
19214
|
-
throw new Error("Cannot get token: clientId and clientSecret are required. Call register() or setCredentials() first.");
|
|
19215
|
-
}
|
|
19099
|
+
async getToken() {
|
|
19216
19100
|
const params = new URLSearchParams();
|
|
19217
|
-
params.set("grant_type",
|
|
19218
|
-
params.set("client_id", this.
|
|
19219
|
-
params.set("client_secret", this.
|
|
19220
|
-
|
|
19221
|
-
|
|
19222
|
-
}
|
|
19223
|
-
log16.info("Requesting access token", { clientId: sanitize(this.clientId) });
|
|
19101
|
+
params.set("grant_type", GRANT_TYPE);
|
|
19102
|
+
params.set("client_id", this.appId);
|
|
19103
|
+
params.set("client_secret", this.appSecret);
|
|
19104
|
+
params.set("scope", SCOPE);
|
|
19105
|
+
log16.info("Requesting access token", { appId: sanitize(this.appId) });
|
|
19224
19106
|
const response = await this.request(
|
|
19225
19107
|
"POST",
|
|
19226
|
-
"/
|
|
19108
|
+
"/auth/token",
|
|
19227
19109
|
{ body: params, contentType: "form" }
|
|
19228
19110
|
);
|
|
19229
19111
|
const now = Date.now();
|
|
@@ -19234,7 +19116,6 @@ var SealClient = class {
|
|
|
19234
19116
|
expiresIn,
|
|
19235
19117
|
scope: response.scope ?? "",
|
|
19236
19118
|
refreshToken: void 0,
|
|
19237
|
-
// Seal 不返回 refresh_token
|
|
19238
19119
|
grantedAt: now,
|
|
19239
19120
|
expiresAt: now + expiresIn * 1e3
|
|
19240
19121
|
};
|
|
@@ -19245,12 +19126,9 @@ var SealClient = class {
|
|
|
19245
19126
|
});
|
|
19246
19127
|
return tokenData;
|
|
19247
19128
|
}
|
|
19248
|
-
/**
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
*/
|
|
19252
|
-
async introspect(_token) {
|
|
19253
|
-
throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
|
|
19129
|
+
/** 获取 baseUrl (供其他模块复用) */
|
|
19130
|
+
getBaseUrl() {
|
|
19131
|
+
return this.baseUrl;
|
|
19254
19132
|
}
|
|
19255
19133
|
};
|
|
19256
19134
|
|
|
@@ -19258,7 +19136,7 @@ var SealClient = class {
|
|
|
19258
19136
|
var import_promises = require("fs/promises");
|
|
19259
19137
|
var import_node_fs = require("fs");
|
|
19260
19138
|
var import_node_path = require("path");
|
|
19261
|
-
var
|
|
19139
|
+
var import_node_crypto2 = require("crypto");
|
|
19262
19140
|
var log17 = createLogger("auth/token-store");
|
|
19263
19141
|
var TokenStore = class {
|
|
19264
19142
|
/** 存储目录路径 */
|
|
@@ -19314,7 +19192,7 @@ var TokenStore = class {
|
|
|
19314
19192
|
const storage = { ciphertext, keyId };
|
|
19315
19193
|
const storageJson = JSON.stringify(storage);
|
|
19316
19194
|
const targetPath = this.filePath(key);
|
|
19317
|
-
const tmpSuffix = (0,
|
|
19195
|
+
const tmpSuffix = (0, import_node_crypto2.randomBytes)(4).toString("hex");
|
|
19318
19196
|
const tmpPath = `${targetPath}.${tmpSuffix}.tmp`;
|
|
19319
19197
|
try {
|
|
19320
19198
|
await (0, import_promises.writeFile)(tmpPath, storageJson, { mode: 384 });
|
|
@@ -19384,12 +19262,10 @@ var DEFAULT_REFRESH_AHEAD_MS = 5 * 60 * 1e3;
|
|
|
19384
19262
|
var MAX_RETRIES = 3;
|
|
19385
19263
|
var RETRY_BASE_MS = 1e3;
|
|
19386
19264
|
var TokenManager = class {
|
|
19387
|
-
|
|
19265
|
+
oauthClient;
|
|
19388
19266
|
tokenStore;
|
|
19389
19267
|
/** Token 过期前提前刷新的时间 (ms) */
|
|
19390
19268
|
refreshAheadMs;
|
|
19391
|
-
/** 自动注册的参数 */
|
|
19392
|
-
autoRegisterParams;
|
|
19393
19269
|
// ── 内存缓存 ──
|
|
19394
19270
|
/** 内存中缓存的 access_token */
|
|
19395
19271
|
cachedToken = null;
|
|
@@ -19403,10 +19279,9 @@ var TokenManager = class {
|
|
|
19403
19279
|
/** 定时刷新器句柄 */
|
|
19404
19280
|
refreshTimer = null;
|
|
19405
19281
|
constructor(deps) {
|
|
19406
|
-
this.
|
|
19282
|
+
this.oauthClient = deps.oauthClient;
|
|
19407
19283
|
this.tokenStore = deps.tokenStore;
|
|
19408
19284
|
this.refreshAheadMs = deps.refreshAheadMs ?? DEFAULT_REFRESH_AHEAD_MS;
|
|
19409
|
-
this.autoRegisterParams = deps.autoRegisterParams;
|
|
19410
19285
|
log18.info("TokenManager initialized", { refreshAheadMs: this.refreshAheadMs });
|
|
19411
19286
|
}
|
|
19412
19287
|
// ── 核心方法 ──
|
|
@@ -19435,13 +19310,6 @@ var TokenManager = class {
|
|
|
19435
19310
|
this.refreshLock = null;
|
|
19436
19311
|
}
|
|
19437
19312
|
}
|
|
19438
|
-
/**
|
|
19439
|
-
* 令牌自省 — 暂不实现,后续补充。
|
|
19440
|
-
* 返回的 identity_id 用于 gate.ts 的 anti-loop 检查 (bot 自己的 ID)。
|
|
19441
|
-
*/
|
|
19442
|
-
async introspect() {
|
|
19443
|
-
throw new Error("introspect() not implemented yet \u2014 Seal introspect API \u6682\u4E0D\u4F7F\u7528");
|
|
19444
|
-
}
|
|
19445
19313
|
/** 检查当前令牌是否包含指定的 scope */
|
|
19446
19314
|
hasScope(scope) {
|
|
19447
19315
|
if (!this.cachedScope) return false;
|
|
@@ -19472,14 +19340,14 @@ var TokenManager = class {
|
|
|
19472
19340
|
*
|
|
19473
19341
|
* 重试策略:
|
|
19474
19342
|
* - 网络错误 / 5xx → 重试 (最多 3 次)
|
|
19475
|
-
* - 401/403 → 不重试 (
|
|
19343
|
+
* - 401/403 → 不重试 (凭证可能无效)
|
|
19476
19344
|
*/
|
|
19477
19345
|
async _acquireTokenWithRetry() {
|
|
19478
19346
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
19479
19347
|
try {
|
|
19480
19348
|
return await this._acquireToken();
|
|
19481
19349
|
} catch (err) {
|
|
19482
|
-
if (err instanceof
|
|
19350
|
+
if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
|
|
19483
19351
|
log18.error("Token acquire failed with auth error, not retrying", { status: err.status });
|
|
19484
19352
|
throw err;
|
|
19485
19353
|
}
|
|
@@ -19507,10 +19375,9 @@ var TokenManager = class {
|
|
|
19507
19375
|
*
|
|
19508
19376
|
* 流程:
|
|
19509
19377
|
* 1. 尝试从 TokenStore 加载缓存
|
|
19510
|
-
* 2.
|
|
19511
|
-
* 3.
|
|
19512
|
-
* 4.
|
|
19513
|
-
* 5. 设置定时刷新器
|
|
19378
|
+
* 2. 调用 OAuthClient.getToken() 获取新 Token
|
|
19379
|
+
* 3. 保存到内存缓存 + TokenStore
|
|
19380
|
+
* 4. 设置定时刷新器
|
|
19514
19381
|
*/
|
|
19515
19382
|
async _acquireToken() {
|
|
19516
19383
|
try {
|
|
@@ -19527,43 +19394,8 @@ var TokenManager = class {
|
|
|
19527
19394
|
} catch {
|
|
19528
19395
|
log18.warn("Failed to load token from store, acquiring new one");
|
|
19529
19396
|
}
|
|
19530
|
-
|
|
19531
|
-
|
|
19532
|
-
const savedCreds = await this.tokenStore.load("credentials");
|
|
19533
|
-
if (savedCreds?.clientId && savedCreds?.clientSecret) {
|
|
19534
|
-
this.sealClient.setCredentials(
|
|
19535
|
-
savedCreds.clientId,
|
|
19536
|
-
savedCreds.clientSecret
|
|
19537
|
-
);
|
|
19538
|
-
log18.info("Credentials loaded from store");
|
|
19539
|
-
}
|
|
19540
|
-
} catch {
|
|
19541
|
-
log18.debug("No saved credentials found");
|
|
19542
|
-
}
|
|
19543
|
-
}
|
|
19544
|
-
if (!this.sealClient.isRegistered()) {
|
|
19545
|
-
if (!this.autoRegisterParams) {
|
|
19546
|
-
throw new Error("SealClient not registered and autoRegisterParams not provided. Call setCredentials() or enable autoRegister.");
|
|
19547
|
-
}
|
|
19548
|
-
log18.info("Auto-registering OAuth client");
|
|
19549
|
-
const registration = await this.sealClient.register({
|
|
19550
|
-
clientName: this.autoRegisterParams.clientName,
|
|
19551
|
-
scopes: this.autoRegisterParams.scopes,
|
|
19552
|
-
tokenSettings: this.autoRegisterParams.tokenSettings ? {
|
|
19553
|
-
accessTokenTtl: this.autoRegisterParams.tokenSettings.accessTokenTtl,
|
|
19554
|
-
refreshTokenTtl: this.autoRegisterParams.tokenSettings.refreshTokenTtl,
|
|
19555
|
-
accessTokenFormat: this.autoRegisterParams.tokenSettings.accessTokenFormat
|
|
19556
|
-
} : void 0
|
|
19557
|
-
});
|
|
19558
|
-
await this.tokenStore.save("credentials", {
|
|
19559
|
-
clientId: registration.clientId,
|
|
19560
|
-
clientSecret: registration.clientSecret,
|
|
19561
|
-
clientSecretExpiresAt: registration.clientSecretExpiresAt
|
|
19562
|
-
});
|
|
19563
|
-
log18.info("OAuth client registered and credentials saved");
|
|
19564
|
-
}
|
|
19565
|
-
log18.info("Acquiring new access token");
|
|
19566
|
-
const tokenData = await this.sealClient.getToken();
|
|
19397
|
+
log18.info("Acquiring new access token via POST /auth/token");
|
|
19398
|
+
const tokenData = await this.oauthClient.getToken();
|
|
19567
19399
|
this.updateCache(tokenData);
|
|
19568
19400
|
await this.tokenStore.save("token", tokenData);
|
|
19569
19401
|
this.scheduleRefresh(tokenData);
|
|
@@ -19753,7 +19585,7 @@ var MessageDedup = class {
|
|
|
19753
19585
|
};
|
|
19754
19586
|
|
|
19755
19587
|
// src/transport/message-pipe.ts
|
|
19756
|
-
var
|
|
19588
|
+
var import_node_crypto3 = require("crypto");
|
|
19757
19589
|
var log21 = createLogger("transport/message-pipe");
|
|
19758
19590
|
var MessagePipe = class {
|
|
19759
19591
|
wsClient;
|
|
@@ -19901,7 +19733,7 @@ var MessagePipe = class {
|
|
|
19901
19733
|
if (timestamp && nonce && signature) {
|
|
19902
19734
|
const token = await this.tokenFn();
|
|
19903
19735
|
const signatureInput = timestamp + nonce + frame.data;
|
|
19904
|
-
const expected = (0,
|
|
19736
|
+
const expected = (0, import_node_crypto3.createHmac)("sha256", token).update(signatureInput).digest("hex");
|
|
19905
19737
|
if (expected !== signature) {
|
|
19906
19738
|
log21.warn("inbound:signature-fail", { timestamp, nonce });
|
|
19907
19739
|
return;
|
|
@@ -20469,28 +20301,17 @@ async function startPlugin(accountConfig, internalOverrides) {
|
|
|
20469
20301
|
cryptoEngine = CryptoEngine.createPassthrough();
|
|
20470
20302
|
log25.info("Crypto passthrough mode \u2713 (disabled by config)");
|
|
20471
20303
|
}
|
|
20472
|
-
const
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
clientSecret: config2.auth.clientSecret,
|
|
20477
|
-
scopes: config2.auth.scopes
|
|
20304
|
+
const oauthClient = new OAuthClient({
|
|
20305
|
+
baseUrl: config2.auth.serverUrl,
|
|
20306
|
+
appId: accountConfig.appId,
|
|
20307
|
+
appSecret: accountConfig.appSecret
|
|
20478
20308
|
});
|
|
20479
20309
|
const tokenStorePath = (0, import_node_path2.join)((0, import_node_os.homedir)(), TOKEN_STORE_DIR, "tokens");
|
|
20480
20310
|
const tokenStore = new TokenStore(tokenStorePath, cryptoEngine);
|
|
20481
20311
|
const tokenManager = new TokenManager({
|
|
20482
|
-
|
|
20312
|
+
oauthClient,
|
|
20483
20313
|
tokenStore,
|
|
20484
|
-
refreshAheadMs: config2.auth.refreshAheadMs
|
|
20485
|
-
autoRegisterParams: config2.auth.autoRegister ? {
|
|
20486
|
-
clientName: config2.auth.clientName,
|
|
20487
|
-
scopes: config2.auth.scopes,
|
|
20488
|
-
tokenSettings: config2.auth.tokenSettings ? {
|
|
20489
|
-
accessTokenTtl: config2.auth.tokenSettings.accessTokenTtl,
|
|
20490
|
-
refreshTokenTtl: config2.auth.tokenSettings.refreshTokenTtl,
|
|
20491
|
-
accessTokenFormat: config2.auth.tokenSettings.accessTokenFormat
|
|
20492
|
-
} : void 0
|
|
20493
|
-
} : void 0
|
|
20314
|
+
refreshAheadMs: config2.auth.refreshAheadMs
|
|
20494
20315
|
});
|
|
20495
20316
|
log25.info("Auth initialized \u2713");
|
|
20496
20317
|
let pushQueue = null;
|