@agentunion/fastaun 0.3.2 → 0.3.4
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/CHANGELOG.md +43 -0
- package/_packed_docs/CHANGELOG.md +43 -0
- package/_packed_docs/INDEX.md +81 -0
- package/_packed_docs/KITE_DOCS_GUIDE.md +55 -0
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +328 -0
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -0
- package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -0
- package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +5 -5
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +454 -396
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1410 -1244
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +19 -1
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +20 -5
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
- package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -0
- package/_packed_docs/sdk/INDEX.md +9 -4
- package/_packed_docs/sdk/README.md +3 -3
- package/dist/auth.d.ts +44 -8
- package/dist/auth.js +398 -119
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +123 -19
- package/dist/client.js +2650 -673
- package/dist/client.js.map +1 -1
- package/dist/discovery.d.ts +4 -0
- package/dist/discovery.js +28 -13
- package/dist/discovery.js.map +1 -1
- package/dist/errors.d.ts +4 -0
- package/dist/errors.js +7 -0
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +9 -0
- package/dist/events.js +42 -12
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +4 -0
- package/dist/keystore/aid-db.js +94 -0
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/file.d.ts +23 -1
- package/dist/keystore/file.js +109 -1
- package/dist/keystore/file.js.map +1 -1
- package/dist/keystore/index.d.ts +20 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +7 -4
- package/dist/logger.js.map +1 -1
- package/dist/namespaces/auth.d.ts +34 -4
- package/dist/namespaces/auth.js +194 -51
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/net.d.ts +43 -0
- package/dist/net.js +192 -0
- package/dist/net.js.map +1 -0
- package/dist/secret-store/file-store.d.ts +21 -2
- package/dist/secret-store/file-store.js +166 -11
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +32 -3
- package/dist/seq-tracker.js +60 -3
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +2 -0
- package/dist/tools/cross-sdk-agent.js +695 -0
- package/dist/tools/cross-sdk-agent.js.map +1 -0
- package/dist/transport.d.ts +10 -1
- package/dist/transport.js +196 -32
- package/dist/transport.js.map +1 -1
- package/dist/v2/crypto/canonical.d.ts +1 -1
- package/dist/v2/crypto/canonical.js +42 -17
- package/dist/v2/crypto/canonical.js.map +1 -1
- package/dist/v2/e2ee/decrypt.js +57 -3
- package/dist/v2/e2ee/decrypt.js.map +1 -1
- package/dist/v2/e2ee/encrypt-group.js +16 -7
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +42 -9
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/metadata-auth.d.ts +1 -0
- package/dist/v2/e2ee/metadata-auth.js +37 -1
- package/dist/v2/e2ee/metadata-auth.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +2 -2
- package/dist/v2/session/keystore.d.ts +10 -3
- package/dist/v2/session/keystore.js +158 -30
- package/dist/v2/session/keystore.js.map +1 -1
- package/dist/v2/session/session.d.ts +7 -3
- package/dist/v2/session/session.js +64 -12
- package/dist/v2/session/session.js.map +1 -1
- package/package.json +46 -46
package/dist/auth.js
CHANGED
|
@@ -17,7 +17,7 @@ import * as https from 'node:https';
|
|
|
17
17
|
import * as path from 'node:path';
|
|
18
18
|
import { URL, fileURLToPath } from 'node:url';
|
|
19
19
|
import WebSocket from 'ws';
|
|
20
|
-
import { AuthError, StateError, ValidationError,
|
|
20
|
+
import { AuthError, StateError, ValidationError, IdentityConflictError, mapRemoteError } from './errors.js';
|
|
21
21
|
import { isJsonObject, } from './types.js';
|
|
22
22
|
const _noopLogger = {
|
|
23
23
|
error: () => { },
|
|
@@ -25,6 +25,8 @@ const _noopLogger = {
|
|
|
25
25
|
info: () => { },
|
|
26
26
|
debug: () => { },
|
|
27
27
|
};
|
|
28
|
+
const AUN_SDK_LANG = 'typescript';
|
|
29
|
+
const AUN_SDK_VERSION = '0.3.4';
|
|
28
30
|
// ── 签名验证辅助 ──────────────────────────────────────────────
|
|
29
31
|
/**
|
|
30
32
|
* 验证签名:支持 ECDSA P-256 (SHA256)、ECDSA P-384 (SHA384)、Ed25519。
|
|
@@ -159,8 +161,11 @@ function _gatewayHttpUrl(gatewayUrl, urlPath) {
|
|
|
159
161
|
return `${scheme}//${parsed.host}${urlPath}`;
|
|
160
162
|
}
|
|
161
163
|
/** 发起 HTTP GET 请求,返回文本内容 */
|
|
162
|
-
async function _fetchText(url, verifySsl) {
|
|
164
|
+
async function _fetchText(url, verifySsl, net) {
|
|
163
165
|
try {
|
|
166
|
+
if (net) {
|
|
167
|
+
return await net.httpGetText(url, 5_000);
|
|
168
|
+
}
|
|
164
169
|
return await _httpGet(url, verifySsl);
|
|
165
170
|
}
|
|
166
171
|
catch (err) {
|
|
@@ -168,8 +173,8 @@ async function _fetchText(url, verifySsl) {
|
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
175
|
/** 发起 HTTP GET 请求,返回 JSON 对象 */
|
|
171
|
-
async function _fetchJson(url, verifySsl) {
|
|
172
|
-
const text = await _fetchText(url, verifySsl);
|
|
176
|
+
async function _fetchJson(url, verifySsl, net) {
|
|
177
|
+
const text = await _fetchText(url, verifySsl, net);
|
|
173
178
|
try {
|
|
174
179
|
const payload = JSON.parse(text);
|
|
175
180
|
if (!isJsonObject(payload)) {
|
|
@@ -237,6 +242,7 @@ export class AuthFlow {
|
|
|
237
242
|
_deviceId;
|
|
238
243
|
_slotId;
|
|
239
244
|
_verifySsl;
|
|
245
|
+
_net;
|
|
240
246
|
_connectionFactory;
|
|
241
247
|
_rootCaPath;
|
|
242
248
|
_chainCacheTtl;
|
|
@@ -261,6 +267,7 @@ export class AuthFlow {
|
|
|
261
267
|
this._slotId = String(opts.slotId ?? '').trim();
|
|
262
268
|
this._connectionFactory = opts.connectionFactory ?? _defaultConnectionFactory;
|
|
263
269
|
this._rootCaPath = opts.rootCaPath ?? null;
|
|
270
|
+
this._net = opts.net ?? null;
|
|
264
271
|
this._verifySsl = opts.verifySsl ?? false;
|
|
265
272
|
this._chainCacheTtl = (opts.chainCacheTtl ?? 86400) * 1000; // 转为毫秒
|
|
266
273
|
this._rootCerts = this._loadRootCerts(this._rootCaPath);
|
|
@@ -305,51 +312,240 @@ export class AuthFlow {
|
|
|
305
312
|
this._slotId = String(opts.slotId ?? '').trim();
|
|
306
313
|
}
|
|
307
314
|
/**
|
|
308
|
-
*
|
|
309
|
-
*
|
|
315
|
+
* 注册新 AID(原子流程,必须由应用层显式调用)。
|
|
316
|
+
*
|
|
317
|
+
* 安全约束(与 Python register_aid 对齐):
|
|
318
|
+
* - **绝不**被 SDK 内部任何路径自动调用(authenticate / connect / 等)
|
|
319
|
+
* - 本地已有任何痕迹 → 抛 IdentityConflictError,应用层应改用 loadIdentity
|
|
320
|
+
* - 服务端已注册同名 AID → 抛 IdentityConflictError
|
|
321
|
+
*
|
|
322
|
+
* 异常分支与恢复:
|
|
323
|
+
* A. 全新流程:临时目录生成 keypair → RPC → cert → 原子 rename
|
|
324
|
+
* B. 恢复流程:上次 RPC 没收到响应就崩溃;扫描 _pending,公钥匹配
|
|
325
|
+
* 则下载 cert 完成 promote(防止"已注册但本地没保存")
|
|
310
326
|
*/
|
|
311
|
-
async
|
|
327
|
+
async registerAid(gatewayUrl, aid) {
|
|
312
328
|
const tStart = Date.now();
|
|
313
329
|
AuthFlow._validateAidName(aid);
|
|
314
|
-
this._logger.debug(`
|
|
330
|
+
this._logger.debug(`registerAid enter: aid=${aid}, gateway=${gatewayUrl}`);
|
|
315
331
|
try {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
332
|
+
// Step 1: 本地完整身份的幂等处理
|
|
333
|
+
// - 服务端 cert 公钥匹配 → 视为已注册成功,幂等返回
|
|
334
|
+
// - 服务端 cert 公钥不匹配 → IdentityConflictError(被别人占)
|
|
335
|
+
// - 服务端无记录 → IdentityConflictError(要求清理本地)
|
|
336
|
+
const existing = this._keystore.loadIdentity(aid);
|
|
337
|
+
if (existing !== null && existing.public_key_der_b64) {
|
|
338
|
+
this._logger.debug(`registerAid: local keypair exists, checking server for idempotency: aid=${aid}`);
|
|
339
|
+
const localPubB64 = String(existing.public_key_der_b64);
|
|
340
|
+
const serverCertPem = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
341
|
+
if (!serverCertPem) {
|
|
342
|
+
// 服务端无记录 → 用现有 keypair 发起注册
|
|
343
|
+
this._logger.debug(`registerAid: server has no record, registering with existing keypair: aid=${aid}`);
|
|
344
|
+
const pendingDir = this._keystore.pendingIdentityDir?.(aid);
|
|
345
|
+
let created;
|
|
346
|
+
try {
|
|
347
|
+
created = await this._createAid(gatewayUrl, existing);
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
this._logger.warn(`registerAid RPC failed (existing keypair): aid=${aid}, error=${e instanceof Error ? e.message : String(e)}`);
|
|
351
|
+
throw e;
|
|
352
|
+
}
|
|
353
|
+
const certPem = String(created.cert ?? '');
|
|
354
|
+
if (!certPem) {
|
|
355
|
+
throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
356
|
+
}
|
|
357
|
+
existing.cert = certPem;
|
|
358
|
+
this._assertCertMatchesLocalKeypair(existing);
|
|
359
|
+
this._persistIdentity(existing);
|
|
360
|
+
this._aid = aid;
|
|
361
|
+
this._logger.debug(`registerAid exit (recovered): elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
362
|
+
return { aid, cert: certPem };
|
|
363
|
+
}
|
|
364
|
+
// 比对公钥
|
|
365
|
+
const cert = _loadX509(serverCertPem);
|
|
366
|
+
const serverPubDer = _extractPublicKey(cert).export({ type: 'spki', format: 'der' });
|
|
367
|
+
const localPubDer = Buffer.from(localPubB64, 'base64');
|
|
368
|
+
if (!serverPubDer.equals(localPubDer)) {
|
|
369
|
+
throw new IdentityConflictError(`AID '${aid}' is registered by another party on server (public key mismatch). ` +
|
|
370
|
+
`Choose a different name.`);
|
|
371
|
+
}
|
|
372
|
+
// 公钥匹配 → 幂等返回;如本地缺 cert,把服务端 cert 写入
|
|
373
|
+
this._logger.info(`registerAid: idempotent return for already-registered AID: aid=${aid}`);
|
|
374
|
+
if (!existing.cert) {
|
|
375
|
+
existing.cert = serverCertPem;
|
|
376
|
+
this._persistIdentity(existing);
|
|
377
|
+
}
|
|
378
|
+
this._aid = aid;
|
|
379
|
+
return { aid, cert: serverCertPem };
|
|
380
|
+
}
|
|
381
|
+
// Step 2: 检查 _pending/ 残留临时目录(崩溃恢复)
|
|
382
|
+
const recovered = await this._tryRecoverPendingRegistration(gatewayUrl, aid);
|
|
383
|
+
if (recovered !== null) {
|
|
384
|
+
this._logger.info(`registerAid recovered from pending: aid=${aid}`);
|
|
385
|
+
return recovered;
|
|
386
|
+
}
|
|
387
|
+
// Step 3: 服务端查重
|
|
388
|
+
const existingCert = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
389
|
+
if (existingCert) {
|
|
390
|
+
this._logger.warn(`registerAid aborted: AID already registered on server: aid=${aid}`);
|
|
391
|
+
throw new IdentityConflictError(`AID '${aid}' is already registered on server. Choose a different name, or if you own the keypair use a recovery flow.`);
|
|
392
|
+
}
|
|
393
|
+
// Step 4: 创建临时目录 + 生成 keypair + 写 priv
|
|
394
|
+
const identity = this._crypto.generateIdentity();
|
|
395
|
+
identity.aid = aid;
|
|
396
|
+
const pendingDir = this._keystore.pendingIdentityDir(aid);
|
|
397
|
+
this._writePendingKeypair(pendingDir, identity);
|
|
398
|
+
// Step 5: 服务端注册拿 cert
|
|
399
|
+
let created;
|
|
400
|
+
try {
|
|
401
|
+
created = await this._createAid(gatewayUrl, identity);
|
|
402
|
+
}
|
|
403
|
+
catch (e) {
|
|
404
|
+
this._logger.warn(`registerAid RPC failed (pending kept for recovery): aid=${aid}, pending=${pendingDir}, error=${e instanceof Error ? e.message : String(e)}`);
|
|
405
|
+
throw e;
|
|
320
406
|
}
|
|
321
|
-
|
|
407
|
+
const certPem = String(created.cert ?? '');
|
|
408
|
+
if (!certPem) {
|
|
409
|
+
throw new AuthError(`registerAid: server response missing cert for ${aid}`);
|
|
410
|
+
}
|
|
411
|
+
identity.cert = certPem;
|
|
412
|
+
// Step 6: 校验 cert 公钥 == 本地公钥
|
|
413
|
+
this._assertCertMatchesLocalKeypair(identity);
|
|
414
|
+
// Step 7: 写 cert.pem 到临时目录
|
|
415
|
+
this._writePendingCert(pendingDir, certPem);
|
|
416
|
+
// Step 8: 原子 rename
|
|
322
417
|
try {
|
|
323
|
-
|
|
324
|
-
Object.assign(identity, created);
|
|
325
|
-
this._logger.debug(`AID register ok: aid=${aid}`);
|
|
418
|
+
this._keystore.promotePendingIdentity(pendingDir, aid);
|
|
326
419
|
}
|
|
327
420
|
catch (e) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
throw e;
|
|
331
|
-
}
|
|
332
|
-
// AID 已在服务端注册,尝试从 PKI 下载恢复证书
|
|
333
|
-
try {
|
|
334
|
-
identity = await this._recoverCertViaDownload(gatewayUrl, identity);
|
|
335
|
-
}
|
|
336
|
-
catch {
|
|
337
|
-
throw new StateError(`AID ${aid} already registered on server but local certificate is missing. ` +
|
|
338
|
-
`Certificate download recovery failed. Options: ` +
|
|
339
|
-
`(1) use a different AID name, or ` +
|
|
340
|
-
`(2) restart Kite server to clear registration.`);
|
|
341
|
-
}
|
|
421
|
+
this._logger.warn(`registerAid promote failed: aid=${aid}, error=${e instanceof Error ? e.message : String(e)}`);
|
|
422
|
+
throw new IdentityConflictError(`AID '${aid}' was created by another process during registration; pending dir kept for cleanup.`);
|
|
342
423
|
}
|
|
424
|
+
// Step 9: 标准持久化
|
|
343
425
|
this._persistIdentity(identity);
|
|
344
|
-
this._aid =
|
|
345
|
-
this._logger.debug(`
|
|
426
|
+
this._aid = aid;
|
|
427
|
+
this._logger.debug(`registerAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
346
428
|
return { aid: identity.aid, cert: identity.cert };
|
|
347
429
|
}
|
|
348
430
|
catch (err) {
|
|
349
|
-
this._logger.debug(`
|
|
431
|
+
this._logger.debug(`registerAid exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
350
432
|
throw err;
|
|
351
433
|
}
|
|
352
434
|
}
|
|
435
|
+
/**
|
|
436
|
+
* 检查 AIDs/_pending/ 下是否有该 aid 的残留,尝试恢复(崩溃恢复)。
|
|
437
|
+
* 返回 dict 表示恢复成功;null 表示无残留 / 残留已清理;抛错表示明确失败。
|
|
438
|
+
*/
|
|
439
|
+
async _tryRecoverPendingRegistration(gatewayUrl, aid) {
|
|
440
|
+
const path = require('node:path');
|
|
441
|
+
const fs = require('node:fs');
|
|
442
|
+
const aidsRoot = this._keystore._aidsRoot;
|
|
443
|
+
const pendingRoot = path.join(aidsRoot, '_pending');
|
|
444
|
+
if (!fs.existsSync(pendingRoot))
|
|
445
|
+
return null;
|
|
446
|
+
const safe = this._keystore.constructor === undefined ? aid : aid;
|
|
447
|
+
const safeAid = aid.replace(/\//g, '_').replace(/\\/g, '_').replace(/:/g, '_');
|
|
448
|
+
const prefix = safeAid + '-';
|
|
449
|
+
const candidates = fs.readdirSync(pendingRoot, { withFileTypes: true })
|
|
450
|
+
.filter((e) => e.isDirectory() && e.name.startsWith(prefix))
|
|
451
|
+
.map((e) => path.join(pendingRoot, e.name))
|
|
452
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
453
|
+
if (candidates.length === 0)
|
|
454
|
+
return null;
|
|
455
|
+
for (const pendingDir of candidates) {
|
|
456
|
+
const privPath = path.join(pendingDir, 'private', 'key.json');
|
|
457
|
+
if (!fs.existsSync(privPath)) {
|
|
458
|
+
try {
|
|
459
|
+
fs.rmSync(pendingDir, { recursive: true, force: true });
|
|
460
|
+
}
|
|
461
|
+
catch { /* ignore */ }
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
let privData;
|
|
465
|
+
try {
|
|
466
|
+
privData = JSON.parse(fs.readFileSync(privPath, 'utf-8'));
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
try {
|
|
470
|
+
fs.rmSync(pendingDir, { recursive: true, force: true });
|
|
471
|
+
}
|
|
472
|
+
catch { /* ignore */ }
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
const localPriv = privData.private_key_pem ?? '';
|
|
476
|
+
const localPubB64 = privData.public_key_der_b64 ?? '';
|
|
477
|
+
if (!localPriv || !localPubB64) {
|
|
478
|
+
try {
|
|
479
|
+
fs.rmSync(pendingDir, { recursive: true, force: true });
|
|
480
|
+
}
|
|
481
|
+
catch { /* ignore */ }
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const serverCert = await this._downloadRegisteredCert(gatewayUrl, aid);
|
|
485
|
+
if (!serverCert) {
|
|
486
|
+
// 上次 RPC 没生效 → 清理临时目录,由调用方走全新注册流程
|
|
487
|
+
this._logger.info(`pending dir found but server has no registration; cleaning up: ${pendingDir}`);
|
|
488
|
+
try {
|
|
489
|
+
fs.rmSync(pendingDir, { recursive: true, force: true });
|
|
490
|
+
}
|
|
491
|
+
catch { /* ignore */ }
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
// 比对公钥
|
|
495
|
+
const cert = _loadX509(serverCert);
|
|
496
|
+
const certPubKey = _extractPublicKey(cert);
|
|
497
|
+
const certPubDer = certPubKey.export({ type: 'spki', format: 'der' });
|
|
498
|
+
const localPubDer = Buffer.from(localPubB64, 'base64');
|
|
499
|
+
if (!certPubDer.equals(localPubDer)) {
|
|
500
|
+
this._logger.warn(`pending dir public key does not match server cert; AID '${aid}' was taken`);
|
|
501
|
+
try {
|
|
502
|
+
fs.rmSync(pendingDir, { recursive: true, force: true });
|
|
503
|
+
}
|
|
504
|
+
catch { /* ignore */ }
|
|
505
|
+
throw new IdentityConflictError(`AID '${aid}' has been registered by another party while local pending registration was incomplete; local pending key discarded.`);
|
|
506
|
+
}
|
|
507
|
+
// 公钥匹配 → 上次 RPC 成功,补 cert + promote
|
|
508
|
+
this._logger.info(`pending recovery: cert public key matches; finalizing registration: aid=${aid}`);
|
|
509
|
+
this._writePendingCert(pendingDir, serverCert);
|
|
510
|
+
const identity = {
|
|
511
|
+
aid,
|
|
512
|
+
private_key_pem: localPriv,
|
|
513
|
+
public_key_der_b64: localPubB64,
|
|
514
|
+
curve: privData.curve ?? 'P-256',
|
|
515
|
+
cert: serverCert,
|
|
516
|
+
};
|
|
517
|
+
try {
|
|
518
|
+
this._keystore.promotePendingIdentity(pendingDir, aid);
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
throw new IdentityConflictError(`AID '${aid}' was created by another process during recovery; pending dir kept for cleanup.`);
|
|
522
|
+
}
|
|
523
|
+
this._persistIdentity(identity);
|
|
524
|
+
this._aid = aid;
|
|
525
|
+
return { aid, cert: serverCert };
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
_writePendingKeypair(pendingDir, identity) {
|
|
530
|
+
const fs = require('node:fs');
|
|
531
|
+
const path = require('node:path');
|
|
532
|
+
const priv = String(identity.private_key_pem ?? '');
|
|
533
|
+
const pub = String(identity.public_key_der_b64 ?? '');
|
|
534
|
+
const curve = String(identity.curve ?? 'P-256');
|
|
535
|
+
if (!priv || !pub) {
|
|
536
|
+
throw new AuthError('registerAid: generated identity missing keypair fields');
|
|
537
|
+
}
|
|
538
|
+
const dir = path.join(pendingDir, 'private');
|
|
539
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
540
|
+
fs.writeFileSync(path.join(dir, 'key.json'), JSON.stringify({ private_key_pem: priv, public_key_der_b64: pub, curve }, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
541
|
+
}
|
|
542
|
+
_writePendingCert(pendingDir, certPem) {
|
|
543
|
+
const fs = require('node:fs');
|
|
544
|
+
const path = require('node:path');
|
|
545
|
+
const dir = path.join(pendingDir, 'public');
|
|
546
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
547
|
+
fs.writeFileSync(path.join(dir, 'cert.pem'), certPem, { encoding: 'utf-8', mode: 0o600 });
|
|
548
|
+
}
|
|
353
549
|
/**
|
|
354
550
|
* 认证(登录)到 Gateway。
|
|
355
551
|
* 执行两阶段挑战-应答认证,返回 token 信息。
|
|
@@ -368,15 +564,24 @@ export class AuthFlow {
|
|
|
368
564
|
const cachedToken = AuthFlow._getCachedAccessToken(identityWithState);
|
|
369
565
|
const cachedRefresh = String(identityWithState.refresh_token ?? '');
|
|
370
566
|
if (cachedToken && cachedRefresh) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
567
|
+
// 复用 cached token 前必须验证本地身份完整性:
|
|
568
|
+
// cert 存在 + cert 公钥 == keypair 公钥 + cert 时间窗口有效。
|
|
569
|
+
// 任何一项不通过 → 不复用,走两步重登(或抛错)。
|
|
570
|
+
const credIssue = this._validateCachedCredentials(identity);
|
|
571
|
+
if (!credIssue) {
|
|
572
|
+
this._logger.debug(`authenticate reusing cached token: aid=${identity.aid} expires_at=${identityWithState.access_token_expires_at}`);
|
|
573
|
+
this._aid = String(identity.aid);
|
|
574
|
+
return {
|
|
575
|
+
aid: identity.aid,
|
|
576
|
+
access_token: cachedToken,
|
|
577
|
+
refresh_token: cachedRefresh,
|
|
578
|
+
expires_at: identityWithState.access_token_expires_at,
|
|
579
|
+
gateway: gatewayUrl,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
this._logger.info(`cached token not reused (credential issue): aid=${identity.aid} reason=${credIssue}`);
|
|
584
|
+
}
|
|
380
585
|
}
|
|
381
586
|
if (!identity.cert) {
|
|
382
587
|
// 本地有密钥但无证书——尝试从 PKI 下载恢复
|
|
@@ -386,26 +591,20 @@ export class AuthFlow {
|
|
|
386
591
|
}
|
|
387
592
|
catch (e) {
|
|
388
593
|
throw new StateError(`local certificate missing and recovery failed: ${e instanceof Error ? e.message : String(e)}. ` +
|
|
389
|
-
`Run auth.
|
|
594
|
+
`Run auth.registerAid() to register a new identity.`);
|
|
390
595
|
}
|
|
391
596
|
}
|
|
597
|
+
// 防线 B:发起两步登录前显式校验 cert 公钥与本地 keypair 公钥一致。
|
|
598
|
+
this._assertCertMatchesLocalKeypair(identity);
|
|
392
599
|
let login;
|
|
393
600
|
try {
|
|
394
601
|
login = await this._login(gatewayUrl, identity);
|
|
395
602
|
this._logger.debug(`auth login ok: aid=${identity.aid}`);
|
|
396
603
|
}
|
|
397
604
|
catch (e) {
|
|
398
|
-
//
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const created = await this._createAid(gatewayUrl, identity);
|
|
402
|
-
identity.cert = created.cert;
|
|
403
|
-
this._persistIdentity(identity);
|
|
404
|
-
login = await this._login(gatewayUrl, identity);
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
throw e;
|
|
408
|
-
}
|
|
605
|
+
// 注册和登录彻底分离:登录失败绝不触发自动注册。
|
|
606
|
+
// 服务端报"not registered"时,应用层应当显式调 registerAid。
|
|
607
|
+
throw e;
|
|
409
608
|
}
|
|
410
609
|
AuthFlow._rememberTokens(identity, login);
|
|
411
610
|
await this._validateNewCert(identity, gatewayUrl);
|
|
@@ -433,12 +632,14 @@ export class AuthFlow {
|
|
|
433
632
|
const tStart = Date.now();
|
|
434
633
|
this._logger.debug(`ensureAuthenticated enter: gateway=${gatewayUrl}`);
|
|
435
634
|
try {
|
|
436
|
-
|
|
635
|
+
// 注册和登录彻底分离:无身份直接抛错,绝不再隐式生成密钥。
|
|
636
|
+
const identity = this._loadIdentityOrRaise();
|
|
437
637
|
if (!identity.cert) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
this._persistIdentity(identity);
|
|
638
|
+
throw new StateError(`local identity for aid ${identity.aid} has no certificate; ` +
|
|
639
|
+
`call auth.authenticate() to attempt cert recovery, or auth.registerAid() if this is a fresh registration.`);
|
|
441
640
|
}
|
|
641
|
+
// 防线 B:发起两步登录前显式校验
|
|
642
|
+
this._assertCertMatchesLocalKeypair(identity);
|
|
442
643
|
const login = await this._login(gatewayUrl, identity);
|
|
443
644
|
AuthFlow._rememberTokens(identity, login);
|
|
444
645
|
await this._validateNewCert(identity, gatewayUrl);
|
|
@@ -681,6 +882,25 @@ export class AuthFlow {
|
|
|
681
882
|
throw err;
|
|
682
883
|
}
|
|
683
884
|
}
|
|
885
|
+
/** 预置 Gateway CA 链材料,但不标记为已验证。 */
|
|
886
|
+
cacheGatewayCaChain(gatewayUrl, caChainPems, chainAid = '') {
|
|
887
|
+
const normalized = caChainPems
|
|
888
|
+
.map((pem) => String(pem ?? '').trim())
|
|
889
|
+
.filter((pem) => pem.length > 0);
|
|
890
|
+
if (normalized.length === 0)
|
|
891
|
+
return;
|
|
892
|
+
for (const pem of normalized)
|
|
893
|
+
_loadX509(pem);
|
|
894
|
+
const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
|
|
895
|
+
this._gatewayChainCache.set(cacheKey, normalized);
|
|
896
|
+
this._gatewayCaVerified.delete(cacheKey);
|
|
897
|
+
}
|
|
898
|
+
/** 丢弃预置或缓存的 Gateway CA 链材料。 */
|
|
899
|
+
discardGatewayCaChain(gatewayUrl, chainAid = '') {
|
|
900
|
+
const cacheKey = chainAid ? `${gatewayUrl}:${chainAid}` : gatewayUrl;
|
|
901
|
+
this._gatewayChainCache.delete(cacheKey);
|
|
902
|
+
this._gatewayCaVerified.delete(cacheKey);
|
|
903
|
+
}
|
|
684
904
|
// ── 内部方法:短连接 RPC ────────────────────────────────────
|
|
685
905
|
/**
|
|
686
906
|
* 通过临时 WebSocket 发送单次 JSON-RPC 请求。
|
|
@@ -840,7 +1060,11 @@ export class AuthFlow {
|
|
|
840
1060
|
auth: { method: 'kite_token', token },
|
|
841
1061
|
protocol: { min: '1.0', max: '1.0' },
|
|
842
1062
|
device: { id: String(opts?.deviceId ?? ''), type: 'sdk' },
|
|
843
|
-
client: {
|
|
1063
|
+
client: {
|
|
1064
|
+
slot_id: String(opts?.slotId ?? ''),
|
|
1065
|
+
sdk_lang: AUN_SDK_LANG,
|
|
1066
|
+
sdk_version: AUN_SDK_VERSION,
|
|
1067
|
+
},
|
|
844
1068
|
delivery_mode: opts?.deliveryMode ?? { mode: 'fanout' },
|
|
845
1069
|
capabilities,
|
|
846
1070
|
};
|
|
@@ -1016,7 +1240,7 @@ export class AuthFlow {
|
|
|
1016
1240
|
/** 从 Gateway PKI 端点获取 CA 链 */
|
|
1017
1241
|
async _fetchGatewayCaChain(gatewayUrl, _chainAid = '') {
|
|
1018
1242
|
const url = _gatewayHttpUrl(gatewayUrl, '/pki/chain');
|
|
1019
|
-
const text = await _fetchText(url, this._verifySsl);
|
|
1243
|
+
const text = await _fetchText(url, this._verifySsl, this._net);
|
|
1020
1244
|
return _splitPemBundle(text);
|
|
1021
1245
|
}
|
|
1022
1246
|
/**
|
|
@@ -1069,7 +1293,7 @@ export class AuthFlow {
|
|
|
1069
1293
|
*/
|
|
1070
1294
|
async _fetchGatewayCrl(gatewayUrl, _issuerCert) {
|
|
1071
1295
|
const url = _gatewayHttpUrl(gatewayUrl, '/pki/crl.json');
|
|
1072
|
-
const payload = await _fetchJson(url, this._verifySsl);
|
|
1296
|
+
const payload = await _fetchJson(url, this._verifySsl, this._net);
|
|
1073
1297
|
const crlPem = String(payload.crl_pem || '');
|
|
1074
1298
|
if (!crlPem) {
|
|
1075
1299
|
throw new AuthError('gateway CRL endpoint returned no signed CRL');
|
|
@@ -1246,7 +1470,7 @@ export class AuthFlow {
|
|
|
1246
1470
|
async _fetchGatewayOcspStatus(gatewayUrl, authCert, issuerCert) {
|
|
1247
1471
|
const serialHex = _certSerialHex(authCert);
|
|
1248
1472
|
const url = _gatewayHttpUrl(gatewayUrl, `/pki/ocsp/${serialHex}`);
|
|
1249
|
-
const payload = await _fetchJson(url, this._verifySsl);
|
|
1473
|
+
const payload = await _fetchJson(url, this._verifySsl, this._net);
|
|
1250
1474
|
const status = String(payload.status || '');
|
|
1251
1475
|
const ocspB64 = String(payload.ocsp_response || '');
|
|
1252
1476
|
if (!ocspB64) {
|
|
@@ -1475,9 +1699,8 @@ export class AuthFlow {
|
|
|
1475
1699
|
* 本地有密钥但无证书、服务端已注册时使用。
|
|
1476
1700
|
*/
|
|
1477
1701
|
async _recoverCertViaDownload(gatewayUrl, identity) {
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1480
|
-
if (!certPem || !certPem.includes('BEGIN CERTIFICATE')) {
|
|
1702
|
+
const certPem = await this._downloadRegisteredCert(gatewayUrl, String(identity.aid));
|
|
1703
|
+
if (!certPem) {
|
|
1481
1704
|
throw new AuthError(`failed to download certificate for ${identity.aid}`);
|
|
1482
1705
|
}
|
|
1483
1706
|
// 验证下载的证书公钥与本地密钥对匹配
|
|
@@ -1492,6 +1715,93 @@ export class AuthFlow {
|
|
|
1492
1715
|
identity.cert = certPem;
|
|
1493
1716
|
return identity;
|
|
1494
1717
|
}
|
|
1718
|
+
/**
|
|
1719
|
+
* 验证本地身份是否适合复用 cached token(不走网络)。
|
|
1720
|
+
* 返回空字符串表示通过;非空字符串表示不通过的原因。
|
|
1721
|
+
* 公钥不匹配时直接抛 AuthError(严重安全问题,不能静默降级)。
|
|
1722
|
+
*/
|
|
1723
|
+
_validateCachedCredentials(identity) {
|
|
1724
|
+
const aid = identity.aid ?? '?';
|
|
1725
|
+
const certPem = identity.cert;
|
|
1726
|
+
const localPubB64 = identity.public_key_der_b64;
|
|
1727
|
+
if (!certPem)
|
|
1728
|
+
return 'no certificate';
|
|
1729
|
+
if (!localPubB64)
|
|
1730
|
+
return 'no public key';
|
|
1731
|
+
try {
|
|
1732
|
+
const cert = _loadX509(certPem);
|
|
1733
|
+
const certPubKey = _extractPublicKey(cert);
|
|
1734
|
+
const certPubDer = certPubKey.export({ type: 'spki', format: 'der' });
|
|
1735
|
+
const localPubDer = Buffer.from(localPubB64, 'base64');
|
|
1736
|
+
if (!certPubDer.equals(localPubDer)) {
|
|
1737
|
+
throw new AuthError(`local certificate public key does not match local keypair for aid ${aid}; ` +
|
|
1738
|
+
`refusing to authenticate. Run auth.registerAid() to repair identity.`);
|
|
1739
|
+
}
|
|
1740
|
+
// cert 时间窗口
|
|
1741
|
+
const now = Date.now();
|
|
1742
|
+
const validFrom = new Date(cert.validFrom).getTime();
|
|
1743
|
+
const validTo = new Date(cert.validTo).getTime();
|
|
1744
|
+
if (now < validFrom)
|
|
1745
|
+
return 'cert not yet valid';
|
|
1746
|
+
if (now > validTo)
|
|
1747
|
+
return 'cert expired';
|
|
1748
|
+
return '';
|
|
1749
|
+
}
|
|
1750
|
+
catch (e) {
|
|
1751
|
+
if (e instanceof AuthError)
|
|
1752
|
+
throw e;
|
|
1753
|
+
return `cert/keypair parse error: ${e instanceof Error ? e.message : String(e)}`;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
/**
|
|
1757
|
+
* 防线 B:authenticate 在调 _login 前显式校验 cert 与本地 keypair 公钥一致。
|
|
1758
|
+
* 命中即抛 AuthError,绝不发起两步登录。
|
|
1759
|
+
*/
|
|
1760
|
+
_assertCertMatchesLocalKeypair(identity) {
|
|
1761
|
+
const aid = identity.aid ?? '?';
|
|
1762
|
+
const certPem = identity.cert;
|
|
1763
|
+
const localPubB64 = identity.public_key_der_b64;
|
|
1764
|
+
if (!certPem || !localPubB64) {
|
|
1765
|
+
throw new AuthError(`identity for aid ${aid} missing cert or public key; refusing to start two-phase login`);
|
|
1766
|
+
}
|
|
1767
|
+
let certPubDer;
|
|
1768
|
+
let localPubDer;
|
|
1769
|
+
try {
|
|
1770
|
+
const cert = _loadX509(certPem);
|
|
1771
|
+
const certPubKey = _extractPublicKey(cert);
|
|
1772
|
+
certPubDer = certPubKey.export({ type: 'spki', format: 'der' });
|
|
1773
|
+
localPubDer = Buffer.from(localPubB64, 'base64');
|
|
1774
|
+
}
|
|
1775
|
+
catch (e) {
|
|
1776
|
+
throw new AuthError(`failed to parse local cert/keypair for aid ${aid}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1777
|
+
}
|
|
1778
|
+
if (!certPubDer.equals(localPubDer)) {
|
|
1779
|
+
throw new AuthError(`local certificate public key does not match local keypair for aid ${aid}; ` +
|
|
1780
|
+
`refusing to start two-phase login. Run auth.registerAid() to repair identity.`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* 通过 PKI HTTP 端点下载服务端登记的证书。
|
|
1785
|
+
* 404 / 找不到 → 返回 null(视为未注册);其它 HTTP 错抛 AuthError。
|
|
1786
|
+
* 该方法既用于 recover 路径,也用于 registerAid 全新注册前的查重前置。
|
|
1787
|
+
*/
|
|
1788
|
+
async _downloadRegisteredCert(gatewayUrl, aid) {
|
|
1789
|
+
const certUrl = _gatewayHttpUrl(gatewayUrl, `/pki/cert/${aid}`);
|
|
1790
|
+
try {
|
|
1791
|
+
const certPem = await _fetchText(certUrl, this._verifySsl);
|
|
1792
|
+
if (!certPem || !certPem.includes('BEGIN CERTIFICATE')) {
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
return certPem;
|
|
1796
|
+
}
|
|
1797
|
+
catch (e) {
|
|
1798
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1799
|
+
if (msg.includes('404') || msg.toLowerCase().includes('not found')) {
|
|
1800
|
+
return null;
|
|
1801
|
+
}
|
|
1802
|
+
throw new AuthError(`failed to fetch ${certUrl}: ${msg}`);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1495
1805
|
// ── 内部方法:new_cert 验证 ────────────────────────────────
|
|
1496
1806
|
/**
|
|
1497
1807
|
* 验证服务端返回的 new_cert,通过后才正式接受。
|
|
@@ -1638,30 +1948,9 @@ export class AuthFlow {
|
|
|
1638
1948
|
throw new ValidationError("AID name must not start with 'guest'");
|
|
1639
1949
|
}
|
|
1640
1950
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
// 必须确认有 keypair(private_key_pem + public_key_der_b64)才算"已存在"
|
|
1645
|
-
// 否则 keystore 可能只有 metadata(如 gateway_url)但没有真正的密钥材料
|
|
1646
|
-
if (existing && existing.private_key_pem && existing.public_key_der_b64) {
|
|
1647
|
-
this._aid = aid;
|
|
1648
|
-
return existing;
|
|
1649
|
-
}
|
|
1650
|
-
const identity = this._crypto.generateIdentity();
|
|
1651
|
-
identity.aid = aid;
|
|
1652
|
-
// 保留 keystore 已有的 metadata(如 gateway_url),避免覆盖
|
|
1653
|
-
if (existing) {
|
|
1654
|
-
for (const [k, v] of Object.entries(existing)) {
|
|
1655
|
-
if (k !== 'aid' && !(k in identity)) {
|
|
1656
|
-
identity[k] = v;
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
}
|
|
1660
|
-
this._persistIdentity(identity); // 立即持久化密钥对
|
|
1661
|
-
this._aid = aid;
|
|
1662
|
-
return identity;
|
|
1663
|
-
}
|
|
1664
|
-
/** 加载身份信息,不存在时抛出 StateError */
|
|
1951
|
+
// (_ensureLocalIdentity 已硬移除:注册和登录彻底分离,登录路径绝不再
|
|
1952
|
+
// 隐式生成密钥;新身份必须由应用层显式调 registerAid)
|
|
1953
|
+
/** 加载身份信息,不存在或半成品时抛出 StateError */
|
|
1665
1954
|
_loadIdentityOrRaise(aid) {
|
|
1666
1955
|
const requestedAid = aid ?? this._aid;
|
|
1667
1956
|
if (requestedAid) {
|
|
@@ -1669,6 +1958,13 @@ export class AuthFlow {
|
|
|
1669
1958
|
if (existing === null) {
|
|
1670
1959
|
throw new StateError(`identity not found for aid: ${requestedAid}`);
|
|
1671
1960
|
}
|
|
1961
|
+
// 防线 A:拒绝半成品 identity(缺 keypair 任一字段)。
|
|
1962
|
+
// 这是上次 registerAid 半完成 / keystore 损坏的常见症状;如果让它流到
|
|
1963
|
+
// _login,签名一定失败,且服务端可能记录一次无效登录尝试。
|
|
1964
|
+
if (!existing.private_key_pem || !existing.public_key_der_b64) {
|
|
1965
|
+
throw new StateError(`local identity for aid ${requestedAid} is incomplete (missing keypair); ` +
|
|
1966
|
+
`call auth.registerAid() first`);
|
|
1967
|
+
}
|
|
1672
1968
|
this._aid = requestedAid;
|
|
1673
1969
|
if (!existing.aid)
|
|
1674
1970
|
existing.aid = requestedAid;
|
|
@@ -1679,6 +1975,9 @@ export class AuthFlow {
|
|
|
1679
1975
|
if (typeof ks.loadAnyIdentity === 'function') {
|
|
1680
1976
|
const existing = ks.loadAnyIdentity();
|
|
1681
1977
|
if (existing !== null && existing !== undefined) {
|
|
1978
|
+
if (!existing.private_key_pem || !existing.public_key_der_b64) {
|
|
1979
|
+
throw new StateError(`local identity is incomplete (missing keypair); call auth.registerAid() first`);
|
|
1980
|
+
}
|
|
1682
1981
|
const loadedAid = existing.aid;
|
|
1683
1982
|
if (typeof loadedAid === 'string' && loadedAid) {
|
|
1684
1983
|
this._aid = loadedAid;
|
|
@@ -1686,29 +1985,11 @@ export class AuthFlow {
|
|
|
1686
1985
|
return existing;
|
|
1687
1986
|
}
|
|
1688
1987
|
}
|
|
1689
|
-
throw new StateError('no local identity found, call auth.
|
|
1690
|
-
}
|
|
1691
|
-
/** 确保有身份(不存在时自动创建密钥对) */
|
|
1692
|
-
_ensureIdentity() {
|
|
1693
|
-
try {
|
|
1694
|
-
return this._loadIdentityOrRaise();
|
|
1695
|
-
}
|
|
1696
|
-
catch (e) {
|
|
1697
|
-
if (!(e instanceof StateError))
|
|
1698
|
-
throw e;
|
|
1699
|
-
if (!this._aid) {
|
|
1700
|
-
throw new StateError('no local identity found, call auth.createAid() first');
|
|
1701
|
-
}
|
|
1702
|
-
const identity = this._crypto.generateIdentity();
|
|
1703
|
-
identity.aid = this._aid;
|
|
1704
|
-
this._persistIdentity(identity); // 立即持久化
|
|
1705
|
-
return identity;
|
|
1706
|
-
}
|
|
1988
|
+
throw new StateError('no local identity found, call auth.registerAid() first');
|
|
1707
1989
|
}
|
|
1990
|
+
// (_ensureIdentity 已硬移除:注册和登录彻底分离,登录路径绝不再隐式
|
|
1991
|
+
// 生成密钥;调用方应改用 _loadIdentityOrRaise 获取已注册身份)
|
|
1708
1992
|
_loadInstanceState(aid) {
|
|
1709
|
-
if (!this._deviceId) {
|
|
1710
|
-
return null;
|
|
1711
|
-
}
|
|
1712
1993
|
const loader = this._keystore.loadInstanceState;
|
|
1713
1994
|
if (typeof loader !== 'function') {
|
|
1714
1995
|
return null;
|
|
@@ -1729,17 +2010,15 @@ export class AuthFlow {
|
|
|
1729
2010
|
}
|
|
1730
2011
|
}
|
|
1731
2012
|
this._keystore.saveIdentity(aid, persisted);
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
db.deleteMetadata(`${key}_protection`);
|
|
1739
|
-
}
|
|
2013
|
+
// 从共享 metadata_kv 中移除实例级字段(它们已保存到 instance_state)
|
|
2014
|
+
const db = this._keystore._getDB?.(aid);
|
|
2015
|
+
if (db && typeof db.deleteMetadata === 'function') {
|
|
2016
|
+
for (const key of AuthFlow._INSTANCE_STATE_FIELDS) {
|
|
2017
|
+
db.deleteMetadata(key);
|
|
2018
|
+
db.deleteMetadata(`${key}_protection`);
|
|
1740
2019
|
}
|
|
1741
2020
|
}
|
|
1742
|
-
if (
|
|
2021
|
+
if (Object.keys(instanceState).length === 0) {
|
|
1743
2022
|
return;
|
|
1744
2023
|
}
|
|
1745
2024
|
const updater = this._keystore.updateInstanceState;
|