keri-ts 2.2.4 → 2.2.6
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/esm/keri/npm/src/_dnt.polyfills.js +14 -14
- package/esm/keri/npm/src/acdc/dhking.js +29 -2
- package/esm/keri/npm/src/acdc/observing.js +10 -3
- package/esm/keri/npm/src/acdc/query-resend.js +74 -0
- package/esm/keri/npm/src/acdc/regengine.js +39 -9
- package/esm/keri/npm/src/acdc/regeventing.js +5 -3
- package/esm/keri/npm/src/acdc/registering.js +19 -0
- package/esm/keri/npm/src/acdc/salting.js +4 -0
- package/esm/keri/npm/src/acdc/unblinding.js +38 -3
- package/esm/keri/npm/src/app/bootstrap/authenticating.js +32 -10
- package/esm/keri/npm/src/app/bootstrap/oobiery.js +363 -30
- package/esm/keri/npm/src/app/credentials/credential-importing.js +33 -4
- package/esm/keri/npm/src/app/credentials/root-replicator.js +27 -15
- package/esm/keri/npm/src/app/credentials/schema-resolving.js +5 -1
- package/esm/keri/npm/src/app/credentials/tel-publishing.js +16 -7
- package/esm/keri/npm/src/app/delivery/forwarding.js +205 -44
- package/esm/keri/npm/src/app/delivery/relaying.js +232 -0
- package/esm/keri/npm/src/app/delivery/respondant.js +8 -5
- package/esm/keri/npm/src/app/exchanges/exchange-messaging.js +1 -1
- package/esm/keri/npm/src/app/exchanges/exchanging.js +65 -9
- package/esm/keri/npm/src/app/identity/delegating.js +66 -9
- package/esm/keri/npm/src/app/identity/habbing.js +190 -71
- package/esm/keri/npm/src/app/identity/keeping.js +111 -39
- package/esm/keri/npm/src/app/identity/retire-identifier.js +98 -0
- package/esm/keri/npm/src/app/ipex/disclosure-replaying.js +116 -0
- package/esm/keri/npm/src/app/ipex/group-ipex.js +122 -25
- package/esm/keri/npm/src/app/ipex/group-issuance.js +137 -37
- package/esm/keri/npm/src/app/ipex/ipex-discloser.js +94 -31
- package/esm/keri/npm/src/app/ipex/ipex-grouping.js +53 -3
- package/esm/keri/npm/src/app/ipex/ipex-v2.js +7 -2
- package/esm/keri/npm/src/app/ipex/issuance.js +147 -44
- package/esm/keri/npm/src/app/ipex/pending-presentations.js +4 -2
- package/esm/keri/npm/src/app/ipex/presenting.js +3 -18
- package/esm/keri/npm/src/app/ipex/receive-issuance.js +2 -1
- package/esm/keri/npm/src/app/ipex/unblinding-workflow.js +39 -11
- package/esm/keri/npm/src/app/mailbox/agent-inbox.js +292 -0
- package/esm/keri/npm/src/app/mailbox/forward-handler.js +298 -52
- package/esm/keri/npm/src/app/mailbox/kel-publisher.js +26 -31
- package/esm/keri/npm/src/app/mailbox/mailbox-poller.js +70 -62
- package/esm/keri/npm/src/app/mailbox/mailboxing.js +20 -4
- package/esm/keri/npm/src/app/multisig/counseling.js +71 -27
- package/esm/keri/npm/src/app/multisig/group-members.js +41 -5
- package/esm/keri/npm/src/app/multisig/grouping.js +50 -2
- package/esm/keri/npm/src/app/multisig/multisig-workflows.js +3 -18
- package/esm/keri/npm/src/app/multisig/proposing.js +1 -16
- package/esm/keri/npm/src/app/multisig/rejoin-group.js +99 -0
- package/esm/keri/npm/src/app/multisig/replace-member.js +29 -8
- package/esm/keri/npm/src/app/protocol/parsering.js +45 -17
- package/esm/keri/npm/src/app/protocol/protocol-processing.js +47 -21
- package/esm/keri/npm/src/app/queries/query-replies.js +0 -22
- package/esm/keri/npm/src/app/queries/relay-refresh.js +81 -0
- package/esm/keri/npm/src/app/queries/witness-inquisitor.js +180 -34
- package/esm/keri/npm/src/app/runtime/agent-runtime.js +42 -73
- package/esm/keri/npm/src/app/runtime/cue-runtime.js +45 -5
- package/esm/keri/npm/src/app/runtime/ipex-auxiliaries.js +8 -1
- package/esm/keri/npm/src/app/runtime/operation-owners.js +26 -56
- package/esm/keri/npm/src/app/runtime/workflow-contract.js +7 -4
- package/esm/keri/npm/src/app/transport/http-client.js +20 -2
- package/esm/keri/npm/src/app/transport/message-source.js +2 -1
- package/esm/keri/npm/src/app/transport/messengering.js +39 -8
- package/esm/keri/npm/src/app/witness/ingress.js +7 -0
- package/esm/keri/npm/src/app/witness/receipt-client.js +43 -16
- package/esm/keri/npm/src/app/witness/receipting.js +62 -20
- package/esm/keri/npm/src/cli/messaging/multisig.js +4 -3
- package/esm/keri/npm/src/core/errors.js +73 -0
- package/esm/keri/npm/src/core/kel/delegation-ancestry.js +11 -2
- package/esm/keri/npm/src/core/kel/eventing.js +25 -28
- package/esm/keri/npm/src/core/kel/kever.js +15 -9
- package/esm/keri/npm/src/core/mailbox/kram-policy.js +16 -0
- package/esm/keri/npm/src/core/mailbox/kram-pruning.js +25 -6
- package/esm/keri/npm/src/core/mailbox/kram-time.js +34 -6
- package/esm/keri/npm/src/core/mailbox/kraming.js +58 -2
- package/esm/keri/npm/src/core/missing-anchor-query.js +20 -0
- package/esm/keri/npm/src/core/routing.js +18 -25
- package/esm/keri/npm/src/core/scheming.js +12 -5
- package/esm/keri/npm/src/core/wire-fields.js +126 -0
- package/esm/keri/npm/src/db/basing.js +4 -13
- package/esm/keri/npm/src/db/core/indexeddb-persistence.js +3 -2
- package/esm/keri/npm/src/db/core/webdber.js +85 -27
- package/esm/keri/npm/src/db/mailbox/mailboxing.js +13 -0
- package/esm/keri/npm/src/db/subing.js +4 -0
- package/esm/keri/npm/src/runtime/index.js +11 -5
- package/esm/keri/npm/src/time/mod.js +5 -0
- package/esm/keri/npm/src/vdr/credential-tsn-record.js +2 -7
- package/esm/keri/npm/src/vdr/eventing.js +71 -28
- package/esm/keri/npm/src/vdr/registry-tsn-record.js +3 -13
- package/esm/keri/npm/src/version.js +2 -2
- package/package.json +1 -1
- package/types/_dnt.polyfills.d.ts +9 -9
- package/types/_dnt.polyfills.d.ts.map +1 -1
- package/types/acdc/dhking.d.ts +8 -0
- package/types/acdc/dhking.d.ts.map +1 -1
- package/types/acdc/observing.d.ts +5 -1
- package/types/acdc/observing.d.ts.map +1 -1
- package/types/acdc/query-resend.d.ts +24 -0
- package/types/acdc/query-resend.d.ts.map +1 -0
- package/types/acdc/regengine.d.ts +19 -1
- package/types/acdc/regengine.d.ts.map +1 -1
- package/types/acdc/regeventing.d.ts.map +1 -1
- package/types/acdc/registering.d.ts +6 -0
- package/types/acdc/registering.d.ts.map +1 -1
- package/types/acdc/salting.d.ts.map +1 -1
- package/types/acdc/unblinding.d.ts +13 -1
- package/types/acdc/unblinding.d.ts.map +1 -1
- package/types/app/bootstrap/authenticating.d.ts +12 -2
- package/types/app/bootstrap/authenticating.d.ts.map +1 -1
- package/types/app/bootstrap/oobiery.d.ts +144 -7
- package/types/app/bootstrap/oobiery.d.ts.map +1 -1
- package/types/app/credentials/credential-importing.d.ts +8 -0
- package/types/app/credentials/credential-importing.d.ts.map +1 -1
- package/types/app/credentials/root-replicator.d.ts +19 -8
- package/types/app/credentials/root-replicator.d.ts.map +1 -1
- package/types/app/credentials/schema-resolving.d.ts.map +1 -1
- package/types/app/credentials/tel-publishing.d.ts +4 -2
- package/types/app/credentials/tel-publishing.d.ts.map +1 -1
- package/types/app/delivery/forwarding.d.ts +90 -5
- package/types/app/delivery/forwarding.d.ts.map +1 -1
- package/types/app/delivery/relaying.d.ts +144 -0
- package/types/app/delivery/relaying.d.ts.map +1 -0
- package/types/app/delivery/respondant.d.ts +9 -2
- package/types/app/delivery/respondant.d.ts.map +1 -1
- package/types/app/exchanges/exchanging.d.ts +36 -0
- package/types/app/exchanges/exchanging.d.ts.map +1 -1
- package/types/app/identity/delegating.d.ts +6 -0
- package/types/app/identity/delegating.d.ts.map +1 -1
- package/types/app/identity/habbing.d.ts +106 -19
- package/types/app/identity/habbing.d.ts.map +1 -1
- package/types/app/identity/keeping.d.ts +54 -19
- package/types/app/identity/keeping.d.ts.map +1 -1
- package/types/app/identity/retire-identifier.d.ts +24 -0
- package/types/app/identity/retire-identifier.d.ts.map +1 -0
- package/types/app/ipex/disclosure-replaying.d.ts +61 -0
- package/types/app/ipex/disclosure-replaying.d.ts.map +1 -0
- package/types/app/ipex/group-ipex.d.ts +60 -14
- package/types/app/ipex/group-ipex.d.ts.map +1 -1
- package/types/app/ipex/group-issuance.d.ts +54 -8
- package/types/app/ipex/group-issuance.d.ts.map +1 -1
- package/types/app/ipex/ipex-discloser.d.ts +39 -2
- package/types/app/ipex/ipex-discloser.d.ts.map +1 -1
- package/types/app/ipex/ipex-grouping.d.ts +23 -2
- package/types/app/ipex/ipex-grouping.d.ts.map +1 -1
- package/types/app/ipex/ipex-v2.d.ts +5 -2
- package/types/app/ipex/ipex-v2.d.ts.map +1 -1
- package/types/app/ipex/issuance.d.ts +46 -16
- package/types/app/ipex/issuance.d.ts.map +1 -1
- package/types/app/ipex/pending-presentations.d.ts +4 -2
- package/types/app/ipex/pending-presentations.d.ts.map +1 -1
- package/types/app/ipex/presenting.d.ts.map +1 -1
- package/types/app/ipex/receive-issuance.d.ts +2 -1
- package/types/app/ipex/receive-issuance.d.ts.map +1 -1
- package/types/app/ipex/unblinding-workflow.d.ts +13 -2
- package/types/app/ipex/unblinding-workflow.d.ts.map +1 -1
- package/types/app/mailbox/agent-inbox.d.ts +126 -0
- package/types/app/mailbox/agent-inbox.d.ts.map +1 -0
- package/types/app/mailbox/forward-handler.d.ts +114 -14
- package/types/app/mailbox/forward-handler.d.ts.map +1 -1
- package/types/app/mailbox/kel-publisher.d.ts +12 -22
- package/types/app/mailbox/kel-publisher.d.ts.map +1 -1
- package/types/app/mailbox/mailbox-poller.d.ts +40 -24
- package/types/app/mailbox/mailbox-poller.d.ts.map +1 -1
- package/types/app/mailbox/mailboxing.d.ts +19 -5
- package/types/app/mailbox/mailboxing.d.ts.map +1 -1
- package/types/app/multisig/counseling.d.ts +32 -11
- package/types/app/multisig/counseling.d.ts.map +1 -1
- package/types/app/multisig/group-members.d.ts +17 -1
- package/types/app/multisig/group-members.d.ts.map +1 -1
- package/types/app/multisig/grouping.d.ts +17 -1
- package/types/app/multisig/grouping.d.ts.map +1 -1
- package/types/app/multisig/multisig-workflows.d.ts +0 -8
- package/types/app/multisig/multisig-workflows.d.ts.map +1 -1
- package/types/app/multisig/proposing.d.ts +4 -19
- package/types/app/multisig/proposing.d.ts.map +1 -1
- package/types/app/multisig/rejoin-group.d.ts +119 -0
- package/types/app/multisig/rejoin-group.d.ts.map +1 -0
- package/types/app/multisig/replace-member.d.ts +15 -1
- package/types/app/multisig/replace-member.d.ts.map +1 -1
- package/types/app/protocol/parsering.d.ts +21 -2
- package/types/app/protocol/parsering.d.ts.map +1 -1
- package/types/app/protocol/protocol-processing.d.ts +25 -5
- package/types/app/protocol/protocol-processing.d.ts.map +1 -1
- package/types/app/queries/query-replies.d.ts +4 -8
- package/types/app/queries/query-replies.d.ts.map +1 -1
- package/types/app/queries/relay-refresh.d.ts +26 -0
- package/types/app/queries/relay-refresh.d.ts.map +1 -0
- package/types/app/queries/witness-inquisitor.d.ts +56 -5
- package/types/app/queries/witness-inquisitor.d.ts.map +1 -1
- package/types/app/runtime/agent-runtime.d.ts +34 -3
- package/types/app/runtime/agent-runtime.d.ts.map +1 -1
- package/types/app/runtime/cue-runtime.d.ts +15 -3
- package/types/app/runtime/cue-runtime.d.ts.map +1 -1
- package/types/app/runtime/ipex-auxiliaries.d.ts.map +1 -1
- package/types/app/runtime/operation-owners.d.ts +16 -26
- package/types/app/runtime/operation-owners.d.ts.map +1 -1
- package/types/app/runtime/workflow-contract.d.ts +21 -6
- package/types/app/runtime/workflow-contract.d.ts.map +1 -1
- package/types/app/transport/http-client.d.ts +9 -0
- package/types/app/transport/http-client.d.ts.map +1 -1
- package/types/app/transport/message-source.d.ts +6 -3
- package/types/app/transport/message-source.d.ts.map +1 -1
- package/types/app/transport/messengering.d.ts +10 -1
- package/types/app/transport/messengering.d.ts.map +1 -1
- package/types/app/witness/ingress.d.ts +1 -0
- package/types/app/witness/ingress.d.ts.map +1 -1
- package/types/app/witness/receipt-client.d.ts +19 -1
- package/types/app/witness/receipt-client.d.ts.map +1 -1
- package/types/app/witness/receipting.d.ts +24 -1
- package/types/app/witness/receipting.d.ts.map +1 -1
- package/types/cli/messaging/multisig.d.ts.map +1 -1
- package/types/core/cues.d.ts +53 -1
- package/types/core/cues.d.ts.map +1 -1
- package/types/core/errors.d.ts +32 -0
- package/types/core/errors.d.ts.map +1 -1
- package/types/core/kel/delegation-ancestry.d.ts.map +1 -1
- package/types/core/kel/eventing.d.ts.map +1 -1
- package/types/core/kel/kever-decisions.d.ts +3 -1
- package/types/core/kel/kever-decisions.d.ts.map +1 -1
- package/types/core/kel/kever.d.ts +7 -1
- package/types/core/kel/kever.d.ts.map +1 -1
- package/types/core/mailbox/kram-policy.d.ts +12 -0
- package/types/core/mailbox/kram-policy.d.ts.map +1 -1
- package/types/core/mailbox/kram-pruning.d.ts +5 -1
- package/types/core/mailbox/kram-pruning.d.ts.map +1 -1
- package/types/core/mailbox/kram-time.d.ts +26 -2
- package/types/core/mailbox/kram-time.d.ts.map +1 -1
- package/types/core/mailbox/kraming.d.ts +14 -1
- package/types/core/mailbox/kraming.d.ts.map +1 -1
- package/types/core/missing-anchor-query.d.ts +34 -0
- package/types/core/missing-anchor-query.d.ts.map +1 -0
- package/types/core/routing.d.ts +3 -7
- package/types/core/routing.d.ts.map +1 -1
- package/types/core/scheming.d.ts.map +1 -1
- package/types/core/wire-fields.d.ts +56 -0
- package/types/core/wire-fields.d.ts.map +1 -0
- package/types/db/basing.d.ts.map +1 -1
- package/types/db/core/indexeddb-persistence.d.ts +3 -2
- package/types/db/core/indexeddb-persistence.d.ts.map +1 -1
- package/types/db/core/webdber.d.ts +32 -6
- package/types/db/core/webdber.d.ts.map +1 -1
- package/types/db/exchange/exchange-records.d.ts +4 -0
- package/types/db/exchange/exchange-records.d.ts.map +1 -1
- package/types/db/mailbox/mailboxing.d.ts +10 -1
- package/types/db/mailbox/mailboxing.d.ts.map +1 -1
- package/types/db/subing.d.ts +2 -0
- package/types/db/subing.d.ts.map +1 -1
- package/types/runtime/index.d.ts +20 -14
- package/types/runtime/index.d.ts.map +1 -1
- package/types/time/mod.d.ts +2 -0
- package/types/time/mod.d.ts.map +1 -1
- package/types/vdr/credential-tsn-record.d.ts.map +1 -1
- package/types/vdr/eventing.d.ts +32 -1
- package/types/vdr/eventing.d.ts.map +1 -1
- package/types/vdr/registry-tsn-record.d.ts.map +1 -1
- package/types/version.d.ts +2 -2
- package/esm/keri/npm/src/app/runtime/signer-conditions.js +0 -51
- package/types/app/runtime/signer-conditions.d.ts +0 -32
- package/types/app/runtime/signer-conditions.d.ts.map +0 -1
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
// https://github.com/tc39/proposal-accessible-object-hasownproperty/blob/main/polyfill.js
|
|
2
|
+
if (!Object.hasOwn) {
|
|
3
|
+
Object.defineProperty(Object, "hasOwn", {
|
|
4
|
+
value: function (object, property) {
|
|
5
|
+
if (object == null) {
|
|
6
|
+
throw new TypeError("Cannot convert undefined or null to object");
|
|
7
|
+
}
|
|
8
|
+
return Object.prototype.hasOwnProperty.call(Object(object), property);
|
|
9
|
+
},
|
|
10
|
+
configurable: true,
|
|
11
|
+
enumerable: false,
|
|
12
|
+
writable: true,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
1
15
|
function findLastIndex(self, callbackfn, that) {
|
|
2
16
|
const boundFunc = that === undefined ? callbackfn : callbackfn.bind(that);
|
|
3
17
|
let index = self.length - 1;
|
|
@@ -34,18 +48,4 @@ if (!Uint8Array.prototype.findLast) {
|
|
|
34
48
|
return findLast(this, callbackfn, that);
|
|
35
49
|
};
|
|
36
50
|
}
|
|
37
|
-
// https://github.com/tc39/proposal-accessible-object-hasownproperty/blob/main/polyfill.js
|
|
38
|
-
if (!Object.hasOwn) {
|
|
39
|
-
Object.defineProperty(Object, "hasOwn", {
|
|
40
|
-
value: function (object, property) {
|
|
41
|
-
if (object == null) {
|
|
42
|
-
throw new TypeError("Cannot convert undefined or null to object");
|
|
43
|
-
}
|
|
44
|
-
return Object.prototype.hasOwnProperty.call(Object(object), property);
|
|
45
|
-
},
|
|
46
|
-
configurable: true,
|
|
47
|
-
enumerable: false,
|
|
48
|
-
writable: true,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
51
|
export {};
|
|
@@ -17,6 +17,7 @@ import { query, reply } from "../core/protocol-eventing.js";
|
|
|
17
17
|
import { messagize } from "../core/protocol-serialization.js";
|
|
18
18
|
import { fetchStoredTsgs, unverifiedReplyDecision } from "../core/routing.js";
|
|
19
19
|
import { timestampMicros } from "../time/mod.js";
|
|
20
|
+
import { QUERY_RESEND_MS } from "./query-resend.js";
|
|
20
21
|
import { validateUpdate, verifyRegistryAnchor } from "./regeventing.js";
|
|
21
22
|
import { publicOpeningAttachment } from "./registering.js";
|
|
22
23
|
const domain = new TextEncoder().encode("KERI/ipex/dhke/X25519-HKDF-SHA256-Salt128/v1");
|
|
@@ -343,6 +344,8 @@ export class Dhke {
|
|
|
343
344
|
peer: null,
|
|
344
345
|
query: null,
|
|
345
346
|
reply: null,
|
|
347
|
+
accepted: null,
|
|
348
|
+
repliedAt: 0,
|
|
346
349
|
proposal: null,
|
|
347
350
|
contribution: null,
|
|
348
351
|
custody: null,
|
|
@@ -493,6 +496,12 @@ export class Dhke {
|
|
|
493
496
|
if (own && (!op.proposal || !bytesEqual(op.proposal, serder.raw))) {
|
|
494
497
|
throw new ValidationError("Own DH query lacks exact local proposal approval");
|
|
495
498
|
}
|
|
499
|
+
// A group re-sends its frozen query unchanged (one member cannot re-date it), and KERIpy resends identical
|
|
500
|
+
// bytes. The wire, attachments included, is compared against the one already authenticated, so a copy is
|
|
501
|
+
// never trusted on its body or SAID alone.
|
|
502
|
+
const wire = auxiliaryQueryWire(envelope);
|
|
503
|
+
if (!own && op.accepted && bytesEqual(op.accepted, wire))
|
|
504
|
+
return this.duplicateQuery(op);
|
|
496
505
|
if (!verifyQueryAttachments(this.hab.db, envelope)) {
|
|
497
506
|
return { kind: "drop", reason: "missingAuthentication" };
|
|
498
507
|
}
|
|
@@ -513,6 +522,7 @@ export class Dhke {
|
|
|
513
522
|
}
|
|
514
523
|
if (this.group && !this.custody(op)) {
|
|
515
524
|
op.peer = { ...peer };
|
|
525
|
+
op.accepted = wire;
|
|
516
526
|
return { kind: "accept" };
|
|
517
527
|
}
|
|
518
528
|
if (!op.reply) {
|
|
@@ -537,10 +547,12 @@ export class Dhke {
|
|
|
537
547
|
}
|
|
538
548
|
if (this.group) {
|
|
539
549
|
this.outgoing.push({ kin: "sign", dest: this.hab.pre, msg: op.contribution.slice() });
|
|
550
|
+
op.accepted = wire;
|
|
540
551
|
return { kind: "accept" };
|
|
541
552
|
}
|
|
542
553
|
}
|
|
543
|
-
this.
|
|
554
|
+
this.emitReply(op);
|
|
555
|
+
op.accepted = wire;
|
|
544
556
|
return { kind: "accept" };
|
|
545
557
|
}
|
|
546
558
|
catch (error) {
|
|
@@ -549,6 +561,21 @@ export class Dhke {
|
|
|
549
561
|
throw error;
|
|
550
562
|
}
|
|
551
563
|
}
|
|
564
|
+
/**
|
|
565
|
+
* A copy of the accepted query: nothing is verified, derived or contributed again, so a member round that
|
|
566
|
+
* already holds its `sign` contribution is not asked for another. If the reply exists it is queued again for a
|
|
567
|
+
* peer that lost it, at most once per {@link QUERY_RESEND_MS}.
|
|
568
|
+
*/
|
|
569
|
+
duplicateQuery(op) {
|
|
570
|
+
if (op.reply && this.now() - op.repliedAt >= QUERY_RESEND_MS)
|
|
571
|
+
this.emitReply(op);
|
|
572
|
+
return { kind: "accept" };
|
|
573
|
+
}
|
|
574
|
+
/** Queue the stored reply for the issuer and note when. */
|
|
575
|
+
emitReply(op) {
|
|
576
|
+
this.outgoing.push({ kin: "reply", dest: op.issuer, msg: op.reply.slice() });
|
|
577
|
+
op.repliedAt = this.now();
|
|
578
|
+
}
|
|
552
579
|
/** Revery decides reply threshold acceptance before any issuer derivation or retention. */
|
|
553
580
|
processReply({ serder, diger, route, cigars = [], tsgs = [] }) {
|
|
554
581
|
try {
|
|
@@ -574,7 +601,7 @@ export class Dhke {
|
|
|
574
601
|
});
|
|
575
602
|
}
|
|
576
603
|
if (this.custody(op))
|
|
577
|
-
this.
|
|
604
|
+
this.emitReply(op);
|
|
578
605
|
}
|
|
579
606
|
else if (this.custody(op) && !op.completed)
|
|
580
607
|
this.retain(op, peer, this.derive(op, peer, "issuer"));
|
|
@@ -11,9 +11,10 @@ import { publicOpeningAttachment } from "./registering.js";
|
|
|
11
11
|
* event commits independently; a later failure preserves the already accepted prefix.
|
|
12
12
|
* Parsed evidence stays borrowed. Composition supplies this owner to v2 protocol dispatch;
|
|
13
13
|
* no local habitat, alias or application disclosure custody is created by public intake.
|
|
14
|
+
* `cues` receives the issuer KEL `logs` query of each event that first waits for its anchoring KEL event.
|
|
14
15
|
*/
|
|
15
16
|
export class RegistryObserver {
|
|
16
|
-
constructor(db, store) {
|
|
17
|
+
constructor(db, store, cues = null) {
|
|
17
18
|
Object.defineProperty(this, "db", {
|
|
18
19
|
enumerable: true,
|
|
19
20
|
configurable: true,
|
|
@@ -26,6 +27,12 @@ export class RegistryObserver {
|
|
|
26
27
|
writable: true,
|
|
27
28
|
value: store
|
|
28
29
|
});
|
|
30
|
+
Object.defineProperty(this, "cues", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
configurable: true,
|
|
33
|
+
writable: true,
|
|
34
|
+
value: cues
|
|
35
|
+
});
|
|
29
36
|
}
|
|
30
37
|
/** Stage parsed events before inception, otherwise delegate ordinary validation and acceptance. */
|
|
31
38
|
processEvent(serder, locators = {}) {
|
|
@@ -62,7 +69,7 @@ export class RegistryObserver {
|
|
|
62
69
|
if (event.ilk === Ilks.rip) {
|
|
63
70
|
if (!event.issuer)
|
|
64
71
|
return { kind: "reject", reason: "issuer" };
|
|
65
|
-
engine ??= new RegistryEventer(this.db, event.issuer, this.store, event.regid);
|
|
72
|
+
engine ??= new RegistryEventer(this.db, event.issuer, this.store, event.regid, this.cues);
|
|
66
73
|
}
|
|
67
74
|
if (engine) {
|
|
68
75
|
const decision = engine.processEvent(serder, locators);
|
|
@@ -124,6 +131,6 @@ export class RegistryObserver {
|
|
|
124
131
|
if (result.kind !== "valid" || result.event.regid !== regid || !result.event.issuer) {
|
|
125
132
|
throw new Error("Corrupt observer registry inception.");
|
|
126
133
|
}
|
|
127
|
-
return new RegistryEventer(this.db, result.event.issuer, this.store, regid);
|
|
134
|
+
return new RegistryEventer(this.db, result.event.issuer, this.store, regid, this.cues);
|
|
128
135
|
}
|
|
129
136
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resend schedule for a group's threshold DH or Unblind query.
|
|
3
|
+
*
|
|
4
|
+
* A single signer re-dates every resend (`Dhke.requery`, `Unblind.requery`), so a resend is a new query the peer's
|
|
5
|
+
* KRAM accepts. A group cannot: re-dating needs the members' signatures, so the frozen query is resent as the same
|
|
6
|
+
* bytes. The peer's KRAM drops identical copies cheaply only while its replay cache row lives, and once the query's
|
|
7
|
+
* `dt` is older than the multi-key acceptance window it refuses every copy as untimely. This schedule therefore
|
|
8
|
+
* backs off and stops resending an unchanged query when the peer can no longer accept it. It never ends the
|
|
9
|
+
* operation: the owner keeps waiting until its own deadline, exactly as before.
|
|
10
|
+
*/
|
|
11
|
+
import { parseBytes } from "cesr-ts";
|
|
12
|
+
import { ValidationError } from "../core/errors.js";
|
|
13
|
+
import { timestampMicros } from "../time/mod.js";
|
|
14
|
+
/** Milliseconds between the first resends of a query, and the shortest gap a receiver re-sends a stored reply. */
|
|
15
|
+
export const QUERY_RESEND_MS = 1000;
|
|
16
|
+
/** The longest gap the backoff reaches, in milliseconds. */
|
|
17
|
+
export const QUERY_RESEND_CAP_MS = 8000;
|
|
18
|
+
/** One group operation's frozen threshold query and when it may next be sent; memory only. */
|
|
19
|
+
export class ThresholdQueryResend {
|
|
20
|
+
constructor() {
|
|
21
|
+
/** The frozen signed query, unchanged on every resend; absent until the members' contributions complete it. */
|
|
22
|
+
Object.defineProperty(this, "query", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: void 0
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(this, "queryAt", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: 0
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(this, "next", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: 0
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, "wait", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: 0
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** Take the frozen query and restart the schedule; its own `dt` is what the acceptance window is measured from. */
|
|
48
|
+
set(query) {
|
|
49
|
+
const body = parseBytes(query)[0]?.body;
|
|
50
|
+
const micros = timestampMicros(body?.sad?.dt);
|
|
51
|
+
if (micros === null)
|
|
52
|
+
throw new ValidationError("Threshold query has no valid date");
|
|
53
|
+
this.query = query;
|
|
54
|
+
this.queryAt = Number(micros / 1000n);
|
|
55
|
+
this.next = 0;
|
|
56
|
+
this.wait = 0;
|
|
57
|
+
}
|
|
58
|
+
/** Whether a query is frozen and the running gap has elapsed at `now` (epoch ms). */
|
|
59
|
+
ready(now) {
|
|
60
|
+
return !!this.query && now >= this.next;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Whether the query is still younger than `windowMs` at `now`; past it a receiver can no longer accept the
|
|
64
|
+
* unchanged query, so it is not sent again. A null window (this store never ran KRAM) never closes.
|
|
65
|
+
*/
|
|
66
|
+
fresh(now, windowMs) {
|
|
67
|
+
return windowMs === null || now - this.queryAt < windowMs;
|
|
68
|
+
}
|
|
69
|
+
/** Record a send at `now`: the next gap doubles from `baseMs` up to {@link QUERY_RESEND_CAP_MS}. */
|
|
70
|
+
sent(now, baseMs) {
|
|
71
|
+
this.wait = this.wait ? Math.max(baseMs, Math.min(this.wait * 2, QUERY_RESEND_CAP_MS)) : baseMs;
|
|
72
|
+
this.next = now + this.wait;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
import { Diger, Ilks, NumberPrimitive, } from "cesr-ts";
|
|
3
3
|
import { DatabaseNotOpenError, DatabaseOperationError } from "../core/errors.js";
|
|
4
4
|
import { consoleLogger } from "../core/logger.js";
|
|
5
|
+
import { missingAnchorCue, missingAnchorQuery } from "../core/missing-anchor-query.js";
|
|
5
6
|
import { findRegistryAnchor, validateRegistrySourceLocators, validateRip, validateUpdate, verifyRegistryAnchor, } from "./regeventing.js";
|
|
6
7
|
/**
|
|
7
8
|
* Borrow the issuer's accepted KEL and registry store; no keys or handles are acquired.
|
|
8
9
|
* The local facade and foreign observer share this accepted-state engine.
|
|
9
10
|
* Publication is atomic; binding this instance happens only after successful commit.
|
|
11
|
+
* An event that first enters the anchor escrow publishes one issuer KEL `logs` query to `cues` after that write.
|
|
10
12
|
*/
|
|
11
13
|
export class RegistryEventer {
|
|
12
|
-
constructor(db, issuer, store, regk = null
|
|
14
|
+
constructor(db, issuer, store, regk = null,
|
|
15
|
+
/** Application cue flow for missing-anchor queries; null for a local issuer, whose own KEL holds every anchor. */
|
|
16
|
+
cues = null) {
|
|
13
17
|
Object.defineProperty(this, "db", {
|
|
14
18
|
enumerable: true,
|
|
15
19
|
configurable: true,
|
|
@@ -34,6 +38,12 @@ export class RegistryEventer {
|
|
|
34
38
|
writable: true,
|
|
35
39
|
value: regk
|
|
36
40
|
});
|
|
41
|
+
Object.defineProperty(this, "cues", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
configurable: true,
|
|
44
|
+
writable: true,
|
|
45
|
+
value: cues
|
|
46
|
+
});
|
|
37
47
|
}
|
|
38
48
|
/** Validated accepted head metadata; local construction may inspect it without duplicating acceptance checks. */
|
|
39
49
|
get acceptedHeadEvent() {
|
|
@@ -234,6 +244,9 @@ export class RegistryEventer {
|
|
|
234
244
|
const decision = this.processOne(serder, locators);
|
|
235
245
|
if (decision.kind === "accept")
|
|
236
246
|
this.processQueued();
|
|
247
|
+
// The escrow row is committed; ask the issuer's KEL holders for what the anchor needs through the ordinary cue path.
|
|
248
|
+
if (decision.kind === "escrow" && decision.query)
|
|
249
|
+
this.cues?.push(missingAnchorCue(decision.query));
|
|
237
250
|
return decision;
|
|
238
251
|
}
|
|
239
252
|
/** Decide and persist one event; callers own subsequent chain progression. */
|
|
@@ -269,10 +282,8 @@ export class RegistryEventer {
|
|
|
269
282
|
return previous;
|
|
270
283
|
this.store.putEvent(serder);
|
|
271
284
|
this.discoverAnchor(event);
|
|
272
|
-
if (!this.store.anchor(event.said))
|
|
273
|
-
this.
|
|
274
|
-
return { kind: "escrow", reason: "anchor", event };
|
|
275
|
-
}
|
|
285
|
+
if (!this.store.anchor(event.said))
|
|
286
|
+
return this.escrowAnchorless(event, locators);
|
|
276
287
|
this.store.baser.maes.rem(event.regid, Number(event.sn), [event.said]);
|
|
277
288
|
if (!prior) {
|
|
278
289
|
this.store.escrowOutOfOrder(event.regid, Number(event.sn), event.said);
|
|
@@ -314,10 +325,8 @@ export class RegistryEventer {
|
|
|
314
325
|
throw new Error("Registry head exists without its accepted inception.");
|
|
315
326
|
this.store.putEvent(serder);
|
|
316
327
|
this.discoverAnchor(event);
|
|
317
|
-
if (!this.store.anchor(event.said))
|
|
318
|
-
this.
|
|
319
|
-
return { kind: "escrow", reason: "anchor", event };
|
|
320
|
-
}
|
|
328
|
+
if (!this.store.anchor(event.said))
|
|
329
|
+
return this.escrowAnchorless(event, locators);
|
|
321
330
|
this.commit(event);
|
|
322
331
|
return { kind: "accept", event };
|
|
323
332
|
});
|
|
@@ -325,6 +334,27 @@ export class RegistryEventer {
|
|
|
325
334
|
this.regk = event.regid;
|
|
326
335
|
return decision;
|
|
327
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Retain an event whose anchoring issuer KEL event is not accepted yet and decide whether to ask for it.
|
|
339
|
+
*
|
|
340
|
+
* The decision names the missing KEL only when this call put the event into the anchor escrow; retries and repeat
|
|
341
|
+
* arrivals find the row present. A source seal naming this issuer supplies the anchor's sequence number, and the
|
|
342
|
+
* highest one bounds the replay; without one the query names only where the recipient's KEL ends.
|
|
343
|
+
*/
|
|
344
|
+
escrowAnchorless(event, locators) {
|
|
345
|
+
const added = this.store.escrowMissingAnchor(event.regid, Number(event.sn), event.said);
|
|
346
|
+
const decision = { kind: "escrow", reason: "anchor", event };
|
|
347
|
+
if (!added)
|
|
348
|
+
return decision;
|
|
349
|
+
const seals = [
|
|
350
|
+
...(locators.sscs ?? []).map((seal) => seal.s.num),
|
|
351
|
+
...(locators.ssts ?? []).filter((seal) => seal.i.qb64 === this.issuer).map((seal) => seal.s.num),
|
|
352
|
+
];
|
|
353
|
+
const anchor = seals.length ? seals.reduce((high, sn) => (sn > high ? sn : high)) : null;
|
|
354
|
+
const accepted = this.db.getKever(this.issuer, { refresh: true })?.sner.num ?? null;
|
|
355
|
+
const query = missingAnchorQuery(this.issuer, accepted, anchor);
|
|
356
|
+
return query ? { ...decision, query } : decision;
|
|
357
|
+
}
|
|
328
358
|
/** Replay is read-only; first acceptance repeats this check under its write transaction. */
|
|
329
359
|
acceptedEvent(event) {
|
|
330
360
|
const existing = this.acceptedAt(event.regid, Number(event.sn));
|
|
@@ -150,8 +150,9 @@ function sealDigests(event) {
|
|
|
150
150
|
export function findRegistryAnchor(db, issuer, registry) {
|
|
151
151
|
let earliest = null;
|
|
152
152
|
for (const event of currentRegistryBranch(db, issuer)) {
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
// Seal test first: fullyWitnessed walks back to the governing establishment, so it runs only on matches.
|
|
154
|
+
if (event.seals.some((seal) => matchesRegistrySeal(seal, registry))
|
|
155
|
+
&& db.fullyWitnessed(event)) {
|
|
155
156
|
earliest = [event.sn, event.said];
|
|
156
157
|
}
|
|
157
158
|
}
|
|
@@ -161,7 +162,8 @@ export function findRegistryAnchor(db, issuer, registry) {
|
|
|
161
162
|
function findDigestAnchor(db, issuer, digest) {
|
|
162
163
|
let earliest = null;
|
|
163
164
|
for (const event of currentRegistryBranch(db, issuer)) {
|
|
164
|
-
|
|
165
|
+
// Seal test first: fullyWitnessed walks back to the governing establishment, so it runs only on matches.
|
|
166
|
+
if (sealDigests(event).includes(digest) && db.fullyWitnessed(event)) {
|
|
165
167
|
earliest = [event.sn, event.said];
|
|
166
168
|
}
|
|
167
169
|
}
|
|
@@ -202,6 +202,25 @@ export class RegistryStore {
|
|
|
202
202
|
const said = this.seqSaider(regk, sn);
|
|
203
203
|
return said ? this.event(said.qb64) : null;
|
|
204
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Registry identifiers with an accepted head, in key order: a scan of `heds.`. With `issuer`, only those whose
|
|
207
|
+
* accepted inception (`rip` at ordinal 0) names that issuer. Local aliases (`regs.`) play no part, so this also
|
|
208
|
+
* lists registries a store learned from others, such as a group registry a co-member incepted.
|
|
209
|
+
*/
|
|
210
|
+
registries(issuer) {
|
|
211
|
+
const found = [];
|
|
212
|
+
for (const [keys] of this.baser.heads.getTopItemIter()) {
|
|
213
|
+
const regk = String(keys[0]);
|
|
214
|
+
if (issuer === undefined) {
|
|
215
|
+
found.push(regk);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
const rip = this.seqEvent(regk, 0);
|
|
219
|
+
if (rip?.ilk === Ilks.rip && rip.issuer === issuer)
|
|
220
|
+
found.push(regk);
|
|
221
|
+
}
|
|
222
|
+
return found;
|
|
223
|
+
}
|
|
205
224
|
/** Publish caller-approved storage markers; validation and duplicate/head policy belong to the engine. */
|
|
206
225
|
accept(regk, sn, serder) {
|
|
207
226
|
sn = registryOrdinal(sn);
|
|
@@ -641,6 +641,10 @@ export class RegistryRoot {
|
|
|
641
641
|
throw new RootUnverified(error.message, { cause: error });
|
|
642
642
|
throw error;
|
|
643
643
|
}
|
|
644
|
+
// The recipient's key opens the box; without its signer the copy stays pending until the host holds it.
|
|
645
|
+
if (!hab.mgr.hasSigner(hab.kever.verfers[0].qb64)) {
|
|
646
|
+
throw new RootUnverified("the recipient's signer is not held; the copy stays pending until it is");
|
|
647
|
+
}
|
|
644
648
|
let plain;
|
|
645
649
|
try {
|
|
646
650
|
const opened = hab.mgr.open(essr, hab.kever.verfers[0]);
|
|
@@ -25,6 +25,7 @@ import { query, reply } from "../core/protocol-eventing.js";
|
|
|
25
25
|
import { messagize } from "../core/protocol-serialization.js";
|
|
26
26
|
import { fetchStoredTsgs, unverifiedReplyDecision } from "../core/routing.js";
|
|
27
27
|
import { prepareNests, publishNests } from "../db/exchange/exchange-nests.js";
|
|
28
|
+
import { QUERY_RESEND_MS } from "./query-resend.js";
|
|
28
29
|
import { validateUpdate, verifyRegistryAnchor, vet, vetBlind, vetBound, } from "./regeventing.js";
|
|
29
30
|
import { publicOpeningAttachment } from "./registering.js";
|
|
30
31
|
/** Ordinary registry states an Unblind operation may reveal unless its approval names others. */
|
|
@@ -89,7 +90,7 @@ const KEL_ASK_MS = 5000;
|
|
|
89
90
|
*/
|
|
90
91
|
export class Unblind {
|
|
91
92
|
/** Bind the exact Grant verifier's store; the state vocabulary is chosen per approved operation. */
|
|
92
|
-
constructor({ hab, exc, registry, rvy = hab.rvy, kram = true }) {
|
|
93
|
+
constructor({ hab, exc, registry, rvy = hab.rvy, now = Date.now, kram = true }) {
|
|
93
94
|
Object.defineProperty(this, "resource", {
|
|
94
95
|
enumerable: true,
|
|
95
96
|
configurable: true,
|
|
@@ -162,7 +163,14 @@ export class Unblind {
|
|
|
162
163
|
writable: true,
|
|
163
164
|
value: void 0
|
|
164
165
|
});
|
|
166
|
+
Object.defineProperty(this, "now", {
|
|
167
|
+
enumerable: true,
|
|
168
|
+
configurable: true,
|
|
169
|
+
writable: true,
|
|
170
|
+
value: void 0
|
|
171
|
+
});
|
|
165
172
|
this.kram = kram;
|
|
173
|
+
this.now = now;
|
|
166
174
|
if (!(hab instanceof Hab) || hab.db !== exc.hby.db || registry.db !== hab.db || rvy.db !== hab.db
|
|
167
175
|
|| exc.hby.habs.get(hab.pre) !== hab) {
|
|
168
176
|
throw new ValidationError("Unblind requires a local signing habitat");
|
|
@@ -228,6 +236,8 @@ export class Unblind {
|
|
|
228
236
|
scope: new Map([...context.registries].map(([regid, nodes]) => [regid, [...nodes]])),
|
|
229
237
|
query: null,
|
|
230
238
|
reply: null,
|
|
239
|
+
accepted: null,
|
|
240
|
+
repliedAt: 0,
|
|
231
241
|
requested: null,
|
|
232
242
|
selected: null,
|
|
233
243
|
openings: new Map(),
|
|
@@ -389,6 +399,11 @@ export class Unblind {
|
|
|
389
399
|
if (own && (!op.proposal || !bytesEqual(op.proposal, serder.raw))) {
|
|
390
400
|
throw new ValidationError("Own Unblind query lacks its exact locally approved proposal");
|
|
391
401
|
}
|
|
402
|
+
// A group re-sends its frozen query unchanged (one member cannot re-date it), and KERIpy resends identical
|
|
403
|
+
// bytes. The wire, attachments and evidence included, is compared against the one already authenticated, so
|
|
404
|
+
// a copy is never trusted on its body or SAID alone.
|
|
405
|
+
if (!own && op.accepted && bytesEqual(op.accepted, this.queryWire(envelope)))
|
|
406
|
+
return this.duplicateQuery(op);
|
|
392
407
|
if (!verifyQueryAttachments(this.hab.db, envelope))
|
|
393
408
|
return { kind: "drop", reason: "missingAuthentication" };
|
|
394
409
|
if (op.requested && !sameSelection(op.requested, selected)) {
|
|
@@ -429,9 +444,11 @@ export class Unblind {
|
|
|
429
444
|
op.requestedByPeer = true;
|
|
430
445
|
// KRAM now holds this query as seen, so a resend of it is a replay and a group cannot re-date it: the
|
|
431
446
|
// owed reply must outlive this owner. The accepted request is the durable record; the operation is a cache.
|
|
432
|
-
|
|
447
|
+
const wire = this.queryWire(envelope);
|
|
448
|
+
this.hab.db.heldu.pin([this.hab.pre, op.xid], wire);
|
|
433
449
|
if (!this.group)
|
|
434
450
|
this.respond(op.xid);
|
|
451
|
+
op.accepted = wire;
|
|
435
452
|
return { kind: "accept" };
|
|
436
453
|
}
|
|
437
454
|
catch (error) {
|
|
@@ -533,9 +550,27 @@ export class Unblind {
|
|
|
533
550
|
}
|
|
534
551
|
op.reply = signed;
|
|
535
552
|
}
|
|
536
|
-
this.
|
|
553
|
+
this.emitReply(op);
|
|
537
554
|
return op.reply.slice();
|
|
538
555
|
}
|
|
556
|
+
/** The query as held and compared: its frame, every attachment group, the public evidence and KRAM's normalized proof. */
|
|
557
|
+
queryWire(envelope) {
|
|
558
|
+
return auxiliaryQueryWire(envelope, this.publicEvents(envelope.nests));
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* A copy of the accepted query: nothing is verified or derived again. If the reply exists it is queued again for
|
|
562
|
+
* a peer that lost it, at most once per {@link QUERY_RESEND_MS}.
|
|
563
|
+
*/
|
|
564
|
+
duplicateQuery(op) {
|
|
565
|
+
if (op.reply && this.now() - op.repliedAt >= QUERY_RESEND_MS)
|
|
566
|
+
this.emitReply(op);
|
|
567
|
+
return { kind: "accept" };
|
|
568
|
+
}
|
|
569
|
+
/** Queue the stored reply for the recipient and note when. */
|
|
570
|
+
emitReply(op) {
|
|
571
|
+
this.outgoing.push({ kin: "reply", dest: op.recipient, msg: op.reply.slice() });
|
|
572
|
+
op.repliedAt = this.now();
|
|
573
|
+
}
|
|
539
574
|
/** Independently vet openings before caching them; Revery owns signature threshold and BADA. */
|
|
540
575
|
processReply({ serder, diger, route, cigars = [], tsgs = [], attachments }) {
|
|
541
576
|
try {
|
|
@@ -2,7 +2,8 @@ import { call, scoped } from "effection";
|
|
|
2
2
|
import { persistResolvedContact } from "../identity/organizing.js";
|
|
3
3
|
import { doEveryOnWrite, PROTOCOL_PACING } from "../runtime/doing.js";
|
|
4
4
|
import { systemClock } from "../runtime/runtime-clock.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isRequestableHttpUrl } from "../transport/http-client.js";
|
|
6
|
+
import { isWellKnownOobiUrl, oobiJobFailure, parseOobiUrl } from "./oobiery.js";
|
|
6
7
|
/**
|
|
7
8
|
* Return true when a well-known URL has been authorized into `wkas.`.
|
|
8
9
|
*
|
|
@@ -118,23 +119,20 @@ export class Authenticator {
|
|
|
118
119
|
return null;
|
|
119
120
|
}
|
|
120
121
|
/**
|
|
121
|
-
* Fetch one `mfa.` record and persist the terminal auth result.
|
|
122
|
+
* Fetch one `mfa.` record and persist the terminal auth result. A URL the HTTP client refuses, a refused or failed
|
|
123
|
+
* request, or a non-OK status each end this authentication as failed in `rmfa.` and remove its `mfa.` row, so the
|
|
124
|
+
* next pass takes the next one; any other error is a fault and propagates.
|
|
122
125
|
*
|
|
123
126
|
* @param url Exact pending well-known authentication URL.
|
|
124
127
|
* @param record Durable mfa record retained until response handling and cleanup establish terminal state.
|
|
125
128
|
*/
|
|
126
129
|
*processPendingAuth(url, record) {
|
|
127
|
-
// Only HTTP status is needed; the response owner still joins body cleanup.
|
|
128
130
|
const cid = record.cid
|
|
129
131
|
?? parseOobiUrl(url, record.oobialias ?? undefined).cid
|
|
130
132
|
?? null;
|
|
131
|
-
const
|
|
132
|
-
const response = yield* scoped(function* () {
|
|
133
|
-
const response = yield* http.open(url);
|
|
134
|
-
return { ok: response.ok, status: response.status };
|
|
135
|
-
});
|
|
133
|
+
const state = yield* this.authenticate(url);
|
|
136
134
|
const date = new Date().toISOString();
|
|
137
|
-
const
|
|
135
|
+
const ok = state === "resolved";
|
|
138
136
|
this.hby.db.mfa.rem(url);
|
|
139
137
|
this.hby.db.rmfa.pin(url, {
|
|
140
138
|
...record,
|
|
@@ -142,7 +140,7 @@ export class Authenticator {
|
|
|
142
140
|
state,
|
|
143
141
|
cid,
|
|
144
142
|
});
|
|
145
|
-
if (
|
|
143
|
+
if (ok && cid) {
|
|
146
144
|
this.hby.db.wkas.add(cid, { url, dt: date });
|
|
147
145
|
persistResolvedContact(this.hby, cid, {
|
|
148
146
|
alias: record.oobialias,
|
|
@@ -150,4 +148,28 @@ export class Authenticator {
|
|
|
150
148
|
});
|
|
151
149
|
}
|
|
152
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Request one well-known URL and read only its status; the response owner still joins body cleanup.
|
|
153
|
+
*
|
|
154
|
+
* @param url Pending well-known authentication URL.
|
|
155
|
+
* @returns The terminal state: `resolved` for an OK status, `http-<status>` otherwise, or the
|
|
156
|
+
* {@link OobiJobFailure} of a URL the client refuses or a refused or failed request.
|
|
157
|
+
*/
|
|
158
|
+
*authenticate(url) {
|
|
159
|
+
if (!isRequestableHttpUrl(url))
|
|
160
|
+
return "url-refused";
|
|
161
|
+
const http = this.http;
|
|
162
|
+
try {
|
|
163
|
+
return yield* scoped(function* () {
|
|
164
|
+
const response = yield* http.open(url);
|
|
165
|
+
return response.ok ? "resolved" : `http-${response.status}`;
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const failure = oobiJobFailure(error);
|
|
170
|
+
if (failure === null)
|
|
171
|
+
throw error;
|
|
172
|
+
return failure;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
153
175
|
}
|