@agentunion/fastaun-browser 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.d.ts +3 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +293 -211
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +11 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1080 -812
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/discovery.d.ts +3 -0
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +15 -1
- package/dist/discovery.js.map +1 -1
- package/dist/e2ee-group.d.ts +4 -0
- package/dist/e2ee-group.d.ts.map +1 -1
- package/dist/e2ee-group.js +327 -201
- package/dist/e2ee-group.js.map +1 -1
- package/dist/e2ee.d.ts +4 -0
- package/dist/e2ee.d.ts.map +1 -1
- package/dist/e2ee.js +176 -117
- package/dist/e2ee.js.map +1 -1
- package/dist/events.d.ts +3 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +4 -1
- package/dist/events.js.map +1 -1
- package/dist/keystore/indexeddb.d.ts +3 -0
- package/dist/keystore/indexeddb.d.ts.map +1 -1
- package/dist/keystore/indexeddb.js +153 -97
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/logger.d.ts +37 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +112 -0
- package/dist/logger.js.map +1 -0
- package/dist/namespaces/auth.d.ts +4 -0
- package/dist/namespaces/auth.d.ts.map +1 -1
- package/dist/namespaces/auth.js +214 -101
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/namespaces/custody.d.ts +3 -0
- package/dist/namespaces/custody.d.ts.map +1 -1
- package/dist/namespaces/custody.js +147 -75
- package/dist/namespaces/custody.js.map +1 -1
- package/dist/namespaces/meta.d.ts +3 -0
- package/dist/namespaces/meta.d.ts.map +1 -1
- package/dist/namespaces/meta.js +94 -43
- package/dist/namespaces/meta.js.map +1 -1
- package/dist/secret-store/indexeddb-store.d.ts +3 -0
- package/dist/secret-store/indexeddb-store.d.ts.map +1 -1
- package/dist/secret-store/indexeddb-store.js +57 -29
- package/dist/secret-store/indexeddb-store.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +74 -4
- package/dist/transport.js.map +1 -1
- package/package.json +1 -1
package/dist/auth.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { base64ToUint8, uint8ToBase64, pemToArrayBuffer, toBufferSource } from './crypto.js';
|
|
5
5
|
import { AuthError, StateError, ValidationError, mapRemoteError } from './errors.js';
|
|
6
6
|
import { ROOT_CA_PEM } from './certs/root.js';
|
|
7
|
+
const _noopLog = { error: () => { }, warn: () => { }, info: () => { }, debug: () => { } };
|
|
7
8
|
import { isJsonObject, } from './types.js';
|
|
8
9
|
// ── ASN.1 / PEM 工具 ────────────────────────────────────
|
|
9
10
|
/** 拆分 PEM bundle 为独立的 PEM 字符串数组 */
|
|
@@ -334,6 +335,8 @@ function gatewayHttpUrl(gatewayUrl, path) {
|
|
|
334
335
|
* - 证书自动续期
|
|
335
336
|
*/
|
|
336
337
|
export class AuthFlow {
|
|
338
|
+
_log = _noopLog;
|
|
339
|
+
setLogger(log) { this._log = log; }
|
|
337
340
|
static _INSTANCE_STATE_FIELDS = [
|
|
338
341
|
'access_token',
|
|
339
342
|
'refresh_token',
|
|
@@ -368,14 +371,23 @@ export class AuthFlow {
|
|
|
368
371
|
// ── 公开 API ──────────────────────────────────────
|
|
369
372
|
/** 加载本地身份信息 */
|
|
370
373
|
async loadIdentity(aid) {
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
identity
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
374
|
+
const tStart = Date.now();
|
|
375
|
+
this._log.debug(`loadIdentity enter: aid=${aid ?? '<current>'}`);
|
|
376
|
+
try {
|
|
377
|
+
const identity = await this._loadIdentityOrRaise(aid);
|
|
378
|
+
const cert = await this._keystore.loadCert(identity.aid);
|
|
379
|
+
if (cert)
|
|
380
|
+
identity.cert = cert;
|
|
381
|
+
const instanceState = await this._loadInstanceState(identity.aid);
|
|
382
|
+
if (instanceState)
|
|
383
|
+
Object.assign(identity, instanceState);
|
|
384
|
+
this._log.debug(`loadIdentity exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid} has_cert=${!!identity.cert}`);
|
|
385
|
+
return identity;
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
this._log.debug(`loadIdentity exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
389
|
+
throw err;
|
|
390
|
+
}
|
|
379
391
|
}
|
|
380
392
|
/** 加载身份,不存在时返回 null */
|
|
381
393
|
async loadIdentityOrNull(aid) {
|
|
@@ -410,265 +422,335 @@ export class AuthFlow {
|
|
|
410
422
|
* 3. 保存返回的证书
|
|
411
423
|
*/
|
|
412
424
|
async createAid(gatewayUrl, aid) {
|
|
425
|
+
const tStart = Date.now();
|
|
426
|
+
this._log.debug(`createAid enter: aid=${aid} gateway=${gatewayUrl}`);
|
|
413
427
|
AuthFlow._validateAidName(aid);
|
|
414
|
-
const identity = await this._ensureLocalIdentity(aid);
|
|
415
|
-
if (identity.cert) {
|
|
416
|
-
return { aid: identity.aid, cert: identity.cert };
|
|
417
|
-
}
|
|
418
|
-
// 本地有密钥但无证书 — 尝试注册
|
|
419
428
|
try {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
+
const identity = await this._ensureLocalIdentity(aid);
|
|
430
|
+
if (identity.cert) {
|
|
431
|
+
this._log.debug(`createAid exit: elapsed=${Date.now() - tStart}ms aid=${aid} reason=already_has_cert`);
|
|
432
|
+
return { aid: identity.aid, cert: identity.cert };
|
|
433
|
+
}
|
|
434
|
+
// 本地有密钥但无证书 — 尝试注册
|
|
435
|
+
try {
|
|
436
|
+
const created = await this._createAid(gatewayUrl, identity);
|
|
437
|
+
Object.assign(identity, created);
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
if (e instanceof Error && e.message.includes('already exists')) {
|
|
441
|
+
// AID 已在服务端注册,尝试下载证书恢复
|
|
442
|
+
try {
|
|
443
|
+
const recovered = await this._recoverCertViaDownload(gatewayUrl, identity);
|
|
444
|
+
Object.assign(identity, recovered);
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
this._log.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=already_registered_recover_failed`);
|
|
448
|
+
throw new StateError(`AID ${aid} already registered on server but local certificate is missing. ` +
|
|
449
|
+
`Certificate download recovery failed. Options: ` +
|
|
450
|
+
`(1) use a different AID name, or ` +
|
|
451
|
+
`(2) restart server to clear registration.`);
|
|
452
|
+
}
|
|
429
453
|
}
|
|
430
|
-
|
|
431
|
-
throw
|
|
432
|
-
`Certificate download recovery failed. Options: ` +
|
|
433
|
-
`(1) use a different AID name, or ` +
|
|
434
|
-
`(2) restart server to clear registration.`);
|
|
454
|
+
else {
|
|
455
|
+
throw e;
|
|
435
456
|
}
|
|
436
457
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
458
|
+
await this._persistIdentity(identity);
|
|
459
|
+
this._aid = identity.aid;
|
|
460
|
+
this._log.debug(`createAid exit: elapsed=${Date.now() - tStart}ms aid=${aid}`);
|
|
461
|
+
return { aid: identity.aid, cert: identity.cert };
|
|
462
|
+
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
this._log.debug(`createAid exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
465
|
+
throw err;
|
|
440
466
|
}
|
|
441
|
-
await this._persistIdentity(identity);
|
|
442
|
-
this._aid = identity.aid;
|
|
443
|
-
return { aid: identity.aid, cert: identity.cert };
|
|
444
467
|
}
|
|
445
468
|
/**
|
|
446
469
|
* 认证已有 AID — login1/login2 双阶段流程。
|
|
447
470
|
*/
|
|
448
471
|
async authenticate(gatewayUrl, aid) {
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
472
|
+
const tStart = Date.now();
|
|
473
|
+
this._log.debug(`authenticate enter: aid=${aid ?? '<current>'} gateway=${gatewayUrl}`);
|
|
474
|
+
try {
|
|
475
|
+
const identity = await this._loadIdentityOrRaise(aid);
|
|
476
|
+
if (!identity.cert) {
|
|
477
|
+
// 尝试下载恢复证书
|
|
478
|
+
try {
|
|
479
|
+
const recovered = await this._recoverCertViaDownload(gatewayUrl, identity);
|
|
480
|
+
Object.assign(identity, recovered);
|
|
481
|
+
await this._persistIdentity(identity);
|
|
482
|
+
}
|
|
483
|
+
catch (e) {
|
|
484
|
+
throw new StateError(`local certificate missing and recovery failed: ${e}. ` +
|
|
485
|
+
`Run auth.createAid() to register a new identity.`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
let login;
|
|
452
489
|
try {
|
|
453
|
-
|
|
454
|
-
Object.assign(identity, recovered);
|
|
455
|
-
await this._persistIdentity(identity);
|
|
490
|
+
login = await this._login(gatewayUrl, identity);
|
|
456
491
|
}
|
|
457
492
|
catch (e) {
|
|
458
|
-
|
|
459
|
-
|
|
493
|
+
// 证书未在服务端注册或公钥不匹配 — 自动重新注册
|
|
494
|
+
if (e instanceof AuthError && (String(e.message).includes('not registered') || String(e.message).includes('public key mismatch'))) {
|
|
495
|
+
this._log.warn(`[auth] cert not registered on server, auto re-register: aid=${identity.aid}`);
|
|
496
|
+
const created = await this._createAid(gatewayUrl, identity);
|
|
497
|
+
identity.cert = created.cert;
|
|
498
|
+
await this._persistIdentity(identity);
|
|
499
|
+
login = await this._login(gatewayUrl, identity);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
throw e;
|
|
503
|
+
}
|
|
460
504
|
}
|
|
505
|
+
this._rememberTokens(identity, login);
|
|
506
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
507
|
+
await this._persistIdentity(identity);
|
|
508
|
+
this._aid = identity.aid;
|
|
509
|
+
this._log.debug(`authenticate exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
510
|
+
return {
|
|
511
|
+
aid: identity.aid,
|
|
512
|
+
access_token: identity.access_token,
|
|
513
|
+
refresh_token: identity.refresh_token,
|
|
514
|
+
expires_at: identity.access_token_expires_at,
|
|
515
|
+
gateway: gatewayUrl,
|
|
516
|
+
};
|
|
461
517
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
}
|
|
466
|
-
catch (e) {
|
|
467
|
-
// 证书未在服务端注册或公钥不匹配 — 自动重新注册
|
|
468
|
-
if (e instanceof AuthError && (String(e.message).includes('not registered') || String(e.message).includes('public key mismatch'))) {
|
|
469
|
-
console.warn(`[auth] 证书未在服务端注册,自动重新注册: aid=${identity.aid}`);
|
|
470
|
-
const created = await this._createAid(gatewayUrl, identity);
|
|
471
|
-
identity.cert = created.cert;
|
|
472
|
-
await this._persistIdentity(identity);
|
|
473
|
-
login = await this._login(gatewayUrl, identity);
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
throw e;
|
|
477
|
-
}
|
|
518
|
+
catch (err) {
|
|
519
|
+
this._log.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
520
|
+
throw err;
|
|
478
521
|
}
|
|
479
|
-
this._rememberTokens(identity, login);
|
|
480
|
-
await this._validateNewCert(identity, gatewayUrl);
|
|
481
|
-
await this._persistIdentity(identity);
|
|
482
|
-
this._aid = identity.aid;
|
|
483
|
-
return {
|
|
484
|
-
aid: identity.aid,
|
|
485
|
-
access_token: identity.access_token,
|
|
486
|
-
refresh_token: identity.refresh_token,
|
|
487
|
-
expires_at: identity.access_token_expires_at,
|
|
488
|
-
gateway: gatewayUrl,
|
|
489
|
-
};
|
|
490
522
|
}
|
|
491
523
|
/**
|
|
492
524
|
* 确保已认证(如无身份则先注册再登录)。
|
|
493
525
|
*/
|
|
494
526
|
async ensureAuthenticated(gatewayUrl) {
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
527
|
+
const tStart = Date.now();
|
|
528
|
+
this._log.debug(`ensureAuthenticated enter: gateway=${gatewayUrl}`);
|
|
529
|
+
try {
|
|
530
|
+
const identity = await this._ensureIdentity();
|
|
531
|
+
if (!identity.cert) {
|
|
532
|
+
const created = await this._createAid(gatewayUrl, identity);
|
|
533
|
+
Object.assign(identity, created);
|
|
534
|
+
await this._persistIdentity(identity);
|
|
535
|
+
}
|
|
536
|
+
const login = await this._login(gatewayUrl, identity);
|
|
537
|
+
this._rememberTokens(identity, login);
|
|
538
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
499
539
|
await this._persistIdentity(identity);
|
|
540
|
+
const token = (identity.access_token || identity.token || identity.kite_token);
|
|
541
|
+
if (!token)
|
|
542
|
+
throw new AuthError('login2 did not return access token');
|
|
543
|
+
this._log.debug(`ensureAuthenticated exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
544
|
+
return { token, identity };
|
|
545
|
+
}
|
|
546
|
+
catch (err) {
|
|
547
|
+
this._log.debug(`ensureAuthenticated exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
548
|
+
throw err;
|
|
500
549
|
}
|
|
501
|
-
const login = await this._login(gatewayUrl, identity);
|
|
502
|
-
this._rememberTokens(identity, login);
|
|
503
|
-
await this._validateNewCert(identity, gatewayUrl);
|
|
504
|
-
await this._persistIdentity(identity);
|
|
505
|
-
const token = (identity.access_token || identity.token || identity.kite_token);
|
|
506
|
-
if (!token)
|
|
507
|
-
throw new AuthError('login2 did not return access token');
|
|
508
|
-
return { token, identity };
|
|
509
550
|
}
|
|
510
551
|
/**
|
|
511
552
|
* 使用已有 token 初始化 WebSocket 会话。
|
|
512
553
|
*/
|
|
513
554
|
async initializeWithToken(transport, challenge, accessToken, opts) {
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
555
|
+
const tStart = Date.now();
|
|
556
|
+
this._log.debug(`initializeWithToken enter: device_id=${opts?.deviceId ?? this._deviceId} slot_id=${opts?.slotId ?? this._slotId}`);
|
|
557
|
+
try {
|
|
558
|
+
const params = isJsonObject(challenge?.params) ? challenge.params : {};
|
|
559
|
+
const nonce = String(params.nonce ?? '');
|
|
560
|
+
if (!nonce)
|
|
561
|
+
throw new AuthError('gateway challenge missing nonce');
|
|
562
|
+
this.setInstanceContext({
|
|
563
|
+
deviceId: String(opts?.deviceId ?? this._deviceId ?? ''),
|
|
564
|
+
slotId: String(opts?.slotId ?? this._slotId ?? ''),
|
|
565
|
+
});
|
|
566
|
+
await this._initializeSession(transport, nonce, accessToken, {
|
|
567
|
+
deviceId: String(opts?.deviceId ?? this._deviceId ?? ''),
|
|
568
|
+
slotId: String(opts?.slotId ?? this._slotId ?? ''),
|
|
569
|
+
deliveryMode: opts?.deliveryMode ?? null,
|
|
570
|
+
});
|
|
571
|
+
this._log.debug(`initializeWithToken exit: elapsed=${Date.now() - tStart}ms`);
|
|
572
|
+
}
|
|
573
|
+
catch (err) {
|
|
574
|
+
this._log.debug(`initializeWithToken exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
575
|
+
throw err;
|
|
576
|
+
}
|
|
527
577
|
}
|
|
528
578
|
/**
|
|
529
579
|
* 连接会话 — 多策略认证:显式 token → 缓存 token → refresh → 重新登录。
|
|
530
580
|
*/
|
|
531
581
|
async connectSession(transport, challenge, gatewayUrl, accessToken) {
|
|
532
|
-
const
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
throw new AuthError('gateway challenge missing nonce');
|
|
536
|
-
const connectOptions = isJsonObject(accessToken)
|
|
537
|
-
? accessToken
|
|
538
|
-
: { accessToken };
|
|
539
|
-
const deviceId = String(connectOptions.deviceId ?? this._deviceId ?? '');
|
|
540
|
-
const slotId = String(connectOptions.slotId ?? this._slotId ?? '');
|
|
541
|
-
const deliveryMode = connectOptions.deliveryMode ?? null;
|
|
542
|
-
this.setInstanceContext({ deviceId, slotId });
|
|
543
|
-
let identity;
|
|
582
|
+
const tStart = Date.now();
|
|
583
|
+
const explicitTokenInput = isJsonObject(accessToken) ? !!accessToken.accessToken : !!accessToken;
|
|
584
|
+
this._log.debug(`connectSession enter: gateway=${gatewayUrl} has_explicit_token=${explicitTokenInput}`);
|
|
544
585
|
try {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
586
|
+
const params = isJsonObject(challenge?.params) ? challenge.params : {};
|
|
587
|
+
const nonce = String(params.nonce ?? '');
|
|
588
|
+
if (!nonce)
|
|
589
|
+
throw new AuthError('gateway challenge missing nonce');
|
|
590
|
+
const connectOptions = isJsonObject(accessToken)
|
|
591
|
+
? accessToken
|
|
592
|
+
: { accessToken };
|
|
593
|
+
const deviceId = String(connectOptions.deviceId ?? this._deviceId ?? '');
|
|
594
|
+
const slotId = String(connectOptions.slotId ?? this._slotId ?? '');
|
|
595
|
+
const deliveryMode = connectOptions.deliveryMode ?? null;
|
|
596
|
+
this.setInstanceContext({ deviceId, slotId });
|
|
597
|
+
let identity;
|
|
553
598
|
try {
|
|
554
|
-
await this.
|
|
555
|
-
deviceId,
|
|
556
|
-
slotId,
|
|
557
|
-
deliveryMode,
|
|
558
|
-
});
|
|
559
|
-
identity.access_token = explicitToken;
|
|
560
|
-
await this._persistIdentity(identity);
|
|
561
|
-
return { token: explicitToken, identity };
|
|
599
|
+
identity = await this.loadIdentity();
|
|
562
600
|
}
|
|
563
|
-
catch
|
|
564
|
-
|
|
565
|
-
throw e;
|
|
566
|
-
// 显式 token 失败,继续尝试其他方式
|
|
601
|
+
catch {
|
|
602
|
+
identity = null;
|
|
567
603
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
604
|
+
// 策略 1: 显式 token
|
|
605
|
+
const explicitToken = String(connectOptions.accessToken ?? '');
|
|
606
|
+
if (explicitToken && identity !== null) {
|
|
607
|
+
try {
|
|
608
|
+
await this._initializeSession(transport, nonce, explicitToken, {
|
|
609
|
+
deviceId,
|
|
610
|
+
slotId,
|
|
611
|
+
deliveryMode,
|
|
612
|
+
});
|
|
613
|
+
identity.access_token = explicitToken;
|
|
614
|
+
await this._persistIdentity(identity);
|
|
615
|
+
this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=explicit_token aid=${identity.aid}`);
|
|
616
|
+
return { token: explicitToken, identity };
|
|
617
|
+
}
|
|
618
|
+
catch (e) {
|
|
619
|
+
if (!(e instanceof AuthError))
|
|
620
|
+
throw e;
|
|
621
|
+
// 显式 token 失败,继续尝试其他方式
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
// 无本地身份 — 先注册 + 登录
|
|
625
|
+
if (identity === null) {
|
|
626
|
+
const authContext = await this.ensureAuthenticated(gatewayUrl);
|
|
627
|
+
const token = authContext.token;
|
|
628
|
+
await this._initializeSession(transport, nonce, token, {
|
|
585
629
|
deviceId,
|
|
586
630
|
slotId,
|
|
587
631
|
deliveryMode,
|
|
588
632
|
});
|
|
589
|
-
|
|
633
|
+
this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=ensure_authenticated`);
|
|
634
|
+
return authContext;
|
|
590
635
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
// 策略 3: refresh token
|
|
598
|
-
const refreshToken = String(identity.refresh_token ?? '');
|
|
599
|
-
if (refreshToken) {
|
|
600
|
-
try {
|
|
601
|
-
identity = await this.refreshCachedTokens(gatewayUrl, identity);
|
|
602
|
-
const refreshedToken = this._getCachedAccessToken(identity);
|
|
603
|
-
if (refreshedToken) {
|
|
604
|
-
await this._initializeSession(transport, nonce, refreshedToken, {
|
|
636
|
+
// 策略 2: 缓存 token
|
|
637
|
+
const cachedToken = this._getCachedAccessToken(identity);
|
|
638
|
+
if (cachedToken) {
|
|
639
|
+
try {
|
|
640
|
+
await this._initializeSession(transport, nonce, cachedToken, {
|
|
605
641
|
deviceId,
|
|
606
642
|
slotId,
|
|
607
643
|
deliveryMode,
|
|
608
644
|
});
|
|
609
|
-
|
|
645
|
+
this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=cached_token aid=${identity.aid}`);
|
|
646
|
+
return { token: cachedToken, identity };
|
|
647
|
+
}
|
|
648
|
+
catch (e) {
|
|
649
|
+
if (!(e instanceof AuthError))
|
|
650
|
+
throw e;
|
|
651
|
+
// 缓存 token 失败,尝试刷新
|
|
610
652
|
}
|
|
611
653
|
}
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
654
|
+
// 策略 3: refresh token
|
|
655
|
+
const refreshToken = String(identity.refresh_token ?? '');
|
|
656
|
+
if (refreshToken) {
|
|
657
|
+
try {
|
|
658
|
+
identity = await this.refreshCachedTokens(gatewayUrl, identity);
|
|
659
|
+
const refreshedToken = this._getCachedAccessToken(identity);
|
|
660
|
+
if (refreshedToken) {
|
|
661
|
+
await this._initializeSession(transport, nonce, refreshedToken, {
|
|
662
|
+
deviceId,
|
|
663
|
+
slotId,
|
|
664
|
+
deliveryMode,
|
|
665
|
+
});
|
|
666
|
+
this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=refresh aid=${identity.aid}`);
|
|
667
|
+
return { token: refreshedToken, identity };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
catch (e) {
|
|
671
|
+
if (!(e instanceof AuthError))
|
|
672
|
+
throw e;
|
|
673
|
+
// refresh 失败,重新登录
|
|
674
|
+
}
|
|
616
675
|
}
|
|
676
|
+
// 策略 4: 重新登录
|
|
677
|
+
const login = await this.authenticate(gatewayUrl, identity.aid);
|
|
678
|
+
const token = String(login.access_token ?? '');
|
|
679
|
+
if (!token)
|
|
680
|
+
throw new AuthError('authenticate did not return access_token');
|
|
681
|
+
await this._initializeSession(transport, nonce, token, {
|
|
682
|
+
deviceId,
|
|
683
|
+
slotId,
|
|
684
|
+
deliveryMode,
|
|
685
|
+
});
|
|
686
|
+
identity = await this.loadIdentity(identity.aid);
|
|
687
|
+
this._log.debug(`connectSession exit: elapsed=${Date.now() - tStart}ms strategy=re_login aid=${identity.aid}`);
|
|
688
|
+
return { token, identity };
|
|
689
|
+
}
|
|
690
|
+
catch (err) {
|
|
691
|
+
this._log.debug(`connectSession exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
692
|
+
throw err;
|
|
617
693
|
}
|
|
618
|
-
// 策略 4: 重新登录
|
|
619
|
-
const login = await this.authenticate(gatewayUrl, identity.aid);
|
|
620
|
-
const token = String(login.access_token ?? '');
|
|
621
|
-
if (!token)
|
|
622
|
-
throw new AuthError('authenticate did not return access_token');
|
|
623
|
-
await this._initializeSession(transport, nonce, token, {
|
|
624
|
-
deviceId,
|
|
625
|
-
slotId,
|
|
626
|
-
deliveryMode,
|
|
627
|
-
});
|
|
628
|
-
identity = await this.loadIdentity(identity.aid);
|
|
629
|
-
return { token, identity };
|
|
630
694
|
}
|
|
631
695
|
/**
|
|
632
696
|
* 刷新 token。
|
|
633
697
|
*/
|
|
634
698
|
async refreshCachedTokens(gatewayUrl, identity) {
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
699
|
+
const tStart = Date.now();
|
|
700
|
+
this._log.debug(`refreshCachedTokens enter: aid=${identity.aid} gateway=${gatewayUrl}`);
|
|
701
|
+
try {
|
|
702
|
+
const refreshToken = String(identity.refresh_token ?? '');
|
|
703
|
+
if (!refreshToken)
|
|
704
|
+
throw new AuthError('missing refresh_token');
|
|
705
|
+
const refreshed = await this._refreshAccessToken(gatewayUrl, refreshToken);
|
|
706
|
+
this._rememberTokens(identity, refreshed);
|
|
707
|
+
await this._validateNewCert(identity, gatewayUrl);
|
|
708
|
+
await this._persistIdentity(identity);
|
|
709
|
+
this._log.debug(`refreshCachedTokens exit: elapsed=${Date.now() - tStart}ms aid=${identity.aid}`);
|
|
710
|
+
return identity;
|
|
711
|
+
}
|
|
712
|
+
catch (err) {
|
|
713
|
+
this._log.debug(`refreshCachedTokens exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
714
|
+
throw err;
|
|
715
|
+
}
|
|
643
716
|
}
|
|
644
717
|
/**
|
|
645
718
|
* 统一的对端证书验证入口:时间有效性 + 链验证 + CRL + OCSP + AID 绑定。
|
|
646
719
|
*/
|
|
647
720
|
async verifyPeerCertificate(gatewayUrl, certPem, expectedAid) {
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
ensureCertTimeValid(cert, 'peer certificate', now);
|
|
651
|
-
await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
|
|
652
|
-
try {
|
|
653
|
-
await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
|
|
654
|
-
}
|
|
655
|
-
catch (e) {
|
|
656
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
657
|
-
if (/revoked/i.test(errMsg))
|
|
658
|
-
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
659
|
-
console.warn('[aun_core.auth] CRL 检查不可用,降级继续:', errMsg);
|
|
660
|
-
}
|
|
721
|
+
const tStart = Date.now();
|
|
722
|
+
this._log.debug(`verifyPeerCertificate enter: aid=${expectedAid} gateway=${gatewayUrl}`);
|
|
661
723
|
try {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
724
|
+
const cert = parseCertDer(certPem);
|
|
725
|
+
const now = Date.now() / 1000;
|
|
726
|
+
ensureCertTimeValid(cert, 'peer certificate', now);
|
|
727
|
+
await this._verifyAuthCertChain(gatewayUrl, cert, expectedAid);
|
|
728
|
+
try {
|
|
729
|
+
await this._verifyAuthCertRevocation(gatewayUrl, cert, expectedAid);
|
|
730
|
+
}
|
|
731
|
+
catch (e) {
|
|
732
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
733
|
+
if (/revoked/i.test(errMsg))
|
|
734
|
+
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
735
|
+
this._log.warn('[aun_core.auth] CRL check unavailable, degrade and continue:', errMsg);
|
|
736
|
+
}
|
|
737
|
+
try {
|
|
738
|
+
await this._verifyAuthCertOcsp(gatewayUrl, cert, expectedAid);
|
|
739
|
+
}
|
|
740
|
+
catch (e) {
|
|
741
|
+
const errMsg = e instanceof Error ? e.message : String(e);
|
|
742
|
+
if (/revoked/i.test(errMsg))
|
|
743
|
+
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
744
|
+
this._log.warn('[aun_core.auth] OCSP check unavailable, degrade and continue:', errMsg);
|
|
745
|
+
}
|
|
746
|
+
if (cert.subjectCN !== expectedAid) {
|
|
747
|
+
throw new AuthError(`peer cert CN mismatch: expected ${expectedAid}, got ${cert.subjectCN || 'none'}`);
|
|
748
|
+
}
|
|
749
|
+
this._log.debug(`verifyPeerCertificate exit: elapsed=${Date.now() - tStart}ms aid=${expectedAid}`);
|
|
669
750
|
}
|
|
670
|
-
|
|
671
|
-
|
|
751
|
+
catch (err) {
|
|
752
|
+
this._log.debug(`verifyPeerCertificate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
753
|
+
throw err;
|
|
672
754
|
}
|
|
673
755
|
}
|
|
674
756
|
// ── 内部方法:短连接 RPC ──────────────────────────
|
|
@@ -889,7 +971,7 @@ export class AuthFlow {
|
|
|
889
971
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
890
972
|
if (/revoked/i.test(errMsg))
|
|
891
973
|
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
892
|
-
|
|
974
|
+
this._log.warn('[aun_core.auth] CRL check unavailable, degrade and continue:', errMsg);
|
|
893
975
|
}
|
|
894
976
|
// 验证 OCSP
|
|
895
977
|
try {
|
|
@@ -899,7 +981,7 @@ export class AuthFlow {
|
|
|
899
981
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
900
982
|
if (/revoked/i.test(errMsg))
|
|
901
983
|
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
902
|
-
|
|
984
|
+
this._log.warn('[aun_core.auth] OCSP check unavailable, degrade and continue:', errMsg);
|
|
903
985
|
}
|
|
904
986
|
// 验证 client_nonce 签名
|
|
905
987
|
try {
|
|
@@ -1224,7 +1306,7 @@ export class AuthFlow {
|
|
|
1224
1306
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
1225
1307
|
if (/revoked/i.test(errMsg))
|
|
1226
1308
|
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
1227
|
-
|
|
1309
|
+
this._log.warn('[aun_core.auth] CRL check unavailable, degrade and continue:', errMsg);
|
|
1228
1310
|
}
|
|
1229
1311
|
try {
|
|
1230
1312
|
await this._verifyAuthCertOcsp(gatewayUrl, cert);
|
|
@@ -1233,7 +1315,7 @@ export class AuthFlow {
|
|
|
1233
1315
|
const errMsg = e instanceof Error ? e.message : String(e);
|
|
1234
1316
|
if (/revoked/i.test(errMsg))
|
|
1235
1317
|
throw e instanceof AuthError ? e : new AuthError(errMsg);
|
|
1236
|
-
|
|
1318
|
+
this._log.warn('[aun_core.auth] OCSP check unavailable, degrade and continue:', errMsg);
|
|
1237
1319
|
}
|
|
1238
1320
|
}
|
|
1239
1321
|
// 验证通过,正式接受
|
|
@@ -1241,7 +1323,7 @@ export class AuthFlow {
|
|
|
1241
1323
|
}
|
|
1242
1324
|
catch (e) {
|
|
1243
1325
|
// 验证失败,静默拒绝(不影响主流程)
|
|
1244
|
-
|
|
1326
|
+
this._log.warn(`reject server-returned new_cert (${identity.aid}):`, e);
|
|
1245
1327
|
}
|
|
1246
1328
|
// active_cert 同步:验证公钥匹配后更新本地 cert
|
|
1247
1329
|
const activeCertPem = identity._pending_active_cert;
|
|
@@ -1256,12 +1338,12 @@ export class AuthFlow {
|
|
|
1256
1338
|
identity.cert = activeCertPem;
|
|
1257
1339
|
}
|
|
1258
1340
|
else {
|
|
1259
|
-
|
|
1341
|
+
this._log.warn(`[auth] active_cert public key mismatches local private key, reject sync (aid=${identity.aid})`);
|
|
1260
1342
|
}
|
|
1261
1343
|
}
|
|
1262
1344
|
}
|
|
1263
1345
|
catch (e) {
|
|
1264
|
-
|
|
1346
|
+
this._log.warn(`[auth] active_cert sync exception (${identity.aid}):`, e);
|
|
1265
1347
|
}
|
|
1266
1348
|
}
|
|
1267
1349
|
}
|