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 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: randomBytes2, createHash } = require("crypto");
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 = randomBytes2(16).toString("base64");
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://seal.example.com",
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-adapter.ts
18816
- var import_node_module = require("module");
18817
- var import_meta = {};
18818
- var log14 = createLogger("crypto/quantun-plug-adapter");
18819
- var SM4_MODE = {
18820
- ECB_NOPADDING: 1,
18821
- ECB_PKCS7PADDING: 2,
18822
- CBC_NOPADDING: 3,
18823
- CBC_PKCS7PADDING: 4
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 DEFAULT_ENCRYPT_MODE = SM4_MODE.CBC_PKCS7PADDING;
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 实例, 透传模式下为 null */
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
- * @param passthrough - true 为透传模式 (不加密); false 为加密模式 (默认)
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(passthrough = false) {
18859
- this.passthrough = passthrough;
18860
- this.plug = passthrough ? null : quantun_plug_adapter_default;
18861
- this.initialized = passthrough;
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
- return new _CryptoEngine(true);
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 加密 (SM4_CBC_PKCS7PADDING), 返回密文 + keyId
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 plug = this.requirePlug();
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 { ciphertext: result.cipherText, keyId: result.keyId };
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 plug = this.requirePlug();
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.plainText;
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
- * 确保引擎已初始化,同时返回非空 plug 实例
19036
- * (通过 init() 守卫保证:加密模式下 init() 成功后 plug 一定非 null)
18996
+ * 确保引擎已初始化
18997
+ * 未初始化时抛出明确错误,避免难以排查的 SDK 内部异常
19037
18998
  */
19038
- requirePlug() {
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 SealApiError = class extends Error {
19008
+ var ApiError = class extends Error {
19049
19009
  constructor(status, body, endpoint) {
19050
- super(`Seal API error: ${status} on ${endpoint}`);
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 = "SealApiError";
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 SealClient = class {
19031
+ var GRANT_TYPE = "client_credentials";
19032
+ var SCOPE = "client_credentials refresh_token";
19033
+ var OAuthClient = class {
19072
19034
  baseUrl;
19073
- clientId;
19074
- clientSecret;
19075
- clientName;
19076
- scopes;
19035
+ appId;
19036
+ appSecret;
19077
19037
  constructor(config2) {
19078
- this.baseUrl = config2.serverUrl.replace(/\/+$/, "");
19079
- this.clientId = config2.clientId;
19080
- this.clientSecret = config2.clientSecret;
19081
- this.clientName = config2.clientName;
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 SealApiError(response.status, parsedBody, path2);
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 SealApiError(response.status, responseBody, path2);
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 /seal/oauth2/token (client_credentials)
19088
+ * 获取访问令牌 → POST /auth/token (client_credentials)
19203
19089
  *
19204
- * 使用客户端凭证模式获取 Access Token。
19205
- * 前置条件: clientId 和 clientSecret 必须存在 (通过 register 或 setCredentials 设置)
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 ErrorclientId/clientSecret 未设置
19210
- * @throws SealApiError — HTTP 错误
19097
+ * @throws ApiErrorHTTP 错误
19211
19098
  */
19212
- async getToken(scope) {
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", "client_credentials");
19218
- params.set("client_id", this.clientId);
19219
- params.set("client_secret", this.clientSecret);
19220
- if (scope) {
19221
- params.set("scope", scope);
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
- "/seal/oauth2/token",
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
- * 令牌校验 → GET /seal/oauth2/introspect
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 import_node_crypto = require("crypto");
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, import_node_crypto.randomBytes)(4).toString("hex");
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
- sealClient;
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.sealClient = deps.sealClient;
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 → 不重试 (client_secret 可能无效)
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 SealApiError && (err.status === 401 || err.status === 403)) {
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. 调用 SealClient.getToken() 获取新 Token
19512
- * 4. 保存到内存缓存 + TokenStore
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
- if (!this.sealClient.isRegistered()) {
19531
- try {
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 import_node_crypto2 = require("crypto");
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, import_node_crypto2.createHmac)("sha256", token).update(signatureInput).digest("hex");
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 sealClient = new SealClient({
20473
- serverUrl: config2.auth.serverUrl,
20474
- clientName: config2.auth.clientName,
20475
- clientId: config2.auth.clientId,
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
- sealClient,
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;