@agentunion/fastaun 0.2.17 → 0.2.19
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/auth.d.ts +6 -0
- package/dist/auth.js +375 -212
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +14 -1
- package/dist/client.js +1255 -729
- package/dist/client.js.map +1 -1
- package/dist/discovery.d.ts +3 -0
- package/dist/discovery.js +29 -2
- package/dist/discovery.js.map +1 -1
- package/dist/e2ee-group.d.ts +2 -1
- package/dist/e2ee-group.js +207 -56
- package/dist/e2ee-group.js.map +1 -1
- package/dist/e2ee.js +45 -11
- package/dist/e2ee.js.map +1 -1
- package/dist/events.js +1 -1
- package/dist/events.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +13 -1
- package/dist/keystore/aid-db.js +31 -3
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/file.d.ts +6 -0
- package/dist/keystore/file.js +20 -9
- package/dist/keystore/file.js.map +1 -1
- package/dist/keystore/index.d.ts +2 -0
- package/dist/keystore/sqlite-backup.js +5 -5
- package/dist/keystore/sqlite-backup.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +69 -13
- package/dist/logger.js.map +1 -1
- package/dist/namespaces/auth.d.ts +13 -4
- package/dist/namespaces/auth.js +354 -150
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/namespaces/custody.d.ts +1 -0
- package/dist/namespaces/custody.js +138 -56
- package/dist/namespaces/custody.js.map +1 -1
- package/dist/namespaces/meta.d.ts +1 -0
- package/dist/namespaces/meta.js +26 -0
- package/dist/namespaces/meta.js.map +1 -1
- package/dist/secret-store/file-store.js +3 -2
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/transport.js +83 -2
- package/dist/transport.js.map +1 -1
- package/package.json +42 -42
package/dist/auth.js
CHANGED
|
@@ -309,263 +309,375 @@ export class AuthFlow {
|
|
|
309
309
|
* 如果 AID 已在服务端注册但本地证书丢失,尝试从 PKI 端点下载恢复。
|
|
310
310
|
*/
|
|
311
311
|
async createAid(gatewayUrl, aid) {
|
|
312
|
+
const tStart = Date.now();
|
|
312
313
|
AuthFlow._validateAidName(aid);
|
|
313
|
-
|
|
314
|
-
if (identity.cert) {
|
|
315
|
-
return { aid: identity.aid, cert: identity.cert };
|
|
316
|
-
}
|
|
317
|
-
// 本地有密钥但无证书——尝试注册
|
|
314
|
+
this._logger.debug(`createAid enter: aid=${aid}, gateway=${gatewayUrl}`);
|
|
318
315
|
try {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (!(e instanceof AUNError) || !String(e.message).includes('already exists')) {
|
|
324
|
-
throw e;
|
|
316
|
+
let identity = this._ensureLocalIdentity(aid);
|
|
317
|
+
if (identity.cert) {
|
|
318
|
+
this._logger.debug(`createAid exit: elapsed=${Date.now() - tStart}ms (already has cert) aid=${aid}`);
|
|
319
|
+
return { aid: identity.aid, cert: identity.cert };
|
|
325
320
|
}
|
|
326
|
-
//
|
|
321
|
+
// 本地有密钥但无证书——尝试注册
|
|
327
322
|
try {
|
|
328
|
-
|
|
323
|
+
const created = await this._createAid(gatewayUrl, identity);
|
|
324
|
+
Object.assign(identity, created);
|
|
325
|
+
this._logger.debug(`AID register ok: aid=${aid}`);
|
|
329
326
|
}
|
|
330
|
-
catch {
|
|
331
|
-
|
|
332
|
-
`
|
|
333
|
-
|
|
334
|
-
|
|
327
|
+
catch (e) {
|
|
328
|
+
if (!(e instanceof AUNError) || !String(e.message).includes('already exists')) {
|
|
329
|
+
this._logger.error(`AID register failed: aid=${aid}, error=${e instanceof Error ? e.message : String(e)}`);
|
|
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
|
+
}
|
|
335
342
|
}
|
|
343
|
+
this._persistIdentity(identity);
|
|
344
|
+
this._aid = String(identity.aid);
|
|
345
|
+
this._logger.debug(`createAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
346
|
+
return { aid: identity.aid, cert: identity.cert };
|
|
347
|
+
}
|
|
348
|
+
catch (err) {
|
|
349
|
+
this._logger.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
350
|
+
throw err;
|
|
336
351
|
}
|
|
337
|
-
this._persistIdentity(identity);
|
|
338
|
-
this._aid = String(identity.aid);
|
|
339
|
-
return { aid: identity.aid, cert: identity.cert };
|
|
340
352
|
}
|
|
341
353
|
/**
|
|
342
354
|
* 认证(登录)到 Gateway。
|
|
343
355
|
* 执行两阶段挑战-应答认证,返回 token 信息。
|
|
344
356
|
*/
|
|
345
357
|
async authenticate(gatewayUrl, opts) {
|
|
358
|
+
const tStart = Date.now();
|
|
346
359
|
let identity = this._loadIdentityOrRaise(opts?.aid);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
360
|
+
this._logger.debug(`authenticate enter: aid=${identity.aid}, gateway=${gatewayUrl}`);
|
|
361
|
+
try {
|
|
362
|
+
// 优先复用 keystore 里的 cached access_token(未过期且有 refresh_token)
|
|
363
|
+
// 避免每次调 authenticate 都走两阶段重登的网络往返
|
|
364
|
+
// 注意:_loadIdentityOrRaise 不带 instance_state(access_token 等),
|
|
365
|
+
// 这里需要主动走 _loadInstanceState 拿到 token
|
|
366
|
+
const instanceState = this._loadInstanceState(String(identity.aid)) || {};
|
|
367
|
+
const identityWithState = { ...identity, ...instanceState };
|
|
368
|
+
const cachedToken = AuthFlow._getCachedAccessToken(identityWithState);
|
|
369
|
+
const cachedRefresh = String(identityWithState.refresh_token ?? '');
|
|
370
|
+
if (cachedToken && cachedRefresh) {
|
|
371
|
+
this._logger.debug(`authenticate reusing cached token: aid=${identity.aid} expires_at=${identityWithState.access_token_expires_at}`);
|
|
372
|
+
this._aid = String(identity.aid);
|
|
373
|
+
return {
|
|
374
|
+
aid: identity.aid,
|
|
375
|
+
access_token: cachedToken,
|
|
376
|
+
refresh_token: cachedRefresh,
|
|
377
|
+
expires_at: identityWithState.access_token_expires_at,
|
|
378
|
+
gateway: gatewayUrl,
|
|
379
|
+
};
|
|
352
380
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
381
|
+
if (!identity.cert) {
|
|
382
|
+
// 本地有密钥但无证书——尝试从 PKI 下载恢复
|
|
383
|
+
try {
|
|
384
|
+
identity = await this._recoverCertViaDownload(gatewayUrl, identity);
|
|
385
|
+
this._persistIdentity(identity);
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
throw new StateError(`local certificate missing and recovery failed: ${e instanceof Error ? e.message : String(e)}. ` +
|
|
389
|
+
`Run auth.createAid() to register a new identity.`);
|
|
390
|
+
}
|
|
356
391
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
try {
|
|
360
|
-
login = await this._login(gatewayUrl, identity);
|
|
361
|
-
}
|
|
362
|
-
catch (e) {
|
|
363
|
-
// 证书未在服务端注册或公钥不匹配 — 自动重新注册
|
|
364
|
-
if (e instanceof AuthError && (String(e.message).includes('not registered') || String(e.message).includes('public key mismatch'))) {
|
|
365
|
-
this._logger.warn(`证书未在服务端注册,自动重新注册: aid=${identity.aid}`);
|
|
366
|
-
const created = await this._createAid(gatewayUrl, identity);
|
|
367
|
-
identity.cert = created.cert;
|
|
368
|
-
this._persistIdentity(identity);
|
|
392
|
+
let login;
|
|
393
|
+
try {
|
|
369
394
|
login = await this._login(gatewayUrl, identity);
|
|
395
|
+
this._logger.debug(`auth login ok: aid=${identity.aid}`);
|
|
370
396
|
}
|
|
371
|
-
|
|
372
|
-
|
|
397
|
+
catch (e) {
|
|
398
|
+
// 证书未在服务端注册或公钥不匹配 — 自动重新注册
|
|
399
|
+
if (e instanceof AuthError && (String(e.message).includes('not registered') || String(e.message).includes('public key mismatch'))) {
|
|
400
|
+
this._logger.warn(`cert not registered on server, auto re-register: aid=${identity.aid}`);
|
|
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
|
+
}
|
|
373
409
|
}
|
|
410
|
+
AuthFlow._rememberTokens(identity, login);
|
|
411
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
412
|
+
this._persistIdentity(identity);
|
|
413
|
+
this._aid = String(identity.aid);
|
|
414
|
+
this._logger.debug(`authenticate exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
415
|
+
return {
|
|
416
|
+
aid: identity.aid,
|
|
417
|
+
access_token: identity.access_token,
|
|
418
|
+
refresh_token: identity.refresh_token,
|
|
419
|
+
expires_at: identity.access_token_expires_at,
|
|
420
|
+
gateway: gatewayUrl,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
this._logger.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
425
|
+
throw err;
|
|
374
426
|
}
|
|
375
|
-
AuthFlow._rememberTokens(identity, login);
|
|
376
|
-
await this._validateNewCert(identity, gatewayUrl);
|
|
377
|
-
this._persistIdentity(identity);
|
|
378
|
-
this._aid = String(identity.aid);
|
|
379
|
-
return {
|
|
380
|
-
aid: identity.aid,
|
|
381
|
-
access_token: identity.access_token,
|
|
382
|
-
refresh_token: identity.refresh_token,
|
|
383
|
-
expires_at: identity.access_token_expires_at,
|
|
384
|
-
gateway: gatewayUrl,
|
|
385
|
-
};
|
|
386
427
|
}
|
|
387
428
|
/**
|
|
388
429
|
* 确保已认证(自动创建 + 登录)。
|
|
389
430
|
* 如果没有本地身份则创建,然后执行登录流程。
|
|
390
431
|
*/
|
|
391
432
|
async ensureAuthenticated(gatewayUrl) {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
433
|
+
const tStart = Date.now();
|
|
434
|
+
this._logger.debug(`ensureAuthenticated enter: gateway=${gatewayUrl}`);
|
|
435
|
+
try {
|
|
436
|
+
const identity = this._ensureIdentity();
|
|
437
|
+
if (!identity.cert) {
|
|
438
|
+
const created = await this._createAid(gatewayUrl, identity);
|
|
439
|
+
Object.assign(identity, created);
|
|
440
|
+
this._persistIdentity(identity);
|
|
441
|
+
}
|
|
442
|
+
const login = await this._login(gatewayUrl, identity);
|
|
443
|
+
AuthFlow._rememberTokens(identity, login);
|
|
444
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
396
445
|
this._persistIdentity(identity);
|
|
446
|
+
const token = identity.access_token || identity.token || identity.kite_token;
|
|
447
|
+
if (!token) {
|
|
448
|
+
throw new AuthError('login2 did not return access token');
|
|
449
|
+
}
|
|
450
|
+
this._logger.debug(`ensureAuthenticated exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
451
|
+
return { token, identity };
|
|
397
452
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this._persistIdentity(identity);
|
|
402
|
-
const token = identity.access_token || identity.token || identity.kite_token;
|
|
403
|
-
if (!token) {
|
|
404
|
-
throw new AuthError('login2 did not return access token');
|
|
453
|
+
catch (err) {
|
|
454
|
+
this._logger.debug(`ensureAuthenticated exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
455
|
+
throw err;
|
|
405
456
|
}
|
|
406
|
-
return { token, identity };
|
|
407
457
|
}
|
|
408
458
|
/**
|
|
409
459
|
* 刷新缓存的 token。
|
|
410
460
|
* 使用 refresh_token 获取新的 access_token。
|
|
411
461
|
*/
|
|
412
462
|
async refreshCachedTokens(gatewayUrl, identity) {
|
|
463
|
+
const tStart = Date.now();
|
|
413
464
|
const refreshToken = String(identity.refresh_token || '');
|
|
414
465
|
if (!refreshToken) {
|
|
466
|
+
this._logger.error(`token refresh failed: missing refresh_token, aid=${identity.aid}`);
|
|
415
467
|
throw new AuthError('missing refresh_token');
|
|
416
468
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
469
|
+
this._logger.debug(`refreshCachedTokens enter: aid=${identity.aid}`);
|
|
470
|
+
try {
|
|
471
|
+
const refreshed = await this._refreshAccessToken(gatewayUrl, refreshToken);
|
|
472
|
+
AuthFlow._rememberTokens(identity, refreshed);
|
|
473
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
474
|
+
this._persistIdentity(identity);
|
|
475
|
+
this._logger.debug(`refreshCachedTokens exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
476
|
+
return identity;
|
|
477
|
+
}
|
|
478
|
+
catch (err) {
|
|
479
|
+
this._logger.debug(`refreshCachedTokens exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
480
|
+
throw err;
|
|
481
|
+
}
|
|
422
482
|
}
|
|
423
483
|
/**
|
|
424
484
|
* 使用 token 初始化 WebSocket 会话。
|
|
425
485
|
* 发送 auth.connect RPC 完成会话握手。
|
|
426
486
|
*/
|
|
427
487
|
async initializeWithToken(transport, challenge, accessToken, opts) {
|
|
428
|
-
const
|
|
429
|
-
this.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
488
|
+
const tStart = Date.now();
|
|
489
|
+
this._logger.debug(`initializeWithToken enter: deviceId=${opts?.deviceId ?? ''}, slotId=${opts?.slotId ?? ''}`);
|
|
490
|
+
try {
|
|
491
|
+
const nonce = this._extractChallengeNonce(challenge);
|
|
492
|
+
this.setInstanceContext({
|
|
493
|
+
deviceId: String(opts?.deviceId ?? ''),
|
|
494
|
+
slotId: String(opts?.slotId ?? ''),
|
|
495
|
+
});
|
|
496
|
+
await this._initializeSession(transport, nonce, accessToken, {
|
|
497
|
+
deviceId: String(opts?.deviceId ?? ''),
|
|
498
|
+
slotId: String(opts?.slotId ?? ''),
|
|
499
|
+
deliveryMode: opts?.deliveryMode ?? null,
|
|
500
|
+
connectionKind: opts?.connectionKind,
|
|
501
|
+
shortTtlMs: opts?.shortTtlMs,
|
|
502
|
+
extraInfo: opts?.extraInfo,
|
|
503
|
+
});
|
|
504
|
+
this._logger.debug(`initializeWithToken exit: elapsed=${Date.now() - tStart}ms`);
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
this._logger.debug(`initializeWithToken exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
508
|
+
throw err;
|
|
509
|
+
}
|
|
438
510
|
}
|
|
439
511
|
/**
|
|
440
512
|
* 连接会话(自动选择认证策略)。
|
|
441
513
|
* 依次尝试:显式 token → 缓存 token → 刷新 token → 完整重认证。
|
|
442
514
|
*/
|
|
443
515
|
async connectSession(transport, challenge, gatewayUrl, opts) {
|
|
516
|
+
const tStart = Date.now();
|
|
444
517
|
const nonce = this._extractChallengeNonce(challenge);
|
|
445
518
|
const deviceId = String(opts?.deviceId ?? '');
|
|
446
519
|
const slotId = String(opts?.slotId ?? '');
|
|
447
520
|
const deliveryMode = opts?.deliveryMode ?? null;
|
|
521
|
+
const connectionKind = opts?.connectionKind;
|
|
522
|
+
const shortTtlMs = opts?.shortTtlMs;
|
|
523
|
+
const extraInfo = opts?.extraInfo;
|
|
448
524
|
this.setInstanceContext({ deviceId, slotId });
|
|
449
|
-
|
|
525
|
+
this._logger.debug(`connectSession enter: gateway=${gatewayUrl}, device_id=${deviceId}, slot_id=${slotId}, has_explicit_token=${!!opts?.accessToken}, has_challenge_nonce=${!!nonce}`);
|
|
450
526
|
try {
|
|
451
|
-
identity
|
|
452
|
-
}
|
|
453
|
-
catch {
|
|
454
|
-
identity = null;
|
|
455
|
-
}
|
|
456
|
-
// 策略 1:显式 token
|
|
457
|
-
const explicitToken = String(opts?.accessToken || '');
|
|
458
|
-
if (explicitToken && identity !== null) {
|
|
527
|
+
let identity;
|
|
459
528
|
try {
|
|
460
|
-
|
|
461
|
-
deviceId,
|
|
462
|
-
slotId,
|
|
463
|
-
deliveryMode,
|
|
464
|
-
});
|
|
465
|
-
identity.access_token = explicitToken;
|
|
466
|
-
this._persistIdentity(identity);
|
|
467
|
-
return { token: explicitToken, identity };
|
|
529
|
+
identity = this.loadIdentity();
|
|
468
530
|
}
|
|
469
|
-
catch
|
|
470
|
-
|
|
471
|
-
throw exc;
|
|
472
|
-
this._logger.debug(`explicit_token 认证失败,尝试下一方式: ${exc.message}`);
|
|
531
|
+
catch {
|
|
532
|
+
identity = null;
|
|
473
533
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
534
|
+
// 策略 1:显式 token
|
|
535
|
+
const explicitToken = String(opts?.accessToken || '');
|
|
536
|
+
if (explicitToken && identity !== null) {
|
|
537
|
+
try {
|
|
538
|
+
await this._initializeSession(transport, nonce, explicitToken, {
|
|
539
|
+
deviceId,
|
|
540
|
+
slotId,
|
|
541
|
+
deliveryMode,
|
|
542
|
+
connectionKind,
|
|
543
|
+
shortTtlMs,
|
|
544
|
+
extraInfo,
|
|
545
|
+
});
|
|
546
|
+
identity.access_token = explicitToken;
|
|
547
|
+
this._persistIdentity(identity);
|
|
548
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=explicit_token aid=${String(identity.aid ?? '')}`);
|
|
549
|
+
return { token: explicitToken, identity };
|
|
550
|
+
}
|
|
551
|
+
catch (exc) {
|
|
552
|
+
if (!(exc instanceof AuthError))
|
|
553
|
+
throw exc;
|
|
554
|
+
this._logger.debug(`explicit_token auth failed, try next method: ${exc.message}`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// 无本地身份时,执行完整认证
|
|
558
|
+
if (identity === null) {
|
|
559
|
+
const authContext = await this.ensureAuthenticated(gatewayUrl);
|
|
560
|
+
const token = String(authContext.token);
|
|
561
|
+
await this._initializeSession(transport, nonce, token, {
|
|
491
562
|
deviceId,
|
|
492
563
|
slotId,
|
|
493
564
|
deliveryMode,
|
|
565
|
+
connectionKind,
|
|
566
|
+
shortTtlMs,
|
|
567
|
+
extraInfo,
|
|
494
568
|
});
|
|
495
|
-
|
|
569
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=ensure_authenticated aid=${String(authContext.identity?.aid ?? '')}`);
|
|
570
|
+
return authContext;
|
|
496
571
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
// 策略 3:refresh_token
|
|
504
|
-
const refreshToken = String(identity.refresh_token || '');
|
|
505
|
-
if (refreshToken) {
|
|
506
|
-
try {
|
|
507
|
-
identity = await this.refreshCachedTokens(gatewayUrl, identity);
|
|
508
|
-
const newCachedToken = AuthFlow._getCachedAccessToken(identity);
|
|
509
|
-
if (newCachedToken) {
|
|
510
|
-
await this._initializeSession(transport, nonce, newCachedToken, {
|
|
572
|
+
// 策略 2:缓存的 access_token
|
|
573
|
+
const cachedToken = AuthFlow._getCachedAccessToken(identity);
|
|
574
|
+
if (cachedToken) {
|
|
575
|
+
try {
|
|
576
|
+
await this._initializeSession(transport, nonce, cachedToken, {
|
|
511
577
|
deviceId,
|
|
512
578
|
slotId,
|
|
513
579
|
deliveryMode,
|
|
580
|
+
connectionKind,
|
|
581
|
+
shortTtlMs,
|
|
582
|
+
extraInfo,
|
|
514
583
|
});
|
|
515
|
-
|
|
584
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=cached_token aid=${String(identity.aid ?? '')}`);
|
|
585
|
+
return { token: cachedToken, identity };
|
|
586
|
+
}
|
|
587
|
+
catch (exc) {
|
|
588
|
+
if (!(exc instanceof AuthError))
|
|
589
|
+
throw exc;
|
|
590
|
+
this._logger.debug(`cached_token auth failed, try refresh: ${exc.message}`);
|
|
516
591
|
}
|
|
517
592
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
593
|
+
// 策略 3:refresh_token
|
|
594
|
+
const refreshToken = String(identity.refresh_token || '');
|
|
595
|
+
if (refreshToken) {
|
|
596
|
+
try {
|
|
597
|
+
identity = await this.refreshCachedTokens(gatewayUrl, identity);
|
|
598
|
+
const newCachedToken = AuthFlow._getCachedAccessToken(identity);
|
|
599
|
+
if (newCachedToken) {
|
|
600
|
+
await this._initializeSession(transport, nonce, newCachedToken, {
|
|
601
|
+
deviceId,
|
|
602
|
+
slotId,
|
|
603
|
+
deliveryMode,
|
|
604
|
+
connectionKind,
|
|
605
|
+
shortTtlMs,
|
|
606
|
+
extraInfo,
|
|
607
|
+
});
|
|
608
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=refresh_token aid=${String(identity.aid ?? '')}`);
|
|
609
|
+
return { token: newCachedToken, identity };
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
catch (exc) {
|
|
613
|
+
if (!(exc instanceof AuthError))
|
|
614
|
+
throw exc;
|
|
615
|
+
this._logger.debug(`refresh_token auth failed, will re-login: ${exc.message}`);
|
|
616
|
+
}
|
|
522
617
|
}
|
|
618
|
+
// 策略 4:完整重认证
|
|
619
|
+
const login = await this.authenticate(gatewayUrl, { aid: identity.aid });
|
|
620
|
+
const token = String(login.access_token || '');
|
|
621
|
+
if (!token) {
|
|
622
|
+
throw new AuthError('authenticate did not return access_token');
|
|
623
|
+
}
|
|
624
|
+
await this._initializeSession(transport, nonce, token, {
|
|
625
|
+
deviceId,
|
|
626
|
+
slotId,
|
|
627
|
+
deliveryMode,
|
|
628
|
+
connectionKind,
|
|
629
|
+
shortTtlMs,
|
|
630
|
+
extraInfo,
|
|
631
|
+
});
|
|
632
|
+
identity = this.loadIdentity(identity.aid);
|
|
633
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=full_reauth aid=${String(identity?.aid ?? '')}`);
|
|
634
|
+
return { token, identity };
|
|
523
635
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (!token) {
|
|
528
|
-
throw new AuthError('authenticate did not return access_token');
|
|
636
|
+
catch (err) {
|
|
637
|
+
this._logger.debug(`connectSession exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
638
|
+
throw err;
|
|
529
639
|
}
|
|
530
|
-
await this._initializeSession(transport, nonce, token, {
|
|
531
|
-
deviceId,
|
|
532
|
-
slotId,
|
|
533
|
-
deliveryMode,
|
|
534
|
-
});
|
|
535
|
-
identity = this.loadIdentity(identity.aid);
|
|
536
|
-
return { token, identity };
|
|
537
640
|
}
|
|
538
641
|
/**
|
|
539
642
|
* 验证对端证书:时间有效性 + 链验证 + CRL + OCSP + AID 绑定。
|
|
540
643
|
* 用于 E2EE 握手中验证通信对端的身份证书。
|
|
541
644
|
*/
|
|
542
645
|
async verifyPeerCertificate(gatewayUrl, certPem, expectedAid) {
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
_ensureCertTimeValid(cert, 'peer certificate', nowMs);
|
|
546
|
-
await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
|
|
547
|
-
try {
|
|
548
|
-
await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
|
|
549
|
-
}
|
|
550
|
-
catch (e) {
|
|
551
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
552
|
-
if (/revoked/i.test(errMsg))
|
|
553
|
-
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
554
|
-
this._logger.warn(`CRL 检查不可用,降级继续: ${errMsg}`);
|
|
555
|
-
}
|
|
646
|
+
const tStart = Date.now();
|
|
647
|
+
this._logger.debug(`verifyPeerCertificate enter: aid=${expectedAid}, gateway=${gatewayUrl}`);
|
|
556
648
|
try {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
649
|
+
const cert = _loadX509(certPem);
|
|
650
|
+
const nowMs = Date.now();
|
|
651
|
+
_ensureCertTimeValid(cert, 'peer certificate', nowMs);
|
|
652
|
+
await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
|
|
653
|
+
try {
|
|
654
|
+
await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
|
|
655
|
+
}
|
|
656
|
+
catch (e) {
|
|
657
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
658
|
+
if (/revoked/i.test(errMsg))
|
|
659
|
+
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
660
|
+
this._logger.warn(`CRL verification unavailable, skipping: ${errMsg}`);
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
await this._verifyAuthCertOcsp(gatewayUrl, cert, expectedAid);
|
|
664
|
+
}
|
|
665
|
+
catch (exc) {
|
|
666
|
+
const errMsg = exc instanceof Error ? exc.message : String(exc);
|
|
667
|
+
if (/revoked/i.test(errMsg))
|
|
668
|
+
throw exc instanceof AuthError ? exc : new AuthError(errMsg);
|
|
669
|
+
this._logger.warn(`OCSP verification unavailable, skipping: ${errMsg}`);
|
|
670
|
+
}
|
|
671
|
+
// 检查 CN 匹配
|
|
672
|
+
const cn = _certSubjectCN(cert);
|
|
673
|
+
if (cn !== expectedAid) {
|
|
674
|
+
throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}, got ${cn || 'none'}`);
|
|
675
|
+
}
|
|
676
|
+
this._logger.debug(`verifyPeerCertificate exit: elapsed=${Date.now() - tStart}ms aid=${expectedAid}`);
|
|
564
677
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}, got ${cn || 'none'}`);
|
|
678
|
+
catch (err) {
|
|
679
|
+
this._logger.debug(`verifyPeerCertificate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
680
|
+
throw err;
|
|
569
681
|
}
|
|
570
682
|
}
|
|
571
683
|
// ── 内部方法:短连接 RPC ────────────────────────────────────
|
|
@@ -574,6 +686,7 @@ export class AuthFlow {
|
|
|
574
686
|
* 流程:连接 → 接收 challenge → 发送请求 → 接收响应 → 关闭。
|
|
575
687
|
*/
|
|
576
688
|
async _shortRpc(gatewayUrl, method, params) {
|
|
689
|
+
this._logger.debug(`_shortRpc enter: method=${method}, gateway=${gatewayUrl}`);
|
|
577
690
|
const ws = await this._connectionFactory(gatewayUrl);
|
|
578
691
|
try {
|
|
579
692
|
// 接收 challenge(第一条消息)
|
|
@@ -651,39 +764,62 @@ export class AuthFlow {
|
|
|
651
764
|
}
|
|
652
765
|
/** 两阶段登录 */
|
|
653
766
|
async _login(gatewayUrl, identity) {
|
|
767
|
+
const tStart = Date.now();
|
|
654
768
|
const clientNonce = this._crypto.newClientNonce();
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
769
|
+
this._logger.debug(`_login enter: aid=${identity.aid}`);
|
|
770
|
+
try {
|
|
771
|
+
// Phase 1: 发送 AID + 证书 + 客户端 nonce
|
|
772
|
+
const phase1 = await this._shortRpc(gatewayUrl, 'auth.aid_login1', {
|
|
773
|
+
aid: identity.aid,
|
|
774
|
+
cert: identity.cert,
|
|
775
|
+
client_nonce: clientNonce,
|
|
776
|
+
});
|
|
777
|
+
this._logger.debug(`login1 response recv: aid=${identity.aid}, request_id=${phase1.request_id}`);
|
|
778
|
+
// 验证 Phase 1 响应(证书链 + 签名)
|
|
779
|
+
await this._verifyPhase1Response(gatewayUrl, phase1, clientNonce);
|
|
780
|
+
this._logger.debug(`login1 verify ok: aid=${identity.aid}`);
|
|
781
|
+
// Phase 2: 用私钥签名 nonce
|
|
782
|
+
const [signature, clientTime] = this._crypto.signLoginNonce(String(identity.private_key_pem), String(phase1.nonce));
|
|
783
|
+
this._logger.debug(`login2 start: aid=${identity.aid}`);
|
|
784
|
+
const phase2 = await this._shortRpc(gatewayUrl, 'auth.aid_login2', {
|
|
785
|
+
aid: identity.aid,
|
|
786
|
+
request_id: phase1.request_id,
|
|
787
|
+
nonce: phase1.nonce,
|
|
788
|
+
client_time: clientTime,
|
|
789
|
+
signature,
|
|
790
|
+
});
|
|
791
|
+
this._logger.debug(`_login exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}, has_token=${!!phase2.access_token || !!phase2.token}`);
|
|
792
|
+
return phase2;
|
|
793
|
+
}
|
|
794
|
+
catch (err) {
|
|
795
|
+
this._logger.debug(`_login exit (error): elapsed=${Date.now() - tStart}ms aid=${identity.aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
796
|
+
throw err;
|
|
797
|
+
}
|
|
673
798
|
}
|
|
674
799
|
/** 刷新 access_token */
|
|
675
800
|
async _refreshAccessToken(gatewayUrl, refreshToken) {
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
801
|
+
const tStart = Date.now();
|
|
802
|
+
this._logger.debug(`_refreshAccessToken enter`);
|
|
803
|
+
try {
|
|
804
|
+
const result = await this._shortRpc(gatewayUrl, 'auth.refresh_token', {
|
|
805
|
+
refresh_token: refreshToken,
|
|
806
|
+
});
|
|
807
|
+
if (!result.success) {
|
|
808
|
+
throw new AuthError(String(result.error || 'refresh failed'));
|
|
809
|
+
}
|
|
810
|
+
this._logger.debug(`_refreshAccessToken exit: elapsed=${Date.now() - tStart}ms`);
|
|
811
|
+
return result;
|
|
812
|
+
}
|
|
813
|
+
catch (err) {
|
|
814
|
+
this._logger.debug(`_refreshAccessToken exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
815
|
+
throw err;
|
|
681
816
|
}
|
|
682
|
-
return result;
|
|
683
817
|
}
|
|
684
818
|
/** 会话初始化:发送 auth.connect RPC */
|
|
685
819
|
async _initializeSession(transport, nonce, token, opts) {
|
|
686
|
-
const
|
|
820
|
+
const connectionKind = String(opts?.connectionKind ?? 'long');
|
|
821
|
+
this._logger.debug(`session init start: deviceId=${opts?.deviceId ?? ''}, slotId=${opts?.slotId ?? ''}, kind=${connectionKind}`);
|
|
822
|
+
const request = {
|
|
687
823
|
nonce,
|
|
688
824
|
auth: { method: 'kite_token', token },
|
|
689
825
|
protocol: { min: '1.0', max: '1.0' },
|
|
@@ -694,11 +830,28 @@ export class AuthFlow {
|
|
|
694
830
|
e2ee: true,
|
|
695
831
|
group_e2ee: true,
|
|
696
832
|
},
|
|
697
|
-
}
|
|
833
|
+
};
|
|
834
|
+
// extra_info:应用层自定义信息(PID/HOME/备注等),踢人时透传给被踢方
|
|
835
|
+
const extraInfo = opts?.extraInfo;
|
|
836
|
+
if (extraInfo && Object.keys(extraInfo).length > 0) {
|
|
837
|
+
request.extra_info = extraInfo;
|
|
838
|
+
}
|
|
839
|
+
// 长短连接选项:默认 long 时不写入 options(保持 wire 兼容)
|
|
840
|
+
if (connectionKind === 'short') {
|
|
841
|
+
const options = { kind: 'short' };
|
|
842
|
+
const ttl = Number(opts?.shortTtlMs ?? 0);
|
|
843
|
+
if (ttl > 0) {
|
|
844
|
+
options.short_ttl_ms = ttl;
|
|
845
|
+
}
|
|
846
|
+
request.options = options;
|
|
847
|
+
}
|
|
848
|
+
const result = await transport.call('auth.connect', request);
|
|
698
849
|
const status = isJsonObject(result) ? result.status : undefined;
|
|
699
850
|
if (status !== 'ok') {
|
|
851
|
+
this._logger.error(`sessioninitfailed: status=${status}, result=${JSON.stringify(result)}`);
|
|
700
852
|
throw new AuthError(`initialize failed: ${JSON.stringify(result)}`);
|
|
701
853
|
}
|
|
854
|
+
this._logger.debug('sessioninitok');
|
|
702
855
|
}
|
|
703
856
|
// ── 内部方法:证书验证 ──────────────────────────────────────
|
|
704
857
|
/**
|
|
@@ -732,7 +885,7 @@ export class AuthFlow {
|
|
|
732
885
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
733
886
|
if (/revoked/i.test(errMsg))
|
|
734
887
|
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
735
|
-
this._logger.warn(`CRL
|
|
888
|
+
this._logger.warn(`CRL verification unavailable, skipping: ${errMsg}`);
|
|
736
889
|
}
|
|
737
890
|
try {
|
|
738
891
|
await this._verifyAuthCertOcsp(gatewayUrl, authCert);
|
|
@@ -741,7 +894,7 @@ export class AuthFlow {
|
|
|
741
894
|
const errMsg = exc instanceof Error ? exc.message : String(exc);
|
|
742
895
|
if (/revoked/i.test(errMsg))
|
|
743
896
|
throw exc instanceof AuthError ? exc : new AuthError(errMsg);
|
|
744
|
-
this._logger.warn(`OCSP
|
|
897
|
+
this._logger.warn(`OCSP verification unavailable, skipping: ${errMsg}`);
|
|
745
898
|
}
|
|
746
899
|
// 验证 client_nonce 签名
|
|
747
900
|
try {
|
|
@@ -915,7 +1068,7 @@ export class AuthFlow {
|
|
|
915
1068
|
revokedSerials = new Set(serialsArr.map((s) => String(s).toLowerCase()));
|
|
916
1069
|
}
|
|
917
1070
|
// 如果两者都没有,返回空集合(无吊销记录)
|
|
918
|
-
this._logger.debug('CRL PEM
|
|
1071
|
+
this._logger.debug('CRL PEM parse failed, using JSON revoked_serials fallback');
|
|
919
1072
|
}
|
|
920
1073
|
// 缓存 TTL:默认 5 分钟,最大 24 小时
|
|
921
1074
|
const now = Date.now();
|
|
@@ -1088,7 +1241,7 @@ export class AuthFlow {
|
|
|
1088
1241
|
// OCSP DER 解析失败时,降级信赖 JSON status 字段
|
|
1089
1242
|
if (e instanceof AuthError)
|
|
1090
1243
|
throw e;
|
|
1091
|
-
this._logger.debug(`OCSP DER
|
|
1244
|
+
this._logger.debug(`OCSP DER parse failed, using JSON status fallback: ${e instanceof Error ? e.message : String(e)}`);
|
|
1092
1245
|
if (!status) {
|
|
1093
1246
|
throw new AuthError('gateway OCSP endpoint returned invalid response and no status field');
|
|
1094
1247
|
}
|
|
@@ -1359,7 +1512,7 @@ export class AuthFlow {
|
|
|
1359
1512
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
1360
1513
|
if (/revoked/i.test(errMsg))
|
|
1361
1514
|
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
1362
|
-
this._logger.warn(`CRL
|
|
1515
|
+
this._logger.warn(`CRL verification unavailable, skipping: ${errMsg}`);
|
|
1363
1516
|
}
|
|
1364
1517
|
try {
|
|
1365
1518
|
await this._verifyAuthCertOcsp(gatewayUrl, cert);
|
|
@@ -1368,7 +1521,7 @@ export class AuthFlow {
|
|
|
1368
1521
|
const errMsg = exc instanceof Error ? exc.message : String(exc);
|
|
1369
1522
|
if (/revoked/i.test(errMsg))
|
|
1370
1523
|
throw exc instanceof AuthError ? exc : new AuthError(errMsg);
|
|
1371
|
-
this._logger.warn(`OCSP
|
|
1524
|
+
this._logger.warn(`OCSP verification unavailable, skipping: ${errMsg}`);
|
|
1372
1525
|
}
|
|
1373
1526
|
}
|
|
1374
1527
|
// 验证通过,正式接受
|
|
@@ -1376,10 +1529,10 @@ export class AuthFlow {
|
|
|
1376
1529
|
}
|
|
1377
1530
|
catch (e) {
|
|
1378
1531
|
if (e instanceof AuthError) {
|
|
1379
|
-
this._logger.warn(
|
|
1532
|
+
this._logger.warn(`rejected server new_cert (${identity.aid}): ${e.message}`);
|
|
1380
1533
|
}
|
|
1381
1534
|
else {
|
|
1382
|
-
this._logger.warn(`new_cert
|
|
1535
|
+
this._logger.warn(`new_cert verification error (${identity.aid}): ${e instanceof Error ? e.message : String(e)}`);
|
|
1383
1536
|
}
|
|
1384
1537
|
}
|
|
1385
1538
|
// active_cert 同步:验证公钥匹配后更新本地 cert
|
|
@@ -1396,12 +1549,12 @@ export class AuthFlow {
|
|
|
1396
1549
|
identity.cert = activeCertPem;
|
|
1397
1550
|
}
|
|
1398
1551
|
else {
|
|
1399
|
-
this._logger.warn(
|
|
1552
|
+
this._logger.warn(`active_cert public key mismatch with local private key, rejecting sync (aid=${identity.aid})`);
|
|
1400
1553
|
}
|
|
1401
1554
|
}
|
|
1402
1555
|
}
|
|
1403
1556
|
catch (e) {
|
|
1404
|
-
this._logger.warn(`active_cert
|
|
1557
|
+
this._logger.warn(`active_cert sync error (${identity.aid}): ${e instanceof Error ? e.message : String(e)}`);
|
|
1405
1558
|
}
|
|
1406
1559
|
}
|
|
1407
1560
|
}
|
|
@@ -1467,12 +1620,22 @@ export class AuthFlow {
|
|
|
1467
1620
|
/** 确保本地有指定 AID 的身份(不存在则创建密钥对) */
|
|
1468
1621
|
_ensureLocalIdentity(aid) {
|
|
1469
1622
|
const existing = this._keystore.loadIdentity(aid);
|
|
1470
|
-
|
|
1623
|
+
// 必须确认有 keypair(private_key_pem + public_key_der_b64)才算"已存在"
|
|
1624
|
+
// 否则 keystore 可能只有 metadata(如 gateway_url)但没有真正的密钥材料
|
|
1625
|
+
if (existing && existing.private_key_pem && existing.public_key_der_b64) {
|
|
1471
1626
|
this._aid = aid;
|
|
1472
1627
|
return existing;
|
|
1473
1628
|
}
|
|
1474
1629
|
const identity = this._crypto.generateIdentity();
|
|
1475
1630
|
identity.aid = aid;
|
|
1631
|
+
// 保留 keystore 已有的 metadata(如 gateway_url),避免覆盖
|
|
1632
|
+
if (existing) {
|
|
1633
|
+
for (const [k, v] of Object.entries(existing)) {
|
|
1634
|
+
if (k !== 'aid' && !(k in identity)) {
|
|
1635
|
+
identity[k] = v;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1476
1639
|
this._persistIdentity(identity); // 立即持久化密钥对
|
|
1477
1640
|
this._aid = aid;
|
|
1478
1641
|
return identity;
|