@agentunion/fastaun 0.2.17 → 0.2.18
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.js +306 -209
- package/dist/auth.js.map +1 -1
- package/dist/client.js +1041 -704
- 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 +24 -9
- 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 +7 -1
- package/dist/keystore/aid-db.js +10 -3
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/file.js +12 -9
- package/dist/keystore/file.js.map +1 -1
- 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 +1 -0
- package/dist/namespaces/auth.js +289 -146
- 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 +1 -1
package/dist/auth.js
CHANGED
|
@@ -309,263 +309,335 @@ 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
|
-
|
|
360
|
+
this._logger.debug(`authenticate enter: aid=${identity.aid}, gateway=${gatewayUrl}`);
|
|
361
|
+
try {
|
|
362
|
+
if (!identity.cert) {
|
|
363
|
+
// 本地有密钥但无证书——尝试从 PKI 下载恢复
|
|
364
|
+
try {
|
|
365
|
+
identity = await this._recoverCertViaDownload(gatewayUrl, identity);
|
|
366
|
+
this._persistIdentity(identity);
|
|
367
|
+
}
|
|
368
|
+
catch (e) {
|
|
369
|
+
throw new StateError(`local certificate missing and recovery failed: ${e instanceof Error ? e.message : String(e)}. ` +
|
|
370
|
+
`Run auth.createAid() to register a new identity.`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
let login;
|
|
349
374
|
try {
|
|
350
|
-
|
|
351
|
-
this.
|
|
375
|
+
login = await this._login(gatewayUrl, identity);
|
|
376
|
+
this._logger.debug(`auth login ok: aid=${identity.aid}`);
|
|
352
377
|
}
|
|
353
378
|
catch (e) {
|
|
354
|
-
|
|
355
|
-
|
|
379
|
+
// 证书未在服务端注册或公钥不匹配 — 自动重新注册
|
|
380
|
+
if (e instanceof AuthError && (String(e.message).includes('not registered') || String(e.message).includes('public key mismatch'))) {
|
|
381
|
+
this._logger.warn(`cert not registered on server, auto re-register: aid=${identity.aid}`);
|
|
382
|
+
const created = await this._createAid(gatewayUrl, identity);
|
|
383
|
+
identity.cert = created.cert;
|
|
384
|
+
this._persistIdentity(identity);
|
|
385
|
+
login = await this._login(gatewayUrl, identity);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
throw e;
|
|
389
|
+
}
|
|
356
390
|
}
|
|
391
|
+
AuthFlow._rememberTokens(identity, login);
|
|
392
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
393
|
+
this._persistIdentity(identity);
|
|
394
|
+
this._aid = String(identity.aid);
|
|
395
|
+
this._logger.debug(`authenticate exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
396
|
+
return {
|
|
397
|
+
aid: identity.aid,
|
|
398
|
+
access_token: identity.access_token,
|
|
399
|
+
refresh_token: identity.refresh_token,
|
|
400
|
+
expires_at: identity.access_token_expires_at,
|
|
401
|
+
gateway: gatewayUrl,
|
|
402
|
+
};
|
|
357
403
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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);
|
|
369
|
-
login = await this._login(gatewayUrl, identity);
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
throw e;
|
|
373
|
-
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
this._logger.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
406
|
+
throw err;
|
|
374
407
|
}
|
|
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
408
|
}
|
|
387
409
|
/**
|
|
388
410
|
* 确保已认证(自动创建 + 登录)。
|
|
389
411
|
* 如果没有本地身份则创建,然后执行登录流程。
|
|
390
412
|
*/
|
|
391
413
|
async ensureAuthenticated(gatewayUrl) {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
414
|
+
const tStart = Date.now();
|
|
415
|
+
this._logger.debug(`ensureAuthenticated enter: gateway=${gatewayUrl}`);
|
|
416
|
+
try {
|
|
417
|
+
const identity = this._ensureIdentity();
|
|
418
|
+
if (!identity.cert) {
|
|
419
|
+
const created = await this._createAid(gatewayUrl, identity);
|
|
420
|
+
Object.assign(identity, created);
|
|
421
|
+
this._persistIdentity(identity);
|
|
422
|
+
}
|
|
423
|
+
const login = await this._login(gatewayUrl, identity);
|
|
424
|
+
AuthFlow._rememberTokens(identity, login);
|
|
425
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
396
426
|
this._persistIdentity(identity);
|
|
427
|
+
const token = identity.access_token || identity.token || identity.kite_token;
|
|
428
|
+
if (!token) {
|
|
429
|
+
throw new AuthError('login2 did not return access token');
|
|
430
|
+
}
|
|
431
|
+
this._logger.debug(`ensureAuthenticated exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
432
|
+
return { token, identity };
|
|
397
433
|
}
|
|
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');
|
|
434
|
+
catch (err) {
|
|
435
|
+
this._logger.debug(`ensureAuthenticated exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
436
|
+
throw err;
|
|
405
437
|
}
|
|
406
|
-
return { token, identity };
|
|
407
438
|
}
|
|
408
439
|
/**
|
|
409
440
|
* 刷新缓存的 token。
|
|
410
441
|
* 使用 refresh_token 获取新的 access_token。
|
|
411
442
|
*/
|
|
412
443
|
async refreshCachedTokens(gatewayUrl, identity) {
|
|
444
|
+
const tStart = Date.now();
|
|
413
445
|
const refreshToken = String(identity.refresh_token || '');
|
|
414
446
|
if (!refreshToken) {
|
|
447
|
+
this._logger.error(`token refresh failed: missing refresh_token, aid=${identity.aid}`);
|
|
415
448
|
throw new AuthError('missing refresh_token');
|
|
416
449
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
450
|
+
this._logger.debug(`refreshCachedTokens enter: aid=${identity.aid}`);
|
|
451
|
+
try {
|
|
452
|
+
const refreshed = await this._refreshAccessToken(gatewayUrl, refreshToken);
|
|
453
|
+
AuthFlow._rememberTokens(identity, refreshed);
|
|
454
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
455
|
+
this._persistIdentity(identity);
|
|
456
|
+
this._logger.debug(`refreshCachedTokens exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
457
|
+
return identity;
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
this._logger.debug(`refreshCachedTokens exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
461
|
+
throw err;
|
|
462
|
+
}
|
|
422
463
|
}
|
|
423
464
|
/**
|
|
424
465
|
* 使用 token 初始化 WebSocket 会话。
|
|
425
466
|
* 发送 auth.connect RPC 完成会话握手。
|
|
426
467
|
*/
|
|
427
468
|
async initializeWithToken(transport, challenge, accessToken, opts) {
|
|
428
|
-
const
|
|
429
|
-
this.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
469
|
+
const tStart = Date.now();
|
|
470
|
+
this._logger.debug(`initializeWithToken enter: deviceId=${opts?.deviceId ?? ''}, slotId=${opts?.slotId ?? ''}`);
|
|
471
|
+
try {
|
|
472
|
+
const nonce = this._extractChallengeNonce(challenge);
|
|
473
|
+
this.setInstanceContext({
|
|
474
|
+
deviceId: String(opts?.deviceId ?? ''),
|
|
475
|
+
slotId: String(opts?.slotId ?? ''),
|
|
476
|
+
});
|
|
477
|
+
await this._initializeSession(transport, nonce, accessToken, {
|
|
478
|
+
deviceId: String(opts?.deviceId ?? ''),
|
|
479
|
+
slotId: String(opts?.slotId ?? ''),
|
|
480
|
+
deliveryMode: opts?.deliveryMode ?? null,
|
|
481
|
+
});
|
|
482
|
+
this._logger.debug(`initializeWithToken exit: elapsed=${Date.now() - tStart}ms`);
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
this._logger.debug(`initializeWithToken exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
486
|
+
throw err;
|
|
487
|
+
}
|
|
438
488
|
}
|
|
439
489
|
/**
|
|
440
490
|
* 连接会话(自动选择认证策略)。
|
|
441
491
|
* 依次尝试:显式 token → 缓存 token → 刷新 token → 完整重认证。
|
|
442
492
|
*/
|
|
443
493
|
async connectSession(transport, challenge, gatewayUrl, opts) {
|
|
494
|
+
const tStart = Date.now();
|
|
444
495
|
const nonce = this._extractChallengeNonce(challenge);
|
|
445
496
|
const deviceId = String(opts?.deviceId ?? '');
|
|
446
497
|
const slotId = String(opts?.slotId ?? '');
|
|
447
498
|
const deliveryMode = opts?.deliveryMode ?? null;
|
|
448
499
|
this.setInstanceContext({ deviceId, slotId });
|
|
449
|
-
|
|
500
|
+
this._logger.debug(`connectSession enter: gateway=${gatewayUrl}, device_id=${deviceId}, slot_id=${slotId}, has_explicit_token=${!!opts?.accessToken}, has_challenge_nonce=${!!nonce}`);
|
|
450
501
|
try {
|
|
451
|
-
identity
|
|
452
|
-
}
|
|
453
|
-
catch {
|
|
454
|
-
identity = null;
|
|
455
|
-
}
|
|
456
|
-
// 策略 1:显式 token
|
|
457
|
-
const explicitToken = String(opts?.accessToken || '');
|
|
458
|
-
if (explicitToken && identity !== null) {
|
|
502
|
+
let identity;
|
|
459
503
|
try {
|
|
460
|
-
|
|
461
|
-
deviceId,
|
|
462
|
-
slotId,
|
|
463
|
-
deliveryMode,
|
|
464
|
-
});
|
|
465
|
-
identity.access_token = explicitToken;
|
|
466
|
-
this._persistIdentity(identity);
|
|
467
|
-
return { token: explicitToken, identity };
|
|
504
|
+
identity = this.loadIdentity();
|
|
468
505
|
}
|
|
469
|
-
catch
|
|
470
|
-
|
|
471
|
-
throw exc;
|
|
472
|
-
this._logger.debug(`explicit_token 认证失败,尝试下一方式: ${exc.message}`);
|
|
506
|
+
catch {
|
|
507
|
+
identity = null;
|
|
473
508
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
509
|
+
// 策略 1:显式 token
|
|
510
|
+
const explicitToken = String(opts?.accessToken || '');
|
|
511
|
+
if (explicitToken && identity !== null) {
|
|
512
|
+
try {
|
|
513
|
+
await this._initializeSession(transport, nonce, explicitToken, {
|
|
514
|
+
deviceId,
|
|
515
|
+
slotId,
|
|
516
|
+
deliveryMode,
|
|
517
|
+
});
|
|
518
|
+
identity.access_token = explicitToken;
|
|
519
|
+
this._persistIdentity(identity);
|
|
520
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=explicit_token aid=${String(identity.aid ?? '')}`);
|
|
521
|
+
return { token: explicitToken, identity };
|
|
522
|
+
}
|
|
523
|
+
catch (exc) {
|
|
524
|
+
if (!(exc instanceof AuthError))
|
|
525
|
+
throw exc;
|
|
526
|
+
this._logger.debug(`explicit_token auth failed, try next method: ${exc.message}`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// 无本地身份时,执行完整认证
|
|
530
|
+
if (identity === null) {
|
|
531
|
+
const authContext = await this.ensureAuthenticated(gatewayUrl);
|
|
532
|
+
const token = String(authContext.token);
|
|
533
|
+
await this._initializeSession(transport, nonce, token, {
|
|
491
534
|
deviceId,
|
|
492
535
|
slotId,
|
|
493
536
|
deliveryMode,
|
|
494
537
|
});
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
catch (exc) {
|
|
498
|
-
if (!(exc instanceof AuthError))
|
|
499
|
-
throw exc;
|
|
500
|
-
this._logger.debug(`cached_token 认证失败,尝试刷新: ${exc.message}`);
|
|
538
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=ensure_authenticated aid=${String(authContext.identity?.aid ?? '')}`);
|
|
539
|
+
return authContext;
|
|
501
540
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
identity = await this.refreshCachedTokens(gatewayUrl, identity);
|
|
508
|
-
const newCachedToken = AuthFlow._getCachedAccessToken(identity);
|
|
509
|
-
if (newCachedToken) {
|
|
510
|
-
await this._initializeSession(transport, nonce, newCachedToken, {
|
|
541
|
+
// 策略 2:缓存的 access_token
|
|
542
|
+
const cachedToken = AuthFlow._getCachedAccessToken(identity);
|
|
543
|
+
if (cachedToken) {
|
|
544
|
+
try {
|
|
545
|
+
await this._initializeSession(transport, nonce, cachedToken, {
|
|
511
546
|
deviceId,
|
|
512
547
|
slotId,
|
|
513
548
|
deliveryMode,
|
|
514
549
|
});
|
|
515
|
-
|
|
550
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=cached_token aid=${String(identity.aid ?? '')}`);
|
|
551
|
+
return { token: cachedToken, identity };
|
|
552
|
+
}
|
|
553
|
+
catch (exc) {
|
|
554
|
+
if (!(exc instanceof AuthError))
|
|
555
|
+
throw exc;
|
|
556
|
+
this._logger.debug(`cached_token auth failed, try refresh: ${exc.message}`);
|
|
516
557
|
}
|
|
517
558
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
559
|
+
// 策略 3:refresh_token
|
|
560
|
+
const refreshToken = String(identity.refresh_token || '');
|
|
561
|
+
if (refreshToken) {
|
|
562
|
+
try {
|
|
563
|
+
identity = await this.refreshCachedTokens(gatewayUrl, identity);
|
|
564
|
+
const newCachedToken = AuthFlow._getCachedAccessToken(identity);
|
|
565
|
+
if (newCachedToken) {
|
|
566
|
+
await this._initializeSession(transport, nonce, newCachedToken, {
|
|
567
|
+
deviceId,
|
|
568
|
+
slotId,
|
|
569
|
+
deliveryMode,
|
|
570
|
+
});
|
|
571
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=refresh_token aid=${String(identity.aid ?? '')}`);
|
|
572
|
+
return { token: newCachedToken, identity };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch (exc) {
|
|
576
|
+
if (!(exc instanceof AuthError))
|
|
577
|
+
throw exc;
|
|
578
|
+
this._logger.debug(`refresh_token auth failed, will re-login: ${exc.message}`);
|
|
579
|
+
}
|
|
522
580
|
}
|
|
581
|
+
// 策略 4:完整重认证
|
|
582
|
+
const login = await this.authenticate(gatewayUrl, { aid: identity.aid });
|
|
583
|
+
const token = String(login.access_token || '');
|
|
584
|
+
if (!token) {
|
|
585
|
+
throw new AuthError('authenticate did not return access_token');
|
|
586
|
+
}
|
|
587
|
+
await this._initializeSession(transport, nonce, token, {
|
|
588
|
+
deviceId,
|
|
589
|
+
slotId,
|
|
590
|
+
deliveryMode,
|
|
591
|
+
});
|
|
592
|
+
identity = this.loadIdentity(identity.aid);
|
|
593
|
+
this._logger.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=full_reauth aid=${String(identity?.aid ?? '')}`);
|
|
594
|
+
return { token, identity };
|
|
523
595
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (!token) {
|
|
528
|
-
throw new AuthError('authenticate did not return access_token');
|
|
596
|
+
catch (err) {
|
|
597
|
+
this._logger.debug(`connectSession exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
598
|
+
throw err;
|
|
529
599
|
}
|
|
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
600
|
}
|
|
538
601
|
/**
|
|
539
602
|
* 验证对端证书:时间有效性 + 链验证 + CRL + OCSP + AID 绑定。
|
|
540
603
|
* 用于 E2EE 握手中验证通信对端的身份证书。
|
|
541
604
|
*/
|
|
542
605
|
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
|
-
}
|
|
606
|
+
const tStart = Date.now();
|
|
607
|
+
this._logger.debug(`verifyPeerCertificate enter: aid=${expectedAid}, gateway=${gatewayUrl}`);
|
|
556
608
|
try {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
609
|
+
const cert = _loadX509(certPem);
|
|
610
|
+
const nowMs = Date.now();
|
|
611
|
+
_ensureCertTimeValid(cert, 'peer certificate', nowMs);
|
|
612
|
+
await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
|
|
613
|
+
try {
|
|
614
|
+
await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
|
|
615
|
+
}
|
|
616
|
+
catch (e) {
|
|
617
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
618
|
+
if (/revoked/i.test(errMsg))
|
|
619
|
+
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
620
|
+
this._logger.warn(`CRL verification unavailable, skipping: ${errMsg}`);
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
await this._verifyAuthCertOcsp(gatewayUrl, cert, expectedAid);
|
|
624
|
+
}
|
|
625
|
+
catch (exc) {
|
|
626
|
+
const errMsg = exc instanceof Error ? exc.message : String(exc);
|
|
627
|
+
if (/revoked/i.test(errMsg))
|
|
628
|
+
throw exc instanceof AuthError ? exc : new AuthError(errMsg);
|
|
629
|
+
this._logger.warn(`OCSP verification unavailable, skipping: ${errMsg}`);
|
|
630
|
+
}
|
|
631
|
+
// 检查 CN 匹配
|
|
632
|
+
const cn = _certSubjectCN(cert);
|
|
633
|
+
if (cn !== expectedAid) {
|
|
634
|
+
throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}, got ${cn || 'none'}`);
|
|
635
|
+
}
|
|
636
|
+
this._logger.debug(`verifyPeerCertificate exit: elapsed=${Date.now() - tStart}ms aid=${expectedAid}`);
|
|
564
637
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}, got ${cn || 'none'}`);
|
|
638
|
+
catch (err) {
|
|
639
|
+
this._logger.debug(`verifyPeerCertificate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
640
|
+
throw err;
|
|
569
641
|
}
|
|
570
642
|
}
|
|
571
643
|
// ── 内部方法:短连接 RPC ────────────────────────────────────
|
|
@@ -574,6 +646,7 @@ export class AuthFlow {
|
|
|
574
646
|
* 流程:连接 → 接收 challenge → 发送请求 → 接收响应 → 关闭。
|
|
575
647
|
*/
|
|
576
648
|
async _shortRpc(gatewayUrl, method, params) {
|
|
649
|
+
this._logger.debug(`_shortRpc enter: method=${method}, gateway=${gatewayUrl}`);
|
|
577
650
|
const ws = await this._connectionFactory(gatewayUrl);
|
|
578
651
|
try {
|
|
579
652
|
// 接收 challenge(第一条消息)
|
|
@@ -651,38 +724,60 @@ export class AuthFlow {
|
|
|
651
724
|
}
|
|
652
725
|
/** 两阶段登录 */
|
|
653
726
|
async _login(gatewayUrl, identity) {
|
|
727
|
+
const tStart = Date.now();
|
|
654
728
|
const clientNonce = this._crypto.newClientNonce();
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
729
|
+
this._logger.debug(`_login enter: aid=${identity.aid}`);
|
|
730
|
+
try {
|
|
731
|
+
// Phase 1: 发送 AID + 证书 + 客户端 nonce
|
|
732
|
+
const phase1 = await this._shortRpc(gatewayUrl, 'auth.aid_login1', {
|
|
733
|
+
aid: identity.aid,
|
|
734
|
+
cert: identity.cert,
|
|
735
|
+
client_nonce: clientNonce,
|
|
736
|
+
});
|
|
737
|
+
this._logger.debug(`login1 response recv: aid=${identity.aid}, request_id=${phase1.request_id}`);
|
|
738
|
+
// 验证 Phase 1 响应(证书链 + 签名)
|
|
739
|
+
await this._verifyPhase1Response(gatewayUrl, phase1, clientNonce);
|
|
740
|
+
this._logger.debug(`login1 verify ok: aid=${identity.aid}`);
|
|
741
|
+
// Phase 2: 用私钥签名 nonce
|
|
742
|
+
const [signature, clientTime] = this._crypto.signLoginNonce(String(identity.private_key_pem), String(phase1.nonce));
|
|
743
|
+
this._logger.debug(`login2 start: aid=${identity.aid}`);
|
|
744
|
+
const phase2 = await this._shortRpc(gatewayUrl, 'auth.aid_login2', {
|
|
745
|
+
aid: identity.aid,
|
|
746
|
+
request_id: phase1.request_id,
|
|
747
|
+
nonce: phase1.nonce,
|
|
748
|
+
client_time: clientTime,
|
|
749
|
+
signature,
|
|
750
|
+
});
|
|
751
|
+
this._logger.debug(`_login exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}, has_token=${!!phase2.access_token || !!phase2.token}`);
|
|
752
|
+
return phase2;
|
|
753
|
+
}
|
|
754
|
+
catch (err) {
|
|
755
|
+
this._logger.debug(`_login exit (error): elapsed=${Date.now() - tStart}ms aid=${identity.aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
756
|
+
throw err;
|
|
757
|
+
}
|
|
673
758
|
}
|
|
674
759
|
/** 刷新 access_token */
|
|
675
760
|
async _refreshAccessToken(gatewayUrl, refreshToken) {
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
761
|
+
const tStart = Date.now();
|
|
762
|
+
this._logger.debug(`_refreshAccessToken enter`);
|
|
763
|
+
try {
|
|
764
|
+
const result = await this._shortRpc(gatewayUrl, 'auth.refresh_token', {
|
|
765
|
+
refresh_token: refreshToken,
|
|
766
|
+
});
|
|
767
|
+
if (!result.success) {
|
|
768
|
+
throw new AuthError(String(result.error || 'refresh failed'));
|
|
769
|
+
}
|
|
770
|
+
this._logger.debug(`_refreshAccessToken exit: elapsed=${Date.now() - tStart}ms`);
|
|
771
|
+
return result;
|
|
772
|
+
}
|
|
773
|
+
catch (err) {
|
|
774
|
+
this._logger.debug(`_refreshAccessToken exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
775
|
+
throw err;
|
|
681
776
|
}
|
|
682
|
-
return result;
|
|
683
777
|
}
|
|
684
778
|
/** 会话初始化:发送 auth.connect RPC */
|
|
685
779
|
async _initializeSession(transport, nonce, token, opts) {
|
|
780
|
+
this._logger.debug(`session init start: deviceId=${opts?.deviceId ?? ''}, slotId=${opts?.slotId ?? ''}`);
|
|
686
781
|
const result = await transport.call('auth.connect', {
|
|
687
782
|
nonce,
|
|
688
783
|
auth: { method: 'kite_token', token },
|
|
@@ -697,8 +792,10 @@ export class AuthFlow {
|
|
|
697
792
|
});
|
|
698
793
|
const status = isJsonObject(result) ? result.status : undefined;
|
|
699
794
|
if (status !== 'ok') {
|
|
795
|
+
this._logger.error(`sessioninitfailed: status=${status}, result=${JSON.stringify(result)}`);
|
|
700
796
|
throw new AuthError(`initialize failed: ${JSON.stringify(result)}`);
|
|
701
797
|
}
|
|
798
|
+
this._logger.debug('sessioninitok');
|
|
702
799
|
}
|
|
703
800
|
// ── 内部方法:证书验证 ──────────────────────────────────────
|
|
704
801
|
/**
|
|
@@ -732,7 +829,7 @@ export class AuthFlow {
|
|
|
732
829
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
733
830
|
if (/revoked/i.test(errMsg))
|
|
734
831
|
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
735
|
-
this._logger.warn(`CRL
|
|
832
|
+
this._logger.warn(`CRL verification unavailable, skipping: ${errMsg}`);
|
|
736
833
|
}
|
|
737
834
|
try {
|
|
738
835
|
await this._verifyAuthCertOcsp(gatewayUrl, authCert);
|
|
@@ -741,7 +838,7 @@ export class AuthFlow {
|
|
|
741
838
|
const errMsg = exc instanceof Error ? exc.message : String(exc);
|
|
742
839
|
if (/revoked/i.test(errMsg))
|
|
743
840
|
throw exc instanceof AuthError ? exc : new AuthError(errMsg);
|
|
744
|
-
this._logger.warn(`OCSP
|
|
841
|
+
this._logger.warn(`OCSP verification unavailable, skipping: ${errMsg}`);
|
|
745
842
|
}
|
|
746
843
|
// 验证 client_nonce 签名
|
|
747
844
|
try {
|
|
@@ -915,7 +1012,7 @@ export class AuthFlow {
|
|
|
915
1012
|
revokedSerials = new Set(serialsArr.map((s) => String(s).toLowerCase()));
|
|
916
1013
|
}
|
|
917
1014
|
// 如果两者都没有,返回空集合(无吊销记录)
|
|
918
|
-
this._logger.debug('CRL PEM
|
|
1015
|
+
this._logger.debug('CRL PEM parse failed, using JSON revoked_serials fallback');
|
|
919
1016
|
}
|
|
920
1017
|
// 缓存 TTL:默认 5 分钟,最大 24 小时
|
|
921
1018
|
const now = Date.now();
|
|
@@ -1088,7 +1185,7 @@ export class AuthFlow {
|
|
|
1088
1185
|
// OCSP DER 解析失败时,降级信赖 JSON status 字段
|
|
1089
1186
|
if (e instanceof AuthError)
|
|
1090
1187
|
throw e;
|
|
1091
|
-
this._logger.debug(`OCSP DER
|
|
1188
|
+
this._logger.debug(`OCSP DER parse failed, using JSON status fallback: ${e instanceof Error ? e.message : String(e)}`);
|
|
1092
1189
|
if (!status) {
|
|
1093
1190
|
throw new AuthError('gateway OCSP endpoint returned invalid response and no status field');
|
|
1094
1191
|
}
|
|
@@ -1359,7 +1456,7 @@ export class AuthFlow {
|
|
|
1359
1456
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
1360
1457
|
if (/revoked/i.test(errMsg))
|
|
1361
1458
|
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
1362
|
-
this._logger.warn(`CRL
|
|
1459
|
+
this._logger.warn(`CRL verification unavailable, skipping: ${errMsg}`);
|
|
1363
1460
|
}
|
|
1364
1461
|
try {
|
|
1365
1462
|
await this._verifyAuthCertOcsp(gatewayUrl, cert);
|
|
@@ -1368,7 +1465,7 @@ export class AuthFlow {
|
|
|
1368
1465
|
const errMsg = exc instanceof Error ? exc.message : String(exc);
|
|
1369
1466
|
if (/revoked/i.test(errMsg))
|
|
1370
1467
|
throw exc instanceof AuthError ? exc : new AuthError(errMsg);
|
|
1371
|
-
this._logger.warn(`OCSP
|
|
1468
|
+
this._logger.warn(`OCSP verification unavailable, skipping: ${errMsg}`);
|
|
1372
1469
|
}
|
|
1373
1470
|
}
|
|
1374
1471
|
// 验证通过,正式接受
|
|
@@ -1376,10 +1473,10 @@ export class AuthFlow {
|
|
|
1376
1473
|
}
|
|
1377
1474
|
catch (e) {
|
|
1378
1475
|
if (e instanceof AuthError) {
|
|
1379
|
-
this._logger.warn(
|
|
1476
|
+
this._logger.warn(`rejected server new_cert (${identity.aid}): ${e.message}`);
|
|
1380
1477
|
}
|
|
1381
1478
|
else {
|
|
1382
|
-
this._logger.warn(`new_cert
|
|
1479
|
+
this._logger.warn(`new_cert verification error (${identity.aid}): ${e instanceof Error ? e.message : String(e)}`);
|
|
1383
1480
|
}
|
|
1384
1481
|
}
|
|
1385
1482
|
// active_cert 同步:验证公钥匹配后更新本地 cert
|
|
@@ -1396,12 +1493,12 @@ export class AuthFlow {
|
|
|
1396
1493
|
identity.cert = activeCertPem;
|
|
1397
1494
|
}
|
|
1398
1495
|
else {
|
|
1399
|
-
this._logger.warn(
|
|
1496
|
+
this._logger.warn(`active_cert public key mismatch with local private key, rejecting sync (aid=${identity.aid})`);
|
|
1400
1497
|
}
|
|
1401
1498
|
}
|
|
1402
1499
|
}
|
|
1403
1500
|
catch (e) {
|
|
1404
|
-
this._logger.warn(`active_cert
|
|
1501
|
+
this._logger.warn(`active_cert sync error (${identity.aid}): ${e instanceof Error ? e.message : String(e)}`);
|
|
1405
1502
|
}
|
|
1406
1503
|
}
|
|
1407
1504
|
}
|