@agentunion/fastaun 0.5.20 → 0.5.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/_packed_docs/CHANGELOG.md +47 -0
- package/_packed_docs/INDEX.md +453 -440
- package/_packed_docs/KITE_DOCS_GUIDE.md +164 -161
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
- package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
- package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -658
- package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
- package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +147 -14
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
- package/_packed_docs/sdk/09-group-rpc-manual.md +5 -2
- package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
- package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +134 -130
- package/_packed_docs/sdk/INDEX.md +239 -221
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -922
- package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +106 -43
- package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +180 -9
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -708
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -476
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +1 -1
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +2 -2
- package/dist/agent-md-schema.js +6 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +22 -2
- package/dist/agent-md.js +84 -67
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.js +50 -15
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.js +13 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +59 -1
- package/dist/client/delivery.js +1018 -125
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.js +123 -37
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +12 -2
- package/dist/client/v2-e2ee.js +751 -294
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +379 -63
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +18 -1
- package/dist/events.js +148 -9
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +43 -1
- package/dist/facades.js +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +6 -1
- package/dist/keystore/aid-db.js +184 -42
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -0
- package/dist/keystore/local-token-store.d.ts +9 -1
- package/dist/keystore/local-token-store.js +79 -14
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.js +754 -11
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +16 -3
- package/dist/ownership-v21.js +35 -17
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +17 -0
- package/dist/ownership-workflow.js +511 -26
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/secret-store/file-store.js +84 -0
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.js +2 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +66 -3
- package/dist/tools/cross-sdk-agent.js +466 -36
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +98 -44
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/v2/e2ee/encrypt-group.js +1 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -26,7 +26,7 @@ import { normalizeGroupId } from './group-id.js';
|
|
|
26
26
|
import { validateAIDFormat, validateGroupIDFormat } from './validators.js';
|
|
27
27
|
import { RPCTransport } from './transport.js';
|
|
28
28
|
import { AuthFlow } from './auth.js';
|
|
29
|
-
import { AgentMdManager, buildDefaultAgentMd
|
|
29
|
+
import { AgentMdManager, buildDefaultAgentMd } from './agent-md.js';
|
|
30
30
|
import { GroupIndexMetaCache } from './group-index.js';
|
|
31
31
|
import { SeqTracker } from './seq-tracker.js';
|
|
32
32
|
import { ClientRuntime } from './client/runtime.js';
|
|
@@ -37,7 +37,7 @@ import { LifecycleController } from './client/lifecycle.js';
|
|
|
37
37
|
import { PeerDirectory } from './client/peers.js';
|
|
38
38
|
import { RpcPipeline } from './client/rpc-pipeline.js';
|
|
39
39
|
import { CollabClient } from './collab/client.js';
|
|
40
|
-
import { CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
|
|
40
|
+
import { DebugFacade, CredentialFacade, OrgFacade, GroupFacade, MailFacade, MessageFacade, StreamFacade } from './facades.js';
|
|
41
41
|
import { StorageVFS } from './storage/vfs.js';
|
|
42
42
|
import { V2E2EECoordinator } from './client/v2-e2ee.js';
|
|
43
43
|
import { GroupStateCoordinator } from './client/group-state.js';
|
|
@@ -48,9 +48,9 @@ import { isJsonObject, ConnectionState, STATE_TO_PUBLIC, } from './types.js';
|
|
|
48
48
|
import { AID } from './aid.js';
|
|
49
49
|
import { certFingerprint, certMatchesFingerprint, normalizeFingerprintHex, publicKeyMatchesFingerprint } from './cert-utils.js';
|
|
50
50
|
import { validateAgentMdCertificate, validateAgentMdDocument } from './agent-md-schema.js';
|
|
51
|
-
import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure
|
|
51
|
+
import { OwnershipFiles, OwnershipStorageAdapter, ownershipFailure } from './ownership-v21.js';
|
|
52
52
|
import { OwnershipWorkflow } from './ownership-workflow.js';
|
|
53
|
-
import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, verifyServiceEvent } from './org-protocol.js';
|
|
53
|
+
import { canonicalJson, createCredential, createMemberCredential, signMembershipBinding, digest, validateMembershipBinding, verifyMembershipBinding, verifyCredentialSignature, createRepresentationProof, verifyServiceEvent, buildRequestBinding, validateRequestBinding, CredentialRenewal } from './org-protocol.js';
|
|
54
54
|
function isPromiseLike(value) {
|
|
55
55
|
return Boolean(value && typeof value.then === 'function');
|
|
56
56
|
}
|
|
@@ -478,6 +478,7 @@ export class AUNClient {
|
|
|
478
478
|
/** 当前 AID 值对象(新 API) */
|
|
479
479
|
_currentAid = null;
|
|
480
480
|
_serviceEventIdentityVersion = 0;
|
|
481
|
+
_serviceEventIdentityAbort = new AbortController();
|
|
481
482
|
/** ownership 关系只在 SDK 内存中编排;服务端不保存状态机。 */
|
|
482
483
|
_ownershipRecords = new Map();
|
|
483
484
|
_ownershipType = 'agent';
|
|
@@ -494,9 +495,9 @@ export class AUNClient {
|
|
|
494
495
|
currentType: () => this.getType(),
|
|
495
496
|
now: () => Math.trunc(Date.now() / 1000),
|
|
496
497
|
readAgentMdFresh: (subAid) => this._agentMdManager.readOwnershipFresh(subAid),
|
|
497
|
-
submitAgentMdRaw: (subAid, raw, condition) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition),
|
|
498
|
+
submitAgentMdRaw: (subAid, raw, condition, assertActive) => this._agentMdManager.submitOwnershipRaw(subAid, raw, condition, assertActive),
|
|
498
499
|
readOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().readOwnerMd(ownerAid, subAid),
|
|
499
|
-
writeOwnerMd: (ownerAid, subAid, raw) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw),
|
|
500
|
+
writeOwnerMd: (ownerAid, subAid, raw, original, assertActive) => this._getOwnershipStorage().writeOwnerMd(ownerAid, subAid, raw, original, assertActive),
|
|
500
501
|
deleteOwnerMd: (ownerAid, subAid) => this._getOwnershipStorage().deleteOwnerMd(ownerAid, subAid),
|
|
501
502
|
resolveCertificate: async (aid, fingerprint, purpose) => this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(aid), aid, fingerprint, purpose),
|
|
502
503
|
});
|
|
@@ -549,6 +550,188 @@ export class AUNClient {
|
|
|
549
550
|
await this._resumeRegistrationIntent();
|
|
550
551
|
await this._resumeOwnership();
|
|
551
552
|
}
|
|
553
|
+
_credentialRenewal;
|
|
554
|
+
_credentialRenewalTimer = null;
|
|
555
|
+
_credentialRenewalAbort = null;
|
|
556
|
+
_credentialRenewalScan = Promise.resolve();
|
|
557
|
+
_getCredentialRenewal() {
|
|
558
|
+
return this._credentialRenewal ??= new CredentialRenewal({
|
|
559
|
+
currentAid: () => this._aid ?? '',
|
|
560
|
+
atomicCheckpointSave: () => this._tokenStore instanceof LocalTokenStore,
|
|
561
|
+
load: (aid) => (this._tokenStore._loadMetadataStrict?.(aid) ?? this._tokenStore.loadMetadata?.(aid))?.credential_renewals_v1 ?? {},
|
|
562
|
+
...(typeof this._tokenStore._loadMetadataStrict === 'function' ? {
|
|
563
|
+
loadRevocations: (aid) => this._tokenStore._loadMetadataStrict(aid)?.credential_revocations_v1 ?? {},
|
|
564
|
+
saveRevocations: (aid, state, guard) => {
|
|
565
|
+
if (guard && !guard.current())
|
|
566
|
+
throw new ValidationError('credential revocation synchronization was superseded');
|
|
567
|
+
this._tokenStore.saveMetadata?.(aid, { credential_revocations_v1: state });
|
|
568
|
+
},
|
|
569
|
+
} : {}),
|
|
570
|
+
save: (aid, records, guard) => {
|
|
571
|
+
const version = this._serviceEventIdentityVersion;
|
|
572
|
+
const current = () => version === this._serviceEventIdentityVersion && aid === this._aid && (!guard || guard.current());
|
|
573
|
+
if (!current())
|
|
574
|
+
throw new ValidationError('membership event receiver identity changed');
|
|
575
|
+
const saved = this._tokenStore instanceof LocalTokenStore ? records : Object.fromEntries(Object.entries(records).map(([cid, value]) => {
|
|
576
|
+
const record = { ...value };
|
|
577
|
+
for (const key of ['submitted_at', 'submission_hash', 'registration_submitted_at', 'registration_submission_hash'])
|
|
578
|
+
delete record[key];
|
|
579
|
+
return [cid, record];
|
|
580
|
+
}));
|
|
581
|
+
this._tokenStore.saveMetadata?.(aid, { credential_renewals_v1: saved });
|
|
582
|
+
if (!current())
|
|
583
|
+
throw new ValidationError('membership event receiver identity changed');
|
|
584
|
+
},
|
|
585
|
+
signer: (aid, guard) => this._credentialRenewalSigner(aid, guard),
|
|
586
|
+
certificate: (aid, fingerprint, signedAt) => signedAt === undefined
|
|
587
|
+
? this._fetchServiceEventCertificate(aid, fingerprint) : this._fetchPeerCert(aid, fingerprint, undefined, signedAt),
|
|
588
|
+
call: (method, params, onSent) => {
|
|
589
|
+
if (!onSent)
|
|
590
|
+
return this._rpcPipeline.call(method, params);
|
|
591
|
+
const version = this._serviceEventIdentityVersion, aid = this._aid;
|
|
592
|
+
return this._rpcPipeline['_callWithSent'](method, params, async () => {
|
|
593
|
+
if (version !== this._serviceEventIdentityVersion || aid !== this._aid)
|
|
594
|
+
throw new ValidationError('credential sender identity changed');
|
|
595
|
+
await onSent();
|
|
596
|
+
});
|
|
597
|
+
},
|
|
598
|
+
send: (to, payload) => this.call('message.send', { to, encrypt: true, payload }),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
_assertCredentialNotRevoked(credential, proofChain) {
|
|
602
|
+
const version = this._serviceEventIdentityVersion, aid = this._aid;
|
|
603
|
+
const current = () => { if (aid !== this._aid || version !== this._serviceEventIdentityVersion)
|
|
604
|
+
throw new ValidationError('credential receiver identity changed'); };
|
|
605
|
+
this._getCredentialRenewal()._assertNotRevoked(credential, proofChain);
|
|
606
|
+
current();
|
|
607
|
+
return current;
|
|
608
|
+
}
|
|
609
|
+
async _credentialRenewalSigner(aid, guard) {
|
|
610
|
+
const check = () => { if (guard && !guard.current())
|
|
611
|
+
throw new ValidationError('credential renewal was superseded'); };
|
|
612
|
+
check();
|
|
613
|
+
const stored = this._aidStore?.load(aid);
|
|
614
|
+
let identity = aid === this._aid && this._identity?.private_key_pem
|
|
615
|
+
? AID._create({ aid, aunPath: this._configModel.aunPath, certPem: String(this._identity.cert ?? this._currentAid?.certPem ?? ''), privateKeyPem: String(this._identity.private_key_pem), certValid: true, privateKeyValid: true })
|
|
616
|
+
: stored?.ok ? stored.data.aid : undefined;
|
|
617
|
+
if (!identity?.isPrivateKeyValid())
|
|
618
|
+
return null;
|
|
619
|
+
const certificate = new crypto.X509Certificate(identity.certPem);
|
|
620
|
+
const end = Date.parse(certificate.validTo), start = Date.parse(certificate.validFrom);
|
|
621
|
+
if (end > Date.now() && end - Date.now() < (end - start) / 2 && this._aidStore) {
|
|
622
|
+
const renewed = await (guard ? this._aidStore._renewCert(aid, guard) : this._aidStore.renewCert(aid));
|
|
623
|
+
check();
|
|
624
|
+
if (!renewed.ok)
|
|
625
|
+
throw new ValidationError(renewed.error.message);
|
|
626
|
+
const loaded = this._aidStore.load(aid);
|
|
627
|
+
if (!loaded.ok)
|
|
628
|
+
throw new ValidationError(loaded.error.message);
|
|
629
|
+
identity = loaded.data.aid;
|
|
630
|
+
if (aid === this._aid && this._identity) {
|
|
631
|
+
this._identity.cert = identity.certPem;
|
|
632
|
+
this._currentAid = identity;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return identity;
|
|
636
|
+
}
|
|
637
|
+
_currentCredential(input) {
|
|
638
|
+
const records = this._tokenStore.loadMetadata?.(this._aid ?? '')?.credential_renewals_v1 ?? {};
|
|
639
|
+
let cid = typeof input === 'string' ? input : String(input.cid);
|
|
640
|
+
const seen = new Set();
|
|
641
|
+
while (records[cid]?.successor_cid && !seen.has(cid)) {
|
|
642
|
+
seen.add(cid);
|
|
643
|
+
cid = String(records[cid].successor_cid);
|
|
644
|
+
}
|
|
645
|
+
return records[cid] ? structuredClone(records[cid]) : null;
|
|
646
|
+
}
|
|
647
|
+
_credentialCurrent(input) {
|
|
648
|
+
return this._getCredentialRenewal().current(input);
|
|
649
|
+
}
|
|
650
|
+
async _resumeCredentialRenewalAfterConnect(guard) {
|
|
651
|
+
if (!this._aid || (guard && !guard.current()))
|
|
652
|
+
return;
|
|
653
|
+
await this._safeAsync(guard ? this._credentialRenewalSigner(this._aid, guard) : this._credentialRenewalSigner(this._aid));
|
|
654
|
+
if (!guard || guard.current())
|
|
655
|
+
await this._safeAsync(this._getCredentialRenewal()._syncRevocations?.(guard) ?? Promise.resolve());
|
|
656
|
+
if (!guard || guard.current())
|
|
657
|
+
await this._getCredentialRenewal().resume(guard);
|
|
658
|
+
}
|
|
659
|
+
_startCredentialRenewal() {
|
|
660
|
+
this._stopCredentialRenewal();
|
|
661
|
+
const aid = this._aid, version = this._serviceEventIdentityVersion;
|
|
662
|
+
if (!aid || !['ready', 'connected'].includes(this._state) || this._closing || this._disconnecting)
|
|
663
|
+
return;
|
|
664
|
+
const abort = new AbortController();
|
|
665
|
+
this._credentialRenewalAbort = abort;
|
|
666
|
+
const current = () => this._credentialRenewalAbort === abort && !abort.signal.aborted && aid === this._aid && version === this._serviceEventIdentityVersion && ['ready', 'connected'].includes(this._state) && !this._closing && !this._disconnecting;
|
|
667
|
+
const guard = { current, signal: abort.signal };
|
|
668
|
+
const scan = async () => {
|
|
669
|
+
const task = this._credentialRenewalScan.then(async () => {
|
|
670
|
+
if (!current())
|
|
671
|
+
return;
|
|
672
|
+
await this._resumeCredentialRenewalAfterConnect(guard);
|
|
673
|
+
});
|
|
674
|
+
this._credentialRenewalScan = task.catch(() => { });
|
|
675
|
+
await this._safeAsync(task);
|
|
676
|
+
if (current())
|
|
677
|
+
this._credentialRenewalTimer = setTimeout(() => { void scan(); }, 60_000);
|
|
678
|
+
};
|
|
679
|
+
void scan();
|
|
680
|
+
}
|
|
681
|
+
_stopCredentialRenewal() {
|
|
682
|
+
this._credentialRenewalAbort?.abort();
|
|
683
|
+
this._credentialRenewalAbort = null;
|
|
684
|
+
if (this._credentialRenewalTimer !== null)
|
|
685
|
+
clearTimeout(this._credentialRenewalTimer);
|
|
686
|
+
this._credentialRenewalTimer = null;
|
|
687
|
+
}
|
|
688
|
+
_rememberCredential(credential, material, certificate) {
|
|
689
|
+
if (!material.long_term && certificate) {
|
|
690
|
+
const cert = new crypto.X509Certificate(typeof certificate === 'string' && !certificate.includes('-----BEGIN CERTIFICATE-----') ? Buffer.from(certificate, 'base64') : certificate);
|
|
691
|
+
const parent = Array.isArray(credential.proof_chain) ? credential.proof_chain[0] : undefined;
|
|
692
|
+
const certificateEnd = Math.floor(Date.parse(cert.validTo) / 1000);
|
|
693
|
+
const defaultEnd = credential.parent != null && isJsonObject(parent) && typeof parent.exp === 'number'
|
|
694
|
+
? Math.min(certificateEnd, Number(parent.exp)) : certificateEnd;
|
|
695
|
+
material = { ...material, long_term: credential.exp === defaultEnd };
|
|
696
|
+
}
|
|
697
|
+
this._safeAsync(this._getCredentialRenewal().remember(credential, material));
|
|
698
|
+
}
|
|
699
|
+
async _verifyIncomingServiceEvent(event, payload) {
|
|
700
|
+
const targetAid = this._identity?.aid ?? '';
|
|
701
|
+
const version = this._serviceEventIdentityVersion;
|
|
702
|
+
const signal = this._serviceEventIdentityAbort.signal;
|
|
703
|
+
const expiresAt = Number(payload?.expires_at);
|
|
704
|
+
const current = () => version === this._serviceEventIdentityVersion && targetAid === (this._identity?.aid ?? '') && Date.now() / 1000 < expiresAt;
|
|
705
|
+
const verified = await verifyServiceEvent(event, payload, { targetAid, resolveEventCertificate: (aid, fingerprint) => this._fetchServiceEventCertificate(aid, fingerprint), resolveCredentialService: this.resolveCredentialService });
|
|
706
|
+
if (!current())
|
|
707
|
+
throw new ValidationError('service event receiver identity changed');
|
|
708
|
+
if (event.replace(/^event\//, '') === 'org.member.changed' && isJsonObject(payload) && Object.hasOwn(payload, 'membership_package')) {
|
|
709
|
+
await this._getCredentialRenewal().importMembershipPackage(payload, { current, signal });
|
|
710
|
+
if (!current())
|
|
711
|
+
throw new ValidationError('service event receiver identity changed');
|
|
712
|
+
}
|
|
713
|
+
return verified;
|
|
714
|
+
}
|
|
715
|
+
async _handleCredentialRenewal(message) {
|
|
716
|
+
if (!isJsonObject(message))
|
|
717
|
+
return false;
|
|
718
|
+
const envelope = message;
|
|
719
|
+
const content = 'payload' in envelope ? envelope.payload : envelope;
|
|
720
|
+
const payload = decryptedOwnershipJsonText(content) ?? content;
|
|
721
|
+
let parsed = payload;
|
|
722
|
+
if (typeof payload === 'string') {
|
|
723
|
+
try {
|
|
724
|
+
parsed = JSON.parse(payload);
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (!isJsonObject(parsed) || !['credential.renewal.request', 'credential.renewal.result'].includes(String(parsed.type)))
|
|
731
|
+
return false;
|
|
732
|
+
await this._safeAsync(this._getCredentialRenewal().receive(String(envelope.from ?? envelope.from_aid ?? envelope.sender_aid ?? ''), parsed));
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
552
735
|
async _resumeRegistrationIntent() {
|
|
553
736
|
const intent = this._tokenStore.loadMetadata?.(this._aid)?.registration_intent;
|
|
554
737
|
if (['group', 'org'].includes(String(intent?.type ?? this.getType())))
|
|
@@ -598,11 +781,13 @@ export class AUNClient {
|
|
|
598
781
|
_defaultConnectDeliveryMode;
|
|
599
782
|
/** peer 证书缓存 */
|
|
600
783
|
_certCache = new Map();
|
|
784
|
+
_certFetchInFlight = new Map();
|
|
601
785
|
_agentMdManager;
|
|
602
786
|
_aidStore;
|
|
603
787
|
_groupIdentityOperations = new Map();
|
|
604
788
|
_groupIndexMetaCache = new GroupIndexMetaCache();
|
|
605
789
|
/** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
|
|
790
|
+
_p2pPeerPullEnabled = true;
|
|
606
791
|
_seqTracker = new SeqTracker();
|
|
607
792
|
_seqTrackerContext = null;
|
|
608
793
|
/** 惰性群同步:已同步过的 group_id 集合 */
|
|
@@ -685,6 +870,7 @@ export class AUNClient {
|
|
|
685
870
|
_storage;
|
|
686
871
|
_collab;
|
|
687
872
|
_message;
|
|
873
|
+
_debug;
|
|
688
874
|
_mail;
|
|
689
875
|
_group;
|
|
690
876
|
_stream;
|
|
@@ -784,7 +970,7 @@ export class AUNClient {
|
|
|
784
970
|
dnsNet,
|
|
785
971
|
});
|
|
786
972
|
this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
|
|
787
|
-
this._transport.setServiceEventVerifier((event, payload) =>
|
|
973
|
+
this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
|
|
788
974
|
this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
|
|
789
975
|
this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
|
|
790
976
|
this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
|
|
@@ -817,7 +1003,8 @@ export class AUNClient {
|
|
|
817
1003
|
this._dispatcher.subscribeProtocol('_raw.message.read_changed', (data) => this._onRawReadChanged(data, 'p2p'));
|
|
818
1004
|
// V2 P2P 推送通知:收到通知后自动走 message.v2.pull 拉取并解密
|
|
819
1005
|
this._dispatcher.subscribeProtocol('_raw.peer.v2.message_received', (data) => {
|
|
820
|
-
const
|
|
1006
|
+
const sender = isJsonObject(data) ? String(data.sender_aid ?? '') : '';
|
|
1007
|
+
const ns = this._p2pPeerPullEnabled ? (sender ? this._delivery.peerNamespace(sender) : '') : this._aid ? `p2p:${this._aid}` : '';
|
|
821
1008
|
const hasInline = isJsonObject(data)
|
|
822
1009
|
&& isJsonObject(data.inline_message);
|
|
823
1010
|
const inlineGeneration = hasInline ? this._delivery.captureInlineAckGeneration() : undefined;
|
|
@@ -896,6 +1083,21 @@ export class AUNClient {
|
|
|
896
1083
|
this._collab = new CollabClient(this);
|
|
897
1084
|
return this._collab;
|
|
898
1085
|
}
|
|
1086
|
+
get debug() {
|
|
1087
|
+
if (!this._debug)
|
|
1088
|
+
this._debug = new DebugFacade(this, async (operation) => {
|
|
1089
|
+
const transport = this._transport;
|
|
1090
|
+
const previous = transport.getTraceMode();
|
|
1091
|
+
transport.setTraceMode('diag');
|
|
1092
|
+
try {
|
|
1093
|
+
return await operation();
|
|
1094
|
+
}
|
|
1095
|
+
finally {
|
|
1096
|
+
transport.setTraceMode(previous);
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
return this._debug;
|
|
1100
|
+
}
|
|
899
1101
|
/** Message facade 入口 */
|
|
900
1102
|
get message() {
|
|
901
1103
|
if (!this._message)
|
|
@@ -974,6 +1176,7 @@ export class AUNClient {
|
|
|
974
1176
|
}
|
|
975
1177
|
async bindOwneraid(ownerAid) { return this._getOwnershipWorkflow().bindOwneraid(ownerAid); }
|
|
976
1178
|
async bindSubaid(subAid) { return this._getOwnershipWorkflow().bindSubaid(subAid); }
|
|
1179
|
+
async resetOwnershipTask() { return this._getOwnershipWorkflow().resetOwnershipTask(); }
|
|
977
1180
|
async releaseSubaid(subAid) { return this._getOwnershipWorkflow().releaseSubaid(subAid); }
|
|
978
1181
|
async transferSubaid(subAid, newOwnerAid) { return this._getOwnershipWorkflow().transferSubaid(subAid, newOwnerAid); }
|
|
979
1182
|
async checkOwnershipConsensus(subAid) { return this._getOwnershipWorkflow().checkOwnershipConsensus(subAid); }
|
|
@@ -1173,7 +1376,7 @@ export class AUNClient {
|
|
|
1173
1376
|
dnsNet,
|
|
1174
1377
|
});
|
|
1175
1378
|
this._transport.setProtocolMetaObserver((meta) => this._observeRpcMeta(meta));
|
|
1176
|
-
this._transport.setServiceEventVerifier((event, payload) =>
|
|
1379
|
+
this._transport.setServiceEventVerifier((event, payload) => this._verifyIncomingServiceEvent(event, payload), () => this._identity?.aid ?? '', () => this._serviceEventIdentityVersion);
|
|
1177
1380
|
this._transport.setProtocolDisconnectCallback((err, closeCode) => this._handleTransportDisconnect(err, closeCode));
|
|
1178
1381
|
this._transport._onForegroundDrained = () => this._delivery.onForegroundPullsDrained();
|
|
1179
1382
|
this._transport._onRpcResponse = () => this._delivery.requestBackgroundPullRecovery();
|
|
@@ -1181,7 +1384,10 @@ export class AUNClient {
|
|
|
1181
1384
|
loadIdentity(aid) {
|
|
1182
1385
|
const previousAid = this._aid;
|
|
1183
1386
|
this._identityRuntime.loadIdentity(aid);
|
|
1387
|
+
this._stopCredentialRenewal();
|
|
1184
1388
|
this._serviceEventIdentityVersion++;
|
|
1389
|
+
this._serviceEventIdentityAbort.abort();
|
|
1390
|
+
this._serviceEventIdentityAbort = new AbortController();
|
|
1185
1391
|
this._ensureReadStateIdentity();
|
|
1186
1392
|
if (previousAid !== this._aid) {
|
|
1187
1393
|
this._ownershipRecords.clear();
|
|
@@ -1289,6 +1495,7 @@ export class AUNClient {
|
|
|
1289
1495
|
? this._ensureReadStateIdentity() : null;
|
|
1290
1496
|
try {
|
|
1291
1497
|
const result = await this._rpcPipeline.call(method, params);
|
|
1498
|
+
await this._safeAsync(this._getCredentialRenewal().observe(method, params ?? {}, result));
|
|
1292
1499
|
if (readIdentity !== null && readIdentity === this._ensureReadStateIdentity())
|
|
1293
1500
|
this._observeReadStateResult(method, result, params ?? {});
|
|
1294
1501
|
return result;
|
|
@@ -1318,6 +1525,13 @@ export class AUNClient {
|
|
|
1318
1525
|
const groupId = String(params.group_id ?? params.groupId ?? params.group_aid ?? params.groupAid ?? fallback).trim();
|
|
1319
1526
|
if (!groupId || !store)
|
|
1320
1527
|
return false;
|
|
1528
|
+
const groupManager = store._agentMdManager ?? this._agentMdManager;
|
|
1529
|
+
const keystore = store._keystore;
|
|
1530
|
+
let pendingBind = keystore?.loadPendingGroupBind?.(`bind:${groupId}`);
|
|
1531
|
+
if (!pendingBind?.public_key_der_b64 || !pendingBind?.private_key_pem)
|
|
1532
|
+
pendingBind = keystore?.loadPendingGroupBind?.(groupId);
|
|
1533
|
+
if (groupManager.pendingUpload(fallback) && (!pendingBind?.public_key_der_b64 || !pendingBind?.private_key_pem))
|
|
1534
|
+
return false;
|
|
1321
1535
|
try {
|
|
1322
1536
|
await this._runGroupIdentityOperation(groupId, () => this.bindGroupAid({ group_id: groupId }, { aidStore: store }));
|
|
1323
1537
|
}
|
|
@@ -1336,6 +1550,8 @@ export class AUNClient {
|
|
|
1336
1550
|
if (!loaded.ok || !loaded.data?.aid?.isPrivateKeyValid())
|
|
1337
1551
|
return false;
|
|
1338
1552
|
const groupManager = store._agentMdManager ?? this._agentMdManager;
|
|
1553
|
+
if (groupManager.pendingUpload(groupAid))
|
|
1554
|
+
return false;
|
|
1339
1555
|
if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? ''))
|
|
1340
1556
|
return false;
|
|
1341
1557
|
await this._runGroupIdentityOperation(groupAid, () => this._uploadGroupAgentMd(store, groupAid, {}, {}));
|
|
@@ -1353,17 +1569,15 @@ export class AUNClient {
|
|
|
1353
1569
|
const aid = this._aid;
|
|
1354
1570
|
if (!aid || !this._currentAid)
|
|
1355
1571
|
return false;
|
|
1356
|
-
const observed = await this.
|
|
1357
|
-
if (observed.exists || String(this._agentMdManager._loadRecord?.(aid, true)?.local_draft_content ?? this._agentMdManager.loadRecord(aid)?.local_draft_content ?? ''))
|
|
1572
|
+
const observed = await this._agentMdManager.readAgentMdFresh(aid);
|
|
1573
|
+
if (observed.exists || String(this._agentMdManager._loadRecord?.(aid, true)?.local_draft_content ?? this._agentMdManager.loadRecord(aid)?.local_draft_content ?? '') || this._agentMdManager.pendingUpload(aid))
|
|
1358
1574
|
return false;
|
|
1359
1575
|
let agentMd = buildDefaultAgentMd(aid, { type: this.getType() });
|
|
1360
1576
|
try {
|
|
1361
1577
|
agentMd = this._agentMdManager.readContent(aid) || agentMd;
|
|
1362
1578
|
}
|
|
1363
1579
|
catch { }
|
|
1364
|
-
|
|
1365
|
-
const raw = signOwnershipFile(this._currentAid, payload.endsWith('\n') || payload.endsWith('\r') ? payload : `${payload}\n`, 'agent');
|
|
1366
|
-
await this._agentMdManager.submitOwnershipRaw(aid, raw, { if_none_match: '*' });
|
|
1580
|
+
await this._agentMdManager.upload(agentMd, { if_none_match: '*' });
|
|
1367
1581
|
return true;
|
|
1368
1582
|
}
|
|
1369
1583
|
async _checkIdentityAdmissionAfterConnect() {
|
|
@@ -1418,6 +1632,8 @@ export class AUNClient {
|
|
|
1418
1632
|
return;
|
|
1419
1633
|
}
|
|
1420
1634
|
const groupManager = store._agentMdManager ?? this._agentMdManager;
|
|
1635
|
+
if (groupManager.pendingUpload(groupAid))
|
|
1636
|
+
return;
|
|
1421
1637
|
if (String((groupManager._loadRecord?.(groupAid, true) ?? groupManager.loadRecord(groupAid))?.local_draft_content ?? '')) {
|
|
1422
1638
|
await this._agentMdManager.check(groupAid);
|
|
1423
1639
|
return;
|
|
@@ -1427,8 +1643,6 @@ export class AUNClient {
|
|
|
1427
1643
|
await this.bindGroupAid({ group_id: groupId, group_aid: groupAid }, { aidStore: store });
|
|
1428
1644
|
return;
|
|
1429
1645
|
}
|
|
1430
|
-
if (await this._resumeGroupAgentMdUpload(store, groupAid))
|
|
1431
|
-
return;
|
|
1432
1646
|
const checked = await this._agentMdManager.check(groupAid);
|
|
1433
1647
|
if (!checked.remote_found) {
|
|
1434
1648
|
await this._uploadGroupAgentMd(store, groupAid, {}, {});
|
|
@@ -1459,12 +1673,45 @@ export class AUNClient {
|
|
|
1459
1673
|
this._groupIdentityOperations.delete(key);
|
|
1460
1674
|
}
|
|
1461
1675
|
}
|
|
1676
|
+
async _resumeOrgRequest(method, params, target) {
|
|
1677
|
+
const binding = params.request_binding;
|
|
1678
|
+
const now = Math.trunc(Date.now() / 1000);
|
|
1679
|
+
if (!isJsonObject(binding) || now < Number(binding.exp))
|
|
1680
|
+
return params;
|
|
1681
|
+
const credentials = isJsonObject(params.credentials) ? params.credentials : {};
|
|
1682
|
+
const credential = ['member_credential', 'approval_credential', 'audit_credential'].map((field) => credentials[field]).find(isJsonObject) ?? params.credential_template;
|
|
1683
|
+
const cid = isJsonObject(credential) ? String(credential.cid) : '';
|
|
1684
|
+
validateRequestBinding(binding, method, target, params, cid, String(params.nonce), String(params.request_id), Number(binding.iat));
|
|
1685
|
+
let exp = now + 300;
|
|
1686
|
+
for (const material of [credential, ...Object.values(credentials).filter((value) => isJsonObject(value) && value.ver === 'aun-cap/1'), ...[params, params.previous_owner, params.new_owner].filter(isJsonObject).flatMap((value) => ['membership_binding', 'credential_template', 'registration_authorization'].map((field) => value[field]))]) {
|
|
1687
|
+
if (isJsonObject(material)) {
|
|
1688
|
+
if (!Number.isInteger(material.exp) || Number(material.exp) <= now)
|
|
1689
|
+
throw new ValidationError('Org recovery authorization material has expired');
|
|
1690
|
+
exp = Math.min(exp, Number(material.exp));
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
const refreshed = { ...params, request_binding: buildRequestBinding(method, target, params, cid, String(params.nonce), String(params.request_id), now, exp) };
|
|
1694
|
+
delete refreshed.client_signature;
|
|
1695
|
+
if (isJsonObject(credentials.representation_proof)) {
|
|
1696
|
+
if (!this._currentAid || !isJsonObject(credential))
|
|
1697
|
+
throw new ValidationError('Org recovery representation signer is unavailable');
|
|
1698
|
+
refreshed.credentials = { ...credentials, representation_proof: createRepresentationProof(credential, refreshed, refreshed.request_binding, this._currentAid, refreshed.proof_chain) };
|
|
1699
|
+
}
|
|
1700
|
+
return refreshed;
|
|
1701
|
+
}
|
|
1462
1702
|
async createOrg(params = {}, options = {}) {
|
|
1463
1703
|
const store = options.aidStore;
|
|
1464
1704
|
const owner = this._currentAid;
|
|
1465
1705
|
const orgAid = String(params.org_id ?? '').trim();
|
|
1466
1706
|
if (!store || !owner?.isPrivateKeyValid() || !orgAid)
|
|
1467
1707
|
throw new ValidationError('createOrg requires aidStore, org_id and current owner identity');
|
|
1708
|
+
const tokens = store._tokenStore;
|
|
1709
|
+
const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
|
|
1710
|
+
let checkpoint = (typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(orgAid) : tokens.loadMetadata(orgAid))?.org_identity_creation;
|
|
1711
|
+
if (checkpoint != null && !isJsonObject(checkpoint))
|
|
1712
|
+
throw ownershipFailure('persistence_unavailable', 'Org creation checkpoint is invalid');
|
|
1713
|
+
if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
|
|
1714
|
+
throw ownershipFailure('conflict', 'pending Org creation context changed');
|
|
1468
1715
|
this._aidStore = store;
|
|
1469
1716
|
let identity = store.load(orgAid);
|
|
1470
1717
|
if (!identity.ok) {
|
|
@@ -1476,18 +1723,13 @@ export class AUNClient {
|
|
|
1476
1723
|
if (!identity.ok)
|
|
1477
1724
|
throw ownershipFailure('unavailable', identity.error.message, true);
|
|
1478
1725
|
const target = options.serviceAid ?? `org.${orgAid.slice(orgAid.indexOf('.') + 1)}`;
|
|
1479
|
-
const tokens = store._tokenStore;
|
|
1480
|
-
const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
|
|
1481
|
-
let checkpoint = tokens.loadMetadata(orgAid)?.org_identity_creation;
|
|
1482
|
-
if (checkpoint && (checkpoint.owner_aid !== owner.aid || checkpoint.input_hash !== inputHash))
|
|
1483
|
-
throw ownershipFailure('conflict', 'pending Org creation context changed');
|
|
1484
1726
|
if (!checkpoint) {
|
|
1485
1727
|
const prepared = this.org.prepareRequest('org.lifecycle.create', { ...params, owner_aid: owner.aid }, target, owner, { orgSigner: identity.data.aid });
|
|
1486
1728
|
checkpoint = { owner_aid: owner.aid, input_hash: inputHash, source_params: params, prepared, phase: 'create' };
|
|
1487
1729
|
tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
|
|
1488
1730
|
}
|
|
1489
1731
|
if (checkpoint.phase === 'create') {
|
|
1490
|
-
const result = await this.call('org.lifecycle.create', checkpoint.prepared);
|
|
1732
|
+
const result = await this.call('org.lifecycle.create', await this._resumeOrgRequest('org.lifecycle.create', checkpoint.prepared, target));
|
|
1491
1733
|
checkpoint = { ...checkpoint, result, phase: 'upload' };
|
|
1492
1734
|
tokens.saveMetadata(orgAid, { org_identity_creation: checkpoint });
|
|
1493
1735
|
}
|
|
@@ -1501,9 +1743,11 @@ export class AUNClient {
|
|
|
1501
1743
|
if (typeof keyStore?.listIdentities !== 'function' || typeof tokens?.loadMetadata !== 'function')
|
|
1502
1744
|
return;
|
|
1503
1745
|
for (const aid of keyStore.listIdentities()) {
|
|
1504
|
-
const metadata = tokens.loadMetadata(aid);
|
|
1746
|
+
const metadata = typeof tokens._loadMetadataStrict === 'function' ? tokens._loadMetadataStrict(aid) : tokens.loadMetadata(aid);
|
|
1505
1747
|
const creation = metadata?.org_identity_creation;
|
|
1506
1748
|
const transfer = metadata?.org_identity_transfer;
|
|
1749
|
+
if (creation != null && !isJsonObject(creation) || transfer != null && !isJsonObject(transfer))
|
|
1750
|
+
throw ownershipFailure('persistence_unavailable', 'Org identity checkpoint is invalid');
|
|
1507
1751
|
try {
|
|
1508
1752
|
if (creation?.owner_aid === this._aid) {
|
|
1509
1753
|
await this.createOrg(creation.source_params, { aidStore: store });
|
|
@@ -1513,21 +1757,46 @@ export class AUNClient {
|
|
|
1513
1757
|
await this.completeOrgTransfer(transfer.source_params, { aidStore: store });
|
|
1514
1758
|
continue;
|
|
1515
1759
|
}
|
|
1516
|
-
const orgManager = store._agentMdManager;
|
|
1517
|
-
if (String((orgManager?._loadRecord?.(aid, true) ?? orgManager?.loadRecord?.(aid))?.local_draft_content ?? ''))
|
|
1518
|
-
continue;
|
|
1519
|
-
const pending = store._agentMdManager.pendingUpload(aid);
|
|
1520
|
-
if (!pending)
|
|
1521
|
-
continue;
|
|
1522
|
-
const fields = validateAgentMdDocument(pending.content, { expectedAid: aid }).frontmatter;
|
|
1523
|
-
if (fields.type === 'org' && fields.owner === this._aid)
|
|
1524
|
-
await this._resumeGroupAgentMdUpload(store, aid);
|
|
1525
1760
|
}
|
|
1526
1761
|
catch (error) {
|
|
1527
1762
|
this._clientLog.warn(`post-connect Org identity recovery failed: aid=${aid} err=${formatCaughtError(error)}`);
|
|
1528
1763
|
}
|
|
1529
1764
|
}
|
|
1530
1765
|
}
|
|
1766
|
+
async _verifyOrgTransferMaterials(params, ownerAid) {
|
|
1767
|
+
const orgAid = String(params.org_id);
|
|
1768
|
+
const previous = params.previous_owner;
|
|
1769
|
+
const next = params.new_owner;
|
|
1770
|
+
const binding = previous.membership_binding;
|
|
1771
|
+
const nextBinding = next.membership_binding;
|
|
1772
|
+
const credential = previous.credential_template;
|
|
1773
|
+
const ext = isJsonObject(credential.ext) ? credential.ext : {};
|
|
1774
|
+
const capability = Array.isArray(credential.cap) && credential.cap.length === 1 && isJsonObject(credential.cap[0]) ? credential.cap[0] : {};
|
|
1775
|
+
const now = Math.trunc(Date.now() / 1000);
|
|
1776
|
+
if (binding.org_id !== orgAid || nextBinding.org_id !== orgAid || binding.member_aid === ownerAid || nextBinding.member_aid !== ownerAid
|
|
1777
|
+
|| binding.role !== 'member' || nextBinding.role !== 'owner' || digest(previous.terms) !== binding.terms_hash || digest(next.terms) !== nextBinding.terms_hash
|
|
1778
|
+
|| !Number.isSafeInteger(nextBinding.iat) || !Number.isSafeInteger(nextBinding.exp) || Number(nextBinding.iat) > now || Number(nextBinding.exp) <= now
|
|
1779
|
+
|| credential.iss !== orgAid || credential.sub !== binding.member_aid || credential.parent != null || credential.delegate !== false
|
|
1780
|
+
|| ext.credential_type !== 'member' || ext.bound_to_membership !== true || ext.org_id !== orgAid || ext.membership_id !== binding.membership_id
|
|
1781
|
+
|| ext.binding_version !== binding.binding_version || ext.membership_terms_hash !== binding.terms_hash
|
|
1782
|
+
|| capability.ns !== 'org' || capability.action !== 'role.member' || capability.resource !== orgAid)
|
|
1783
|
+
throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
|
|
1784
|
+
validateMembershipBinding(binding);
|
|
1785
|
+
const certificates = new Map();
|
|
1786
|
+
for (const [aid, signature] of [[binding.member_aid, binding.member_signature], [orgAid, binding.org_signature], [orgAid, credential.signature]]) {
|
|
1787
|
+
if (!isJsonObject(signature))
|
|
1788
|
+
throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
|
|
1789
|
+
const fingerprint = String(signature.cert_fingerprint);
|
|
1790
|
+
const certificate = await this._auth.resolveOwnershipCertificate(await this._resolveOwnershipCertificateGateway(String(aid)), String(aid), fingerprint);
|
|
1791
|
+
certificates.set(fingerprint, certificate.cert_pem);
|
|
1792
|
+
}
|
|
1793
|
+
const resolve = (fingerprint) => certificates.get(fingerprint);
|
|
1794
|
+
const checkedAt = Math.trunc(Date.now() / 1000);
|
|
1795
|
+
verifyMembershipBinding(binding, resolve, checkedAt, orgAid);
|
|
1796
|
+
verifyCredentialSignature(credential, resolve, checkedAt, orgAid);
|
|
1797
|
+
if (Number(nextBinding.exp) <= checkedAt)
|
|
1798
|
+
throw ownershipFailure('invalid', 'Org transfer membership materials do not match');
|
|
1799
|
+
}
|
|
1531
1800
|
async completeOrgTransfer(params = {}, options = {}) {
|
|
1532
1801
|
const store = options.aidStore;
|
|
1533
1802
|
const owner = this._currentAid;
|
|
@@ -1545,9 +1814,13 @@ export class AUNClient {
|
|
|
1545
1814
|
const tokenStore = store._tokenStore;
|
|
1546
1815
|
const pendingKey = `org-transfer:${orgAid}:${transferId}`;
|
|
1547
1816
|
const inputHash = crypto.createHash('sha256').update(canonicalJson(params)).digest('hex');
|
|
1548
|
-
let pending = tokenStore.loadMetadata(orgAid)?.org_identity_transfer;
|
|
1817
|
+
let pending = (typeof tokenStore._loadMetadataStrict === 'function' ? tokenStore._loadMetadataStrict(orgAid) : tokenStore.loadMetadata(orgAid))?.org_identity_transfer;
|
|
1818
|
+
if (pending != null && !isJsonObject(pending))
|
|
1819
|
+
throw ownershipFailure('persistence_unavailable', 'Org transfer checkpoint is invalid');
|
|
1549
1820
|
if (pending && (pending.transfer_id !== transferId || pending.owner_aid !== owner.aid || pending.input_hash !== inputHash))
|
|
1550
1821
|
throw ownershipFailure('conflict', 'pending Org transfer context changed');
|
|
1822
|
+
if (!pending)
|
|
1823
|
+
await this._verifyOrgTransferMaterials(params, owner.aid);
|
|
1551
1824
|
let keyPair = localStore.loadPendingGroupBind(pendingKey);
|
|
1552
1825
|
if (!keyPair) {
|
|
1553
1826
|
if (pending)
|
|
@@ -1568,7 +1841,7 @@ export class AUNClient {
|
|
|
1568
1841
|
tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
|
|
1569
1842
|
}
|
|
1570
1843
|
if (pending.phase === 'rekey') {
|
|
1571
|
-
const result = await this.call('org.lifecycle.transferAccept', pending.rekey_params);
|
|
1844
|
+
const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.rekey_params, target));
|
|
1572
1845
|
pending = { ...pending, phase: 'finalize', rekey_result: result };
|
|
1573
1846
|
tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
|
|
1574
1847
|
}
|
|
@@ -1604,7 +1877,7 @@ export class AUNClient {
|
|
|
1604
1877
|
pending = { ...pending, finalize_params: this.org.prepareRequest('org.lifecycle.transferAccept', final, target, owner, { orgSigner: orgIdentity }) };
|
|
1605
1878
|
tokenStore.saveMetadata(orgAid, { org_identity_transfer: pending });
|
|
1606
1879
|
}
|
|
1607
|
-
const result = await this.call('org.lifecycle.transferAccept', pending.finalize_params);
|
|
1880
|
+
const result = await this.call('org.lifecycle.transferAccept', await this._resumeOrgRequest('org.lifecycle.transferAccept', pending.finalize_params, target));
|
|
1608
1881
|
await this._uploadOrgAgentMd(store, orgAid, pending.source_params);
|
|
1609
1882
|
tokenStore.saveMetadata(orgAid, { org_identity_transfer: null });
|
|
1610
1883
|
localStore.clearPendingGroupBind(pendingKey);
|
|
@@ -2663,7 +2936,10 @@ export class AUNClient {
|
|
|
2663
2936
|
if (event === 'message.received') {
|
|
2664
2937
|
return this._handleOwnershipMessage(payload).then((handled) => {
|
|
2665
2938
|
if (!handled)
|
|
2666
|
-
return this.
|
|
2939
|
+
return this._handleCredentialRenewal(payload).then((renewed) => {
|
|
2940
|
+
if (!renewed)
|
|
2941
|
+
return this._delivery.publishAppEvent(event, payload, source, deliveryContext);
|
|
2942
|
+
});
|
|
2667
2943
|
return undefined;
|
|
2668
2944
|
});
|
|
2669
2945
|
}
|
|
@@ -3094,22 +3370,36 @@ export class AUNClient {
|
|
|
3094
3370
|
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (cache_hit)`);
|
|
3095
3371
|
return cached.certPem;
|
|
3096
3372
|
}
|
|
3097
|
-
const
|
|
3098
|
-
|
|
3099
|
-
|
|
3373
|
+
const inFlightKey = `${cacheKey}:timeout:${timeoutMs}`;
|
|
3374
|
+
const pending = this._certFetchInFlight.get(inFlightKey);
|
|
3375
|
+
if (pending)
|
|
3376
|
+
return await pending;
|
|
3377
|
+
const request = (async () => {
|
|
3378
|
+
const gatewayUrl = this._gatewayUrl;
|
|
3379
|
+
if (!gatewayUrl) {
|
|
3380
|
+
throw new ValidationError('gateway url unavailable for e2ee cert fetch');
|
|
3381
|
+
}
|
|
3382
|
+
const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
|
|
3383
|
+
const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
|
|
3384
|
+
const validated = await this._validateAndCachePeerCert({
|
|
3385
|
+
aid,
|
|
3386
|
+
certPem,
|
|
3387
|
+
certFingerprint,
|
|
3388
|
+
source: 'fetch',
|
|
3389
|
+
signedAt,
|
|
3390
|
+
agentMdRead,
|
|
3391
|
+
});
|
|
3392
|
+
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
|
|
3393
|
+
return validated;
|
|
3394
|
+
})();
|
|
3395
|
+
this._certFetchInFlight.set(inFlightKey, request);
|
|
3396
|
+
try {
|
|
3397
|
+
return await request;
|
|
3398
|
+
}
|
|
3399
|
+
finally {
|
|
3400
|
+
if (this._certFetchInFlight.get(inFlightKey) === request)
|
|
3401
|
+
this._certFetchInFlight.delete(inFlightKey);
|
|
3100
3402
|
}
|
|
3101
|
-
const peerGatewayUrl = AUNClient._resolvePeerGatewayUrl(gatewayUrl, aid);
|
|
3102
|
-
const certPem = await _httpGetText(AUNClient._buildCertUrl(peerGatewayUrl, aid, certFingerprint), this._configModel.verifySsl, timeoutMs);
|
|
3103
|
-
const validated = await this._validateAndCachePeerCert({
|
|
3104
|
-
aid,
|
|
3105
|
-
certPem,
|
|
3106
|
-
certFingerprint,
|
|
3107
|
-
source: 'fetch',
|
|
3108
|
-
signedAt,
|
|
3109
|
-
agentMdRead,
|
|
3110
|
-
});
|
|
3111
|
-
this._clientLog.debug(`_fetchPeerCert exit: elapsed=${Date.now() - tStart}ms aid=${aid} (fetched)`);
|
|
3112
|
-
return validated;
|
|
3113
3403
|
}
|
|
3114
3404
|
catch (err) {
|
|
3115
3405
|
this._clientLog.debug(`_fetchPeerCert exit (error): elapsed=${Date.now() - tStart}ms aid=${aid} err=${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -3685,7 +3975,7 @@ export class AUNClient {
|
|
|
3685
3975
|
}
|
|
3686
3976
|
/** 后台 Promise 统一兜底,避免事件回调里的异步异常变成未处理拒绝。 */
|
|
3687
3977
|
_safeAsync(promise) {
|
|
3688
|
-
promise.catch((exc) => {
|
|
3978
|
+
return promise.then(() => undefined).catch((exc) => {
|
|
3689
3979
|
this._clientLog.warn(`background task exception: ${formatCaughtError(exc)}`);
|
|
3690
3980
|
});
|
|
3691
3981
|
}
|
|
@@ -3880,7 +4170,7 @@ export class AUNClient {
|
|
|
3880
4170
|
return await this._v2E2EE.ackGroupV2(groupId, upToSeq, groupAid);
|
|
3881
4171
|
}
|
|
3882
4172
|
/** 解密单条 V2 pull 消息。缺 sender IK 时先入 pending,后台补齐后重试。 */
|
|
3883
|
-
async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull') {
|
|
4173
|
+
async _decryptV2Message(msg, allowPending = true, emitUndecryptable = true, rotateKeys = true, observeAgentMd = true, deferStatus, deferKeyFetch = false, source = 'pull', deferredEffects) {
|
|
3884
4174
|
const session = this._v2Session;
|
|
3885
4175
|
if (!session)
|
|
3886
4176
|
return null;
|
|
@@ -3896,8 +4186,12 @@ export class AUNClient {
|
|
|
3896
4186
|
return null;
|
|
3897
4187
|
}
|
|
3898
4188
|
const e2eeMeta = this._v2E2eeMeta(envelope);
|
|
3899
|
-
if (observeAgentMd)
|
|
3900
|
-
|
|
4189
|
+
if (observeAgentMd) {
|
|
4190
|
+
if (deferredEffects)
|
|
4191
|
+
deferredEffects.push(() => this._agentMdManager.observeEnvelope(envelope));
|
|
4192
|
+
else
|
|
4193
|
+
this._agentMdManager.observeEnvelope(envelope);
|
|
4194
|
+
}
|
|
3901
4195
|
let spkId = '';
|
|
3902
4196
|
let recipientKeySource = '';
|
|
3903
4197
|
if (isJsonObject(envelope.recipient)) {
|
|
@@ -3976,8 +4270,12 @@ export class AUNClient {
|
|
|
3976
4270
|
};
|
|
3977
4271
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
3978
4272
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|
|
3979
|
-
if (emitUndecryptable)
|
|
3980
|
-
|
|
4273
|
+
if (emitUndecryptable) {
|
|
4274
|
+
if (deferredEffects)
|
|
4275
|
+
deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
|
|
4276
|
+
else
|
|
4277
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
4278
|
+
}
|
|
3981
4279
|
return null;
|
|
3982
4280
|
}
|
|
3983
4281
|
this._clientLog.debug(`V2 decrypt key lookup ok: seq=${String(msg.seq ?? '')}, group=${groupIdForKeys || '<p2p>'}, ik_len=${ikPriv.byteLength}, spk_len=${spkPriv?.byteLength ?? 0}`);
|
|
@@ -4015,8 +4313,12 @@ export class AUNClient {
|
|
|
4015
4313
|
};
|
|
4016
4314
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
4017
4315
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|
|
4018
|
-
if (emitUndecryptable)
|
|
4019
|
-
|
|
4316
|
+
if (emitUndecryptable) {
|
|
4317
|
+
if (deferredEffects)
|
|
4318
|
+
deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
|
|
4319
|
+
else
|
|
4320
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
4321
|
+
}
|
|
4020
4322
|
return null;
|
|
4021
4323
|
}
|
|
4022
4324
|
let plaintext;
|
|
@@ -4044,16 +4346,24 @@ export class AUNClient {
|
|
|
4044
4346
|
};
|
|
4045
4347
|
this._attachV2EnvelopeMetadata(event, e2eeMeta);
|
|
4046
4348
|
this._logMessageDebug('decrypt-fail', 'v2.decrypt', undecryptableEvent, event);
|
|
4047
|
-
if (emitUndecryptable)
|
|
4048
|
-
|
|
4349
|
+
if (emitUndecryptable) {
|
|
4350
|
+
if (deferredEffects)
|
|
4351
|
+
deferredEffects.push(() => { this._dispatcher.enqueue(undecryptableEvent, event); });
|
|
4352
|
+
else
|
|
4353
|
+
this._dispatcher.enqueue(undecryptableEvent, event);
|
|
4354
|
+
}
|
|
4049
4355
|
return null;
|
|
4050
4356
|
}
|
|
4051
4357
|
if (plaintext === null) {
|
|
4052
4358
|
this._clientLog.debug(`V2 decrypt returned null plaintext: seq=${String(msg.seq ?? '')}, group=${groupIdForKeys || '<p2p>'}`);
|
|
4053
4359
|
return null;
|
|
4054
4360
|
}
|
|
4055
|
-
if (rotateKeys)
|
|
4056
|
-
|
|
4361
|
+
if (rotateKeys) {
|
|
4362
|
+
if (deferredEffects)
|
|
4363
|
+
deferredEffects.push(() => this._scheduleV2KeyRotationForMessage(msg, envelope));
|
|
4364
|
+
else
|
|
4365
|
+
this._scheduleV2KeyRotationForMessage(msg, envelope);
|
|
4366
|
+
}
|
|
4057
4367
|
const e2ee = this._v2E2eeMeta(envelope);
|
|
4058
4368
|
let result = {
|
|
4059
4369
|
message_id: String(msg.message_id ?? ''),
|
|
@@ -4149,6 +4459,8 @@ export class AUNClient {
|
|
|
4149
4459
|
if (protectedHeaders && Object.keys(protectedHeaders).length > 0) {
|
|
4150
4460
|
meta.protected_headers = protectedHeaders;
|
|
4151
4461
|
}
|
|
4462
|
+
if (typeof envelope.need_render === 'boolean')
|
|
4463
|
+
meta.need_render = envelope.need_render;
|
|
4152
4464
|
const payloadType = String(envelope.payload_type ?? protectedHeaders?.payload_type ?? '').trim();
|
|
4153
4465
|
if (payloadType) {
|
|
4154
4466
|
meta.payload_type = payloadType;
|
|
@@ -4164,6 +4476,8 @@ export class AUNClient {
|
|
|
4164
4476
|
return meta;
|
|
4165
4477
|
}
|
|
4166
4478
|
_attachV2EnvelopeMetadata(message, meta) {
|
|
4479
|
+
if (typeof meta.need_render === 'boolean')
|
|
4480
|
+
message.need_render = meta.need_render;
|
|
4167
4481
|
const payloadType = typeof meta.payload_type === 'string' ? meta.payload_type.trim() : '';
|
|
4168
4482
|
if (payloadType)
|
|
4169
4483
|
message.payload_type = payloadType;
|
|
@@ -4521,6 +4835,7 @@ export class AUNClient {
|
|
|
4521
4835
|
// ── 内部:后台任务 ────────────────────────────────────────
|
|
4522
4836
|
/** 启动所有后台任务 */
|
|
4523
4837
|
_startBackgroundTasks() {
|
|
4838
|
+
this._startCredentialRenewal();
|
|
4524
4839
|
// WebSocket Ping/Pong 由 Gateway 与传输层处理;SDK 不发送 RPC 心跳。
|
|
4525
4840
|
// 短连接生命周期短,不需要 token 刷新。
|
|
4526
4841
|
if (this._sessionOptions.connection_kind !== 'short') {
|
|
@@ -4530,6 +4845,7 @@ export class AUNClient {
|
|
|
4530
4845
|
}
|
|
4531
4846
|
/** 停止所有后台任务 */
|
|
4532
4847
|
_stopBackgroundTasks() {
|
|
4848
|
+
this._stopCredentialRenewal();
|
|
4533
4849
|
if (this._heartbeatTimer !== null) {
|
|
4534
4850
|
clearInterval(this._heartbeatTimer);
|
|
4535
4851
|
this._heartbeatTimer = null;
|