@dxos/credentials 0.5.0 → 0.5.1-main.0ba1ecb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +755 -167
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +729 -144
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/credentials/credential-factory.d.ts +3 -1
- package/dist/types/src/credentials/credential-factory.d.ts.map +1 -1
- package/dist/types/src/credentials/credential-generator.d.ts +18 -2
- package/dist/types/src/credentials/credential-generator.d.ts.map +1 -1
- package/dist/types/src/credentials/signing.d.ts.map +1 -1
- package/dist/types/src/credentials/verifier.d.ts +1 -1
- package/dist/types/src/credentials/verifier.d.ts.map +1 -1
- package/dist/types/src/graph/credential-graph.d.ts +152 -0
- package/dist/types/src/graph/credential-graph.d.ts.map +1 -0
- package/dist/types/src/state-machine/member-state-machine.d.ts +17 -12
- package/dist/types/src/state-machine/member-state-machine.d.ts.map +1 -1
- package/dist/types/src/state-machine/member-state-machine.test.d.ts +2 -0
- package/dist/types/src/state-machine/member-state-machine.test.d.ts.map +1 -0
- package/dist/types/src/state-machine/space-state-machine.d.ts +3 -3
- package/dist/types/src/state-machine/space-state-machine.d.ts.map +1 -1
- package/package.json +16 -16
- package/project.json +3 -0
- package/src/credentials/credential-factory.ts +9 -1
- package/src/credentials/credential-generator.ts +35 -2
- package/src/credentials/signing.ts +1 -0
- package/src/credentials/verifier.test.ts +25 -0
- package/src/credentials/verifier.ts +5 -1
- package/src/graph/credential-graph.ts +557 -0
- package/src/state-machine/invitation-state-machine.test.ts +1 -1
- package/src/state-machine/member-state-machine.test.ts +580 -0
- package/src/state-machine/member-state-machine.ts +111 -53
- package/src/state-machine/space-state-machine.test.ts +20 -16
- package/src/state-machine/space-state-machine.ts +12 -33
|
@@ -112,6 +112,9 @@ import { PublicKey as PublicKey2 } from "@dxos/keys";
|
|
|
112
112
|
import { verifySignature } from "@dxos/crypto";
|
|
113
113
|
var SIGNATURE_TYPE_ED25519 = "ED25519Signature";
|
|
114
114
|
var verifyCredential = async (credential) => {
|
|
115
|
+
if (credential.parentCredentialIds?.length === 0) {
|
|
116
|
+
delete credential.parentCredentialIds;
|
|
117
|
+
}
|
|
115
118
|
if (!credential.issuer.equals(credential.proof.signer)) {
|
|
116
119
|
if (!credential.proof.chain) {
|
|
117
120
|
return {
|
|
@@ -176,10 +179,10 @@ var verifyChain = async (chain, authority, subject) => {
|
|
|
176
179
|
|
|
177
180
|
// packages/core/halo/credentials/src/credentials/credential-factory.ts
|
|
178
181
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/credentials/credential-factory.ts";
|
|
179
|
-
var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce }) => {
|
|
182
|
+
var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce, parentCredentialIds }) => {
|
|
180
183
|
invariant(assertion["@type"], "Invalid assertion.", {
|
|
181
184
|
F: __dxlog_file,
|
|
182
|
-
L:
|
|
185
|
+
L: 48,
|
|
183
186
|
S: void 0,
|
|
184
187
|
A: [
|
|
185
188
|
"assertion['@type']",
|
|
@@ -188,7 +191,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
188
191
|
});
|
|
189
192
|
invariant(!!signingKey === !!chain, "Chain must be provided if and only if the signing key differs from the issuer.", {
|
|
190
193
|
F: __dxlog_file,
|
|
191
|
-
L:
|
|
194
|
+
L: 49,
|
|
192
195
|
S: void 0,
|
|
193
196
|
A: [
|
|
194
197
|
"!!signingKey === !!chain",
|
|
@@ -199,7 +202,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
199
202
|
const result = await verifyChain(chain, issuer, signingKey);
|
|
200
203
|
invariant(result.kind === "pass", "Invalid chain.", {
|
|
201
204
|
F: __dxlog_file,
|
|
202
|
-
L:
|
|
205
|
+
L: 52,
|
|
203
206
|
S: void 0,
|
|
204
207
|
A: [
|
|
205
208
|
"result.kind === 'pass'",
|
|
@@ -222,6 +225,9 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
222
225
|
nonce
|
|
223
226
|
}
|
|
224
227
|
};
|
|
228
|
+
if ((parentCredentialIds?.length ?? 0) > 0) {
|
|
229
|
+
credential.parentCredentialIds = parentCredentialIds;
|
|
230
|
+
}
|
|
225
231
|
const signedPayload = getCredentialProofPayload(credential);
|
|
226
232
|
credential.proof.value = await signer.sign(signingKey ?? issuer, signedPayload);
|
|
227
233
|
if (chain) {
|
|
@@ -238,12 +244,13 @@ var createCredentialMessage = (credential) => {
|
|
|
238
244
|
};
|
|
239
245
|
var createCredentialSignerWithKey = (signer, issuer) => ({
|
|
240
246
|
getIssuer: () => issuer,
|
|
241
|
-
createCredential: ({ subject, assertion, nonce }) => createCredential({
|
|
247
|
+
createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
|
|
242
248
|
signer,
|
|
243
249
|
issuer,
|
|
244
250
|
subject,
|
|
245
251
|
assertion,
|
|
246
|
-
nonce
|
|
252
|
+
nonce,
|
|
253
|
+
parentCredentialIds
|
|
247
254
|
})
|
|
248
255
|
});
|
|
249
256
|
var createCredentialSignerWithChain = (signer, chain, signingKey) => ({
|
|
@@ -311,7 +318,7 @@ var CredentialGenerator = class {
|
|
|
311
318
|
assertion: {
|
|
312
319
|
"@type": "dxos.halo.credentials.SpaceMember",
|
|
313
320
|
spaceKey,
|
|
314
|
-
role: SpaceMember.Role.
|
|
321
|
+
role: SpaceMember.Role.EDITOR,
|
|
315
322
|
genesisFeedKey
|
|
316
323
|
}
|
|
317
324
|
}),
|
|
@@ -406,14 +413,15 @@ var createDeviceAuthorization = async (signer, identityKey, deviceKey) => {
|
|
|
406
413
|
credential
|
|
407
414
|
}));
|
|
408
415
|
};
|
|
409
|
-
var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, profile, invitationCredentialId) => {
|
|
416
|
+
var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, role = SpaceMember.Role.ADMIN, membershipChainHeads = [], profile, invitationCredentialId) => {
|
|
410
417
|
const credentials = await Promise.all([
|
|
411
418
|
await signer.createCredential({
|
|
412
419
|
subject: identityKey,
|
|
420
|
+
parentCredentialIds: membershipChainHeads,
|
|
413
421
|
assertion: {
|
|
414
422
|
"@type": "dxos.halo.credentials.SpaceMember",
|
|
415
423
|
spaceKey,
|
|
416
|
-
role
|
|
424
|
+
role,
|
|
417
425
|
profile,
|
|
418
426
|
genesisFeedKey,
|
|
419
427
|
invitationCredentialId
|
|
@@ -446,6 +454,20 @@ var createDelegatedSpaceInvitationCredential = async (signer, subject, invitatio
|
|
|
446
454
|
}
|
|
447
455
|
};
|
|
448
456
|
};
|
|
457
|
+
var createCancelDelegatedSpaceInvitationCredential = async (signer, subject, invitationCredentialId) => {
|
|
458
|
+
const credential = await signer.createCredential({
|
|
459
|
+
subject,
|
|
460
|
+
assertion: {
|
|
461
|
+
"@type": "dxos.halo.invitations.CancelDelegatedInvitation",
|
|
462
|
+
credentialId: invitationCredentialId
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
return {
|
|
466
|
+
credential: {
|
|
467
|
+
credential
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
};
|
|
449
471
|
|
|
450
472
|
// packages/core/halo/credentials/src/presentations/presentation.ts
|
|
451
473
|
var signPresentation = async ({ presentation, signer, signerKey, chain, nonce }) => {
|
|
@@ -557,10 +579,10 @@ var verifyPresentationSignature = async (presentation, proof) => {
|
|
|
557
579
|
// packages/core/halo/credentials/src/state-machine/space-state-machine.ts
|
|
558
580
|
import { runInContextAsync } from "@dxos/async";
|
|
559
581
|
import { Context } from "@dxos/context";
|
|
560
|
-
import { PublicKey as
|
|
561
|
-
import { log as
|
|
562
|
-
import { SpaceMember as
|
|
563
|
-
import { Callback as Callback4, ComplexMap as
|
|
582
|
+
import { PublicKey as PublicKey7 } from "@dxos/keys";
|
|
583
|
+
import { log as log3 } from "@dxos/log";
|
|
584
|
+
import { SpaceMember as SpaceMember3 } from "@dxos/protocols/proto/dxos/halo/credentials";
|
|
585
|
+
import { Callback as Callback4, ComplexMap as ComplexMap5, ComplexSet as ComplexSet3 } from "@dxos/util";
|
|
564
586
|
|
|
565
587
|
// packages/core/halo/credentials/src/state-machine/feed-state-machine.ts
|
|
566
588
|
import { invariant as invariant2 } from "@dxos/invariant";
|
|
@@ -684,25 +706,587 @@ var InvitationStateMachine = class {
|
|
|
684
706
|
};
|
|
685
707
|
|
|
686
708
|
// packages/core/halo/credentials/src/state-machine/member-state-machine.ts
|
|
709
|
+
import { invariant as invariant4 } from "@dxos/invariant";
|
|
710
|
+
import { PublicKey as PublicKey6 } from "@dxos/keys";
|
|
711
|
+
import { log as log2 } from "@dxos/log";
|
|
712
|
+
import { SpaceMember as SpaceMember2 } from "@dxos/protocols/proto/dxos/halo/credentials";
|
|
713
|
+
import { ComplexMap as ComplexMap4 } from "@dxos/util";
|
|
714
|
+
|
|
715
|
+
// packages/core/halo/credentials/src/graph/credential-graph.ts
|
|
687
716
|
import { invariant as invariant3 } from "@dxos/invariant";
|
|
688
717
|
import { PublicKey as PublicKey5 } from "@dxos/keys";
|
|
689
718
|
import { log } from "@dxos/log";
|
|
690
|
-
import { Callback as Callback3, ComplexMap as ComplexMap3 } from "@dxos/util";
|
|
691
|
-
var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/
|
|
719
|
+
import { Callback as Callback3, ComplexMap as ComplexMap3, ComplexSet as ComplexSet2 } from "@dxos/util";
|
|
720
|
+
var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/graph/credential-graph.ts";
|
|
721
|
+
var CredentialGraph = class {
|
|
722
|
+
constructor(_stateHandler) {
|
|
723
|
+
this._stateHandler = _stateHandler;
|
|
724
|
+
this._vertexIdGenerator = 1;
|
|
725
|
+
this._root = {
|
|
726
|
+
id: -1,
|
|
727
|
+
parents: [],
|
|
728
|
+
children: []
|
|
729
|
+
};
|
|
730
|
+
this._sentinel = {
|
|
731
|
+
id: -2,
|
|
732
|
+
parents: [],
|
|
733
|
+
children: []
|
|
734
|
+
};
|
|
735
|
+
this._vertexByCredentialId = new ComplexMap3(PublicKey5.hash);
|
|
736
|
+
this._subjectToVertex = new ComplexMap3(PublicKey5.hash);
|
|
737
|
+
this._subjectToState = new ComplexMap3(PublicKey5.hash);
|
|
738
|
+
this.onSubjectStateChanged = new Callback3();
|
|
739
|
+
}
|
|
740
|
+
getSubjectState(subjectId) {
|
|
741
|
+
return this._subjectToState.get(subjectId);
|
|
742
|
+
}
|
|
743
|
+
getState() {
|
|
744
|
+
return this._subjectToState;
|
|
745
|
+
}
|
|
746
|
+
getLeafIds() {
|
|
747
|
+
return this._sentinel.parents.map((v) => v.credential.id);
|
|
748
|
+
}
|
|
749
|
+
getGlobalStateScope() {
|
|
750
|
+
return {
|
|
751
|
+
state: this._subjectToVertex
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
addVertex(credential, assertion) {
|
|
755
|
+
const newVertex = {
|
|
756
|
+
id: this._vertexIdGenerator++,
|
|
757
|
+
credential,
|
|
758
|
+
assertion,
|
|
759
|
+
parents: [],
|
|
760
|
+
children: []
|
|
761
|
+
};
|
|
762
|
+
this._vertexByCredentialId.set(credential.id, newVertex);
|
|
763
|
+
const parentIds = credential.parentCredentialIds ?? [];
|
|
764
|
+
if (parentIds.length === 0) {
|
|
765
|
+
this._root.children.push(newVertex);
|
|
766
|
+
newVertex.parents.push(this._root);
|
|
767
|
+
} else {
|
|
768
|
+
for (const parentId of parentIds) {
|
|
769
|
+
const parentVertex = this._vertexByCredentialId.get(parentId);
|
|
770
|
+
if (parentVertex == null) {
|
|
771
|
+
log.error("credential skipped because of the unknown parent", {
|
|
772
|
+
credential,
|
|
773
|
+
parentId
|
|
774
|
+
}, {
|
|
775
|
+
F: __dxlog_file3,
|
|
776
|
+
L: 72,
|
|
777
|
+
S: this,
|
|
778
|
+
C: (f, a) => f(...a)
|
|
779
|
+
});
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
parentVertex.children.push(newVertex);
|
|
783
|
+
newVertex.parents.push(parentVertex);
|
|
784
|
+
this._removeSentinelConnection(parentVertex);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
newVertex.children.push(this._sentinel);
|
|
788
|
+
this._sentinel.parents.push(newVertex);
|
|
789
|
+
return this._onVertexInserted(newVertex);
|
|
790
|
+
}
|
|
791
|
+
_removeSentinelConnection(vertex) {
|
|
792
|
+
const sentinelIdx = vertex.children.indexOf(this._sentinel);
|
|
793
|
+
if (sentinelIdx >= 0) {
|
|
794
|
+
vertex.children.splice(sentinelIdx, 1);
|
|
795
|
+
const vertexInSentinelIdx = this._sentinel.parents.indexOf(vertex);
|
|
796
|
+
invariant3(vertexInSentinelIdx >= 0, void 0, {
|
|
797
|
+
F: __dxlog_file3,
|
|
798
|
+
L: 90,
|
|
799
|
+
S: this,
|
|
800
|
+
A: [
|
|
801
|
+
"vertexInSentinelIdx >= 0",
|
|
802
|
+
""
|
|
803
|
+
]
|
|
804
|
+
});
|
|
805
|
+
this._sentinel.parents.splice(vertexInSentinelIdx, 1);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
async _onVertexInserted(newVertex) {
|
|
809
|
+
const { credential, assertion } = newVertex;
|
|
810
|
+
invariant3(credential, void 0, {
|
|
811
|
+
F: __dxlog_file3,
|
|
812
|
+
L: 97,
|
|
813
|
+
S: this,
|
|
814
|
+
A: [
|
|
815
|
+
"credential",
|
|
816
|
+
""
|
|
817
|
+
]
|
|
818
|
+
});
|
|
819
|
+
let changedSubjects = [];
|
|
820
|
+
const isUpdateAppliedOnTopOfThePreviousState = this._sentinel.parents.length === 1;
|
|
821
|
+
if (isUpdateAppliedOnTopOfThePreviousState) {
|
|
822
|
+
const subjectId = credential.subject.id;
|
|
823
|
+
if (this._stateHandler.isUpdateAllowed(this.getGlobalStateScope(), credential, assertion)) {
|
|
824
|
+
const newSubjectState = this._stateHandler.createState(credential, newVertex.assertion);
|
|
825
|
+
const prevSubjectState = this._subjectToState.get(subjectId);
|
|
826
|
+
this._subjectToState.set(subjectId, newSubjectState);
|
|
827
|
+
this._subjectToVertex.set(subjectId, newVertex);
|
|
828
|
+
if (this._stateHandler.hasStateChanged(newSubjectState, prevSubjectState)) {
|
|
829
|
+
changedSubjects.push(newSubjectState);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
} else {
|
|
833
|
+
changedSubjects = this._recomputeState();
|
|
834
|
+
}
|
|
835
|
+
if (changedSubjects.length > 0) {
|
|
836
|
+
await this.onSubjectStateChanged.callIfSet(changedSubjects);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* DFS the graph from root to sentinel pausing on merge points (nodes with multiple parents).
|
|
841
|
+
* Continue after all paths leading to a merge point converge by merging their states.
|
|
842
|
+
* In case of a concurrent update paths are replayed taking into account the state set
|
|
843
|
+
* by the winning branch.
|
|
844
|
+
*/
|
|
845
|
+
_recomputeState() {
|
|
846
|
+
const pendingPaths = /* @__PURE__ */ new Map();
|
|
847
|
+
const paths = [
|
|
848
|
+
this._createRootPath()
|
|
849
|
+
];
|
|
850
|
+
let lastPath = null;
|
|
851
|
+
while (lastPath == null) {
|
|
852
|
+
const path = paths.pop();
|
|
853
|
+
log("visit vertex", {
|
|
854
|
+
id: path.head.id
|
|
855
|
+
}, {
|
|
856
|
+
F: __dxlog_file3,
|
|
857
|
+
L: 132,
|
|
858
|
+
S: this,
|
|
859
|
+
C: (f, a) => f(...a)
|
|
860
|
+
});
|
|
861
|
+
this._updatePathState(path);
|
|
862
|
+
const convergedPaths = this._handleMergePoint(paths, pendingPaths, path);
|
|
863
|
+
if (convergedPaths == null) {
|
|
864
|
+
log("waiting for other paths", void 0, {
|
|
865
|
+
F: __dxlog_file3,
|
|
866
|
+
L: 136,
|
|
867
|
+
S: this,
|
|
868
|
+
C: (f, a) => f(...a)
|
|
869
|
+
});
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
const mergeResult = this._mergePaths(convergedPaths);
|
|
873
|
+
if (mergeResult.type === "replay_required") {
|
|
874
|
+
this._replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths);
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
const merged = mergeResult.path;
|
|
878
|
+
if (merged.head.children.length === 0) {
|
|
879
|
+
lastPath = merged;
|
|
880
|
+
} else if (merged.head.children.length === 1) {
|
|
881
|
+
merged.head = merged.head.children[0];
|
|
882
|
+
paths.push(merged);
|
|
883
|
+
} else {
|
|
884
|
+
this._forkTraversal(paths, merged);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (paths.length > 0) {
|
|
888
|
+
log.error("traversal finished while there were active paths", {
|
|
889
|
+
paths: paths.map((p) => ({
|
|
890
|
+
path: toChosenPath(p),
|
|
891
|
+
head: p.head.id
|
|
892
|
+
}))
|
|
893
|
+
}, {
|
|
894
|
+
F: __dxlog_file3,
|
|
895
|
+
L: 155,
|
|
896
|
+
S: this,
|
|
897
|
+
C: (f, a) => f(...a)
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
return this._setCurrentState(lastPath);
|
|
901
|
+
}
|
|
902
|
+
_replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths) {
|
|
903
|
+
paths.push(...mergeResult.replay.map((path) => {
|
|
904
|
+
const stateOverrides = path.stateOverrides ?? new ComplexMap3(PublicKey5.hash);
|
|
905
|
+
mergeResult.stateOverrides.forEach((value, key) => stateOverrides.set(key, value));
|
|
906
|
+
return {
|
|
907
|
+
...mergeResult.from,
|
|
908
|
+
chosenPath: path.chosenPath,
|
|
909
|
+
stateOverrides
|
|
910
|
+
};
|
|
911
|
+
}));
|
|
912
|
+
log("replay paths", () => ({
|
|
913
|
+
count: paths.length,
|
|
914
|
+
paths: paths.map((path) => ({
|
|
915
|
+
from: mergeResult.from.head.id,
|
|
916
|
+
path: toChosenPath(path),
|
|
917
|
+
overrides: path?.stateOverrides?.mapValues((v) => this._stateHandler.toLogString(v.assertion))
|
|
918
|
+
}))
|
|
919
|
+
}), {
|
|
920
|
+
F: __dxlog_file3,
|
|
921
|
+
L: 175,
|
|
922
|
+
S: this,
|
|
923
|
+
C: (f, a) => f(...a)
|
|
924
|
+
});
|
|
925
|
+
const clearedPending = convergedPaths.filter((l) => !mergeResult.replay.includes(l));
|
|
926
|
+
pendingPaths.set(convergedPaths[0].head.id, clearedPending);
|
|
927
|
+
}
|
|
928
|
+
_handleMergePoint(paths, pendingPaths, path) {
|
|
929
|
+
const pendingList = pendingPaths.get(path.head.id) ?? [];
|
|
930
|
+
pendingPaths.set(path.head.id, pendingList);
|
|
931
|
+
pendingList.push(path);
|
|
932
|
+
if (pendingList.length < path.head.parents.length) {
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
if (path.head.id === this._sentinel.id && paths.length > 0) {
|
|
936
|
+
log("waiting for all the active paths to converge on the sentinel", void 0, {
|
|
937
|
+
F: __dxlog_file3,
|
|
938
|
+
L: 199,
|
|
939
|
+
S: this,
|
|
940
|
+
C: (f, a) => f(...a)
|
|
941
|
+
});
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
pendingPaths.delete(path.head.id);
|
|
945
|
+
return pendingList;
|
|
946
|
+
}
|
|
947
|
+
_updatePathState(path) {
|
|
948
|
+
const headCredential = path.head.credential;
|
|
949
|
+
if (headCredential == null) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const updatedSubject = headCredential.subject.id;
|
|
953
|
+
path.credentials.add(headCredential.id);
|
|
954
|
+
let isUpdateAllowed = this._stateHandler.isUpdateAllowed(path, headCredential, path.head.assertion);
|
|
955
|
+
if (!isUpdateAllowed && path.head.parents[0]?.id === this._root.id) {
|
|
956
|
+
const globalState = this.getGlobalStateScope();
|
|
957
|
+
isUpdateAllowed = this._stateHandler.isUpdateAllowed(globalState, headCredential, path.head.assertion);
|
|
958
|
+
}
|
|
959
|
+
if (isUpdateAllowed) {
|
|
960
|
+
path.forkChangedSubjects.add(updatedSubject);
|
|
961
|
+
path.forkIssuers.add(headCredential.issuer);
|
|
962
|
+
path.state.set(updatedSubject, path.head);
|
|
963
|
+
log("path state updated", () => ({
|
|
964
|
+
subject: updatedSubject,
|
|
965
|
+
newState: this._stateHandler.toLogString(path.head.assertion)
|
|
966
|
+
}), {
|
|
967
|
+
F: __dxlog_file3,
|
|
968
|
+
L: 223,
|
|
969
|
+
S: this,
|
|
970
|
+
C: (f, a) => f(...a)
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
_forkTraversal(paths, path) {
|
|
975
|
+
const replayChoice = path.chosenPath?.[path.head.id];
|
|
976
|
+
const choices = replayChoice ?? path.head.children;
|
|
977
|
+
for (const choice of choices) {
|
|
978
|
+
log("edge traversal", {
|
|
979
|
+
from: path.head.id,
|
|
980
|
+
to: choice.id
|
|
981
|
+
}, {
|
|
982
|
+
F: __dxlog_file3,
|
|
983
|
+
L: 234,
|
|
984
|
+
S: this,
|
|
985
|
+
C: (f, a) => f(...a)
|
|
986
|
+
});
|
|
987
|
+
const fork = {
|
|
988
|
+
forkPoint: path,
|
|
989
|
+
chosenPath: {
|
|
990
|
+
...path.chosenPath,
|
|
991
|
+
[path.head.id]: [
|
|
992
|
+
choice
|
|
993
|
+
]
|
|
994
|
+
},
|
|
995
|
+
head: choice,
|
|
996
|
+
credentials: new ComplexSet2(PublicKey5.hash, path.credentials),
|
|
997
|
+
state: new ComplexMap3(PublicKey5.hash, [
|
|
998
|
+
...path.state.entries()
|
|
999
|
+
]),
|
|
1000
|
+
forkIssuers: new ComplexSet2(PublicKey5.hash),
|
|
1001
|
+
forkChangedSubjects: new ComplexSet2(PublicKey5.hash),
|
|
1002
|
+
stateOverrides: path.stateOverrides
|
|
1003
|
+
};
|
|
1004
|
+
paths.push(fork);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Updates the current graph state.
|
|
1009
|
+
* @returns changed states.
|
|
1010
|
+
*/
|
|
1011
|
+
_setCurrentState(path) {
|
|
1012
|
+
const changedSubjects = [];
|
|
1013
|
+
const newStateMap = new ComplexMap3(PublicKey5.hash);
|
|
1014
|
+
const newVertexMap = new ComplexMap3(PublicKey5.hash);
|
|
1015
|
+
for (const [subjectKey, subjectVertex] of path.state.entries()) {
|
|
1016
|
+
const newState = this._stateHandler.createState(subjectVertex.credential, subjectVertex.assertion);
|
|
1017
|
+
const prevState = this._subjectToState.get(subjectKey);
|
|
1018
|
+
newStateMap.set(subjectKey, newState);
|
|
1019
|
+
newVertexMap.set(subjectKey, subjectVertex);
|
|
1020
|
+
if (this._stateHandler.hasStateChanged(newState, prevState)) {
|
|
1021
|
+
changedSubjects.push(newState);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
this._subjectToState = newStateMap;
|
|
1025
|
+
this._subjectToVertex = newVertexMap;
|
|
1026
|
+
return changedSubjects;
|
|
1027
|
+
}
|
|
1028
|
+
/*
|
|
1029
|
+
* Walk up all the fork points and return the first one present in all the paths.
|
|
1030
|
+
* We use local id to determine vertex position in the graph, because nodes can't
|
|
1031
|
+
* be inserted in the middle (between a parent and a child) and ids are monotonically increasing.
|
|
1032
|
+
*/
|
|
1033
|
+
_leastCommonAncestor(paths) {
|
|
1034
|
+
const uniqueForkPoints = paths.reduce((acc, path) => {
|
|
1035
|
+
let it = path.forkPoint;
|
|
1036
|
+
while (it) {
|
|
1037
|
+
acc.set(it.head.id, it);
|
|
1038
|
+
it = it.forkPoint;
|
|
1039
|
+
}
|
|
1040
|
+
return acc;
|
|
1041
|
+
}, /* @__PURE__ */ new Map());
|
|
1042
|
+
let maxId = this._root.id;
|
|
1043
|
+
let maxState = null;
|
|
1044
|
+
for (const [id, state] of uniqueForkPoints.entries()) {
|
|
1045
|
+
const headCredential = state.head.credential;
|
|
1046
|
+
if (headCredential != null) {
|
|
1047
|
+
const isPointInEveryPath = paths.every((p) => p.credentials.has(headCredential.id));
|
|
1048
|
+
if (isPointInEveryPath && id > maxId) {
|
|
1049
|
+
maxId = id;
|
|
1050
|
+
maxState = state;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
return maxState ?? this._createRootPath();
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* We might be merging paths where some of them had fork points after the initial forking.
|
|
1058
|
+
* We need all the paths to point to the least common fork point and contain all the changes
|
|
1059
|
+
* that happened after it.
|
|
1060
|
+
*/
|
|
1061
|
+
_moveUpToForkPoint(forkPoint, path) {
|
|
1062
|
+
const isForkPointInPath = path.chosenPath[forkPoint.head.id] == null || path.forkPoint == null;
|
|
1063
|
+
if (isForkPointInPath) {
|
|
1064
|
+
return path;
|
|
1065
|
+
}
|
|
1066
|
+
if (forkPoint.head.id === path.forkPoint?.head.id) {
|
|
1067
|
+
return path;
|
|
1068
|
+
}
|
|
1069
|
+
let it = path.forkPoint;
|
|
1070
|
+
while (it.head.id !== forkPoint.head.id) {
|
|
1071
|
+
it.forkIssuers.forEach((iss) => path.forkIssuers.add(iss));
|
|
1072
|
+
it.forkChangedSubjects.forEach((m) => path.forkChangedSubjects.add(m));
|
|
1073
|
+
it = it.forkPoint;
|
|
1074
|
+
path.forkPoint = it;
|
|
1075
|
+
}
|
|
1076
|
+
return path;
|
|
1077
|
+
}
|
|
1078
|
+
_mergePaths(convergedPaths) {
|
|
1079
|
+
invariant3(convergedPaths.length >= 1, void 0, {
|
|
1080
|
+
F: __dxlog_file3,
|
|
1081
|
+
L: 324,
|
|
1082
|
+
S: this,
|
|
1083
|
+
A: [
|
|
1084
|
+
"convergedPaths.length >= 1",
|
|
1085
|
+
""
|
|
1086
|
+
]
|
|
1087
|
+
});
|
|
1088
|
+
if (convergedPaths.length === 1) {
|
|
1089
|
+
return {
|
|
1090
|
+
type: "merged",
|
|
1091
|
+
path: convergedPaths[0]
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
const forkPoint = this._leastCommonAncestor(convergedPaths);
|
|
1095
|
+
log("merging paths", () => ({
|
|
1096
|
+
forkPointId: forkPoint.head.id,
|
|
1097
|
+
pathCount: convergedPaths.length,
|
|
1098
|
+
forkPoints: convergedPaths.map((fp) => fp.forkPoint?.head.id)
|
|
1099
|
+
}), {
|
|
1100
|
+
F: __dxlog_file3,
|
|
1101
|
+
L: 329,
|
|
1102
|
+
S: this,
|
|
1103
|
+
C: (f, a) => f(...a)
|
|
1104
|
+
});
|
|
1105
|
+
const paths = convergedPaths.map((p) => this._moveUpToForkPoint(forkPoint, p));
|
|
1106
|
+
invariant3(forkPoint, void 0, {
|
|
1107
|
+
F: __dxlog_file3,
|
|
1108
|
+
L: 335,
|
|
1109
|
+
S: this,
|
|
1110
|
+
A: [
|
|
1111
|
+
"forkPoint",
|
|
1112
|
+
""
|
|
1113
|
+
]
|
|
1114
|
+
});
|
|
1115
|
+
const result = {
|
|
1116
|
+
forkPoint: forkPoint.forkPoint,
|
|
1117
|
+
chosenPath: {
|
|
1118
|
+
...forkPoint.chosenPath,
|
|
1119
|
+
[forkPoint.head.id]: []
|
|
1120
|
+
},
|
|
1121
|
+
stateOverrides: forkPoint.stateOverrides,
|
|
1122
|
+
credentials: new ComplexSet2(PublicKey5.hash, forkPoint.credentials),
|
|
1123
|
+
forkIssuers: new ComplexSet2(PublicKey5.hash, forkPoint.forkIssuers),
|
|
1124
|
+
forkChangedSubjects: new ComplexSet2(PublicKey5.hash, forkPoint.forkChangedSubjects),
|
|
1125
|
+
state: forkPoint.state.mapValues((v) => v),
|
|
1126
|
+
head: paths[0].head
|
|
1127
|
+
};
|
|
1128
|
+
const subjectToBranch = new ComplexMap3(PublicKey5.hash);
|
|
1129
|
+
for (const path of paths) {
|
|
1130
|
+
log("processing a path", () => ({
|
|
1131
|
+
choices: toChosenPath(path),
|
|
1132
|
+
modified: path.forkChangedSubjects,
|
|
1133
|
+
forkIssuers: path.forkIssuers,
|
|
1134
|
+
state: path.state.mapValues((v) => this._stateHandler.toLogString(v.assertion))
|
|
1135
|
+
}), {
|
|
1136
|
+
F: __dxlog_file3,
|
|
1137
|
+
L: 348,
|
|
1138
|
+
S: this,
|
|
1139
|
+
C: (f, a) => f(...a)
|
|
1140
|
+
});
|
|
1141
|
+
path.forkIssuers.forEach((iss) => result.forkIssuers.add(iss));
|
|
1142
|
+
path.credentials.forEach((cred) => result.credentials.add(cred));
|
|
1143
|
+
result.chosenPath[forkPoint.head.id].push(...path.chosenPath[forkPoint.head.id] ?? []);
|
|
1144
|
+
for (const modifiedSubject of path.forkChangedSubjects) {
|
|
1145
|
+
const existingBranch = subjectToBranch.get(modifiedSubject);
|
|
1146
|
+
if (existingBranch == null || this._shouldOverrideCredential(existingBranch, path, modifiedSubject)) {
|
|
1147
|
+
subjectToBranch.set(modifiedSubject, path);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
const replayPaths = /* @__PURE__ */ new Set();
|
|
1152
|
+
const addReplayPath = replayPaths.add.bind(replayPaths);
|
|
1153
|
+
for (const [subject, branch] of subjectToBranch.entries()) {
|
|
1154
|
+
result.forkChangedSubjects.add(subject);
|
|
1155
|
+
const vertex = branch.state.get(subject);
|
|
1156
|
+
result.state.set(subject, vertex);
|
|
1157
|
+
log("set subject state", () => ({
|
|
1158
|
+
subject,
|
|
1159
|
+
state: this._stateHandler.toLogString(vertex.assertion)
|
|
1160
|
+
}), {
|
|
1161
|
+
F: __dxlog_file3,
|
|
1162
|
+
L: 370,
|
|
1163
|
+
S: this,
|
|
1164
|
+
C: (f, a) => f(...a)
|
|
1165
|
+
});
|
|
1166
|
+
const otherPaths = paths.filter((p) => p !== branch);
|
|
1167
|
+
this._stateHandler.getConflictingPaths(otherPaths, vertex).forEach(addReplayPath);
|
|
1168
|
+
}
|
|
1169
|
+
if (replayPaths.size > 0) {
|
|
1170
|
+
return {
|
|
1171
|
+
type: "replay_required",
|
|
1172
|
+
replay: [
|
|
1173
|
+
...replayPaths.values()
|
|
1174
|
+
],
|
|
1175
|
+
from: forkPoint,
|
|
1176
|
+
stateOverrides: subjectToBranch.mapValues((v, key) => v.state.get(key))
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
return {
|
|
1180
|
+
type: "merged",
|
|
1181
|
+
path: result
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* A candidate credential is preferred over the existing credential if:
|
|
1186
|
+
* 1. It is the merge-point, because it's the last credential that was issued in awareness of all
|
|
1187
|
+
* the previously existing ones.
|
|
1188
|
+
* 2. A path where candidate was set contains existing credential in it, which means that the candidate
|
|
1189
|
+
* was issued after the existing credential by a legitimate issuer.
|
|
1190
|
+
* 3. A state-specific logic (_stateHandler) is able to justify using the candidate credential.
|
|
1191
|
+
* 4. The path where candidate was set has more issuers than the existing path (longer branch).
|
|
1192
|
+
* 5. The issuance time of the candidate is after the issuance time of the existing credential (LWW).
|
|
1193
|
+
*/
|
|
1194
|
+
_shouldOverrideCredential(existing, candidate, modifiedSubject) {
|
|
1195
|
+
const candidateVertex = candidate.state.get(modifiedSubject);
|
|
1196
|
+
const currentVertex = existing.state.get(modifiedSubject);
|
|
1197
|
+
if (candidateVertex.id === currentVertex.id) {
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
const mergePointId = existing.head.id;
|
|
1201
|
+
if (candidateVertex.id === mergePointId || currentVertex.id === mergePointId) {
|
|
1202
|
+
log("merge point chosen to break the tie", {
|
|
1203
|
+
mergePointId: existing.head.id
|
|
1204
|
+
}, {
|
|
1205
|
+
F: __dxlog_file3,
|
|
1206
|
+
L: 408,
|
|
1207
|
+
S: this,
|
|
1208
|
+
C: (f, a) => f(...a)
|
|
1209
|
+
});
|
|
1210
|
+
return mergePointId === candidateVertex.id;
|
|
1211
|
+
}
|
|
1212
|
+
const candidateCredential = candidateVertex.credential;
|
|
1213
|
+
const currentCredential = currentVertex.credential;
|
|
1214
|
+
if (existing.credentials.has(candidateCredential.id) !== candidate.credentials.has(currentCredential.id)) {
|
|
1215
|
+
log("one of the credentials was overridden in another branch", {
|
|
1216
|
+
current: currentVertex.id,
|
|
1217
|
+
candidate: candidateVertex.id
|
|
1218
|
+
}, {
|
|
1219
|
+
F: __dxlog_file3,
|
|
1220
|
+
L: 415,
|
|
1221
|
+
S: this,
|
|
1222
|
+
C: (f, a) => f(...a)
|
|
1223
|
+
});
|
|
1224
|
+
return candidate.credentials.has(currentCredential.id);
|
|
1225
|
+
}
|
|
1226
|
+
const winningCredential = this._stateHandler.tryPickWinningUpdate(existing, currentCredential, candidate, candidateCredential);
|
|
1227
|
+
if (winningCredential != null) {
|
|
1228
|
+
return winningCredential === candidateCredential;
|
|
1229
|
+
}
|
|
1230
|
+
if (candidate.forkIssuers.size !== existing.forkIssuers.size) {
|
|
1231
|
+
log("longer issuers branch used to break the tie", {
|
|
1232
|
+
issuerCount: [
|
|
1233
|
+
existing.forkIssuers.size,
|
|
1234
|
+
candidate.forkIssuers.size
|
|
1235
|
+
]
|
|
1236
|
+
}, {
|
|
1237
|
+
F: __dxlog_file3,
|
|
1238
|
+
L: 432,
|
|
1239
|
+
S: this,
|
|
1240
|
+
C: (f, a) => f(...a)
|
|
1241
|
+
});
|
|
1242
|
+
return candidate.forkIssuers.size > existing.forkIssuers.size;
|
|
1243
|
+
}
|
|
1244
|
+
log("issuance date used to break the tie", void 0, {
|
|
1245
|
+
F: __dxlog_file3,
|
|
1246
|
+
L: 437,
|
|
1247
|
+
S: this,
|
|
1248
|
+
C: (f, a) => f(...a)
|
|
1249
|
+
});
|
|
1250
|
+
return candidateCredential.issuanceDate.getTime() > currentCredential.issuanceDate.getTime();
|
|
1251
|
+
}
|
|
1252
|
+
_createRootPath() {
|
|
1253
|
+
return {
|
|
1254
|
+
head: this._root,
|
|
1255
|
+
chosenPath: {},
|
|
1256
|
+
forkIssuers: new ComplexSet2(PublicKey5.hash),
|
|
1257
|
+
forkChangedSubjects: new ComplexSet2(PublicKey5.hash),
|
|
1258
|
+
state: new ComplexMap3(PublicKey5.hash),
|
|
1259
|
+
credentials: new ComplexSet2(PublicKey5.hash)
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
var toChosenPath = (path) => {
|
|
1264
|
+
return Object.fromEntries(Object.entries(path.chosenPath).map(([k, vs]) => [
|
|
1265
|
+
k,
|
|
1266
|
+
vs.map((v) => v.id)
|
|
1267
|
+
]));
|
|
1268
|
+
};
|
|
1269
|
+
|
|
1270
|
+
// packages/core/halo/credentials/src/state-machine/member-state-machine.ts
|
|
1271
|
+
var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
|
|
692
1272
|
var MemberStateMachine = class {
|
|
693
1273
|
constructor(_spaceKey) {
|
|
694
1274
|
this._spaceKey = _spaceKey;
|
|
695
|
-
this.
|
|
696
|
-
this.
|
|
1275
|
+
this._memberProfiles = new ComplexMap4(PublicKey6.hash);
|
|
1276
|
+
this._hashgraph = new CredentialGraph(this);
|
|
1277
|
+
this.onMemberRoleChanged = this._hashgraph.onSubjectStateChanged;
|
|
697
1278
|
}
|
|
698
1279
|
get creator() {
|
|
699
|
-
return this.
|
|
1280
|
+
return this._ownerKey && this._hashgraph.getSubjectState(this._ownerKey);
|
|
700
1281
|
}
|
|
701
1282
|
get members() {
|
|
702
|
-
return this.
|
|
1283
|
+
return this._hashgraph.getState();
|
|
1284
|
+
}
|
|
1285
|
+
get membershipChainHeads() {
|
|
1286
|
+
return this._hashgraph.getLeafIds();
|
|
703
1287
|
}
|
|
704
1288
|
getRole(member) {
|
|
705
|
-
return this.
|
|
1289
|
+
return this._getRole(this._hashgraph.getGlobalStateScope(), member);
|
|
706
1290
|
}
|
|
707
1291
|
/**
|
|
708
1292
|
* Processes the SpaceMember credential.
|
|
@@ -712,87 +1296,125 @@ var MemberStateMachine = class {
|
|
|
712
1296
|
const assertion = getCredentialAssertion(credential);
|
|
713
1297
|
switch (assertion["@type"]) {
|
|
714
1298
|
case "dxos.halo.credentials.SpaceMember": {
|
|
715
|
-
|
|
716
|
-
F:
|
|
717
|
-
L:
|
|
1299
|
+
invariant4(assertion.spaceKey.equals(this._spaceKey), void 0, {
|
|
1300
|
+
F: __dxlog_file4,
|
|
1301
|
+
L: 66,
|
|
718
1302
|
S: this,
|
|
719
1303
|
A: [
|
|
720
1304
|
"assertion.spaceKey.equals(this._spaceKey)",
|
|
721
1305
|
""
|
|
722
1306
|
]
|
|
723
1307
|
});
|
|
724
|
-
if (this.
|
|
725
|
-
|
|
1308
|
+
if (this._ownerKey == null && credential.issuer === this._spaceKey) {
|
|
1309
|
+
this._ownerKey = credential.subject.id;
|
|
726
1310
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
credential,
|
|
730
|
-
assertion,
|
|
731
|
-
removed: false,
|
|
732
|
-
profile: assertion.profile
|
|
733
|
-
};
|
|
734
|
-
if (!this._creator) {
|
|
735
|
-
this._creator = info;
|
|
1311
|
+
if (assertion.profile != null) {
|
|
1312
|
+
this._memberProfiles.set(credential.subject.id, assertion.profile);
|
|
736
1313
|
}
|
|
737
|
-
this.
|
|
738
|
-
log("member added", {
|
|
739
|
-
member: credential.subject.id,
|
|
740
|
-
space: this._spaceKey,
|
|
741
|
-
role: assertion.role,
|
|
742
|
-
profile: assertion.profile
|
|
743
|
-
}, {
|
|
744
|
-
F: __dxlog_file3,
|
|
745
|
-
L: 79,
|
|
746
|
-
S: this,
|
|
747
|
-
C: (f, a) => f(...a)
|
|
748
|
-
});
|
|
749
|
-
await this.onMemberAdmitted.callIfSet(info);
|
|
1314
|
+
await this._hashgraph.addVertex(credential, assertion);
|
|
750
1315
|
break;
|
|
751
1316
|
}
|
|
752
1317
|
case "dxos.halo.credentials.MemberProfile": {
|
|
753
|
-
const member = this.
|
|
754
|
-
if (
|
|
755
|
-
|
|
1318
|
+
const member = this._hashgraph.getSubjectState(credential.subject.id);
|
|
1319
|
+
if (member) {
|
|
1320
|
+
member.profile = assertion.profile;
|
|
1321
|
+
} else {
|
|
1322
|
+
log2.warn("Member not found", {
|
|
756
1323
|
id: credential.subject.id
|
|
757
1324
|
}, {
|
|
758
|
-
F:
|
|
759
|
-
L:
|
|
1325
|
+
F: __dxlog_file4,
|
|
1326
|
+
L: 81,
|
|
760
1327
|
S: this,
|
|
761
1328
|
C: (f, a) => f(...a)
|
|
762
1329
|
});
|
|
763
|
-
} else {
|
|
764
|
-
member.profile = assertion.profile;
|
|
765
1330
|
}
|
|
1331
|
+
this._memberProfiles.set(credential.subject.id, assertion.profile);
|
|
766
1332
|
break;
|
|
767
1333
|
}
|
|
768
1334
|
default:
|
|
769
1335
|
throw new Error("Invalid assertion type");
|
|
770
1336
|
}
|
|
771
1337
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1338
|
+
createState(credential, assertion) {
|
|
1339
|
+
const memberKey = credential.subject.id;
|
|
1340
|
+
return {
|
|
1341
|
+
key: memberKey,
|
|
1342
|
+
role: assertion.role,
|
|
1343
|
+
credential,
|
|
1344
|
+
assertion,
|
|
1345
|
+
profile: this._memberProfiles.get(memberKey)
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
isUpdateAllowed(scope, credential, assertion) {
|
|
1349
|
+
if (assertion.role === SpaceMember2.Role.OWNER) {
|
|
1350
|
+
return credential.issuer.equals(this._spaceKey);
|
|
1351
|
+
}
|
|
1352
|
+
const issuer = credential.issuer;
|
|
1353
|
+
const isChangingOwnRole = issuer.equals(credential.subject.id);
|
|
1354
|
+
if (isChangingOwnRole) {
|
|
1355
|
+
return false;
|
|
1356
|
+
}
|
|
1357
|
+
if (issuer.equals(assertion.spaceKey)) {
|
|
1358
|
+
return true;
|
|
1359
|
+
}
|
|
1360
|
+
const issuerRole = this._getRole(scope, issuer);
|
|
1361
|
+
return issuerRole === SpaceMember2.Role.ADMIN || issuerRole === SpaceMember2.Role.OWNER;
|
|
1362
|
+
}
|
|
1363
|
+
getConflictingPaths(paths, update) {
|
|
1364
|
+
if (update.assertion.role !== SpaceMember2.Role.REMOVED && update.assertion.role !== SpaceMember2.Role.EDITOR) {
|
|
1365
|
+
return [];
|
|
1366
|
+
}
|
|
1367
|
+
const memberId = update.credential.subject.id;
|
|
1368
|
+
return paths.filter((p) => p.forkIssuers.has(memberId));
|
|
1369
|
+
}
|
|
1370
|
+
tryPickWinningUpdate(scope1, update1, scope2, update2) {
|
|
1371
|
+
const path1IssuerRole = this._getRole(scope1, update1.issuer);
|
|
1372
|
+
const path2IssuerRole = this._getRole(scope2, update2.issuer);
|
|
1373
|
+
if (path2IssuerRole === SpaceMember2.Role.OWNER !== (path1IssuerRole === SpaceMember2.Role.OWNER)) {
|
|
1374
|
+
log2("owner decision used to break the tie", void 0, {
|
|
1375
|
+
F: __dxlog_file4,
|
|
1376
|
+
L: 139,
|
|
1377
|
+
S: this,
|
|
1378
|
+
C: (f, a) => f(...a)
|
|
1379
|
+
});
|
|
1380
|
+
return path1IssuerRole === SpaceMember2.Role.OWNER ? update1 : update2;
|
|
1381
|
+
}
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
toLogString(assertion) {
|
|
1385
|
+
const role = assertion?.role ?? SpaceMember2.Role.REMOVED;
|
|
1386
|
+
return Object.entries(SpaceMember2.Role).find(([_, value]) => value === role)[0];
|
|
1387
|
+
}
|
|
1388
|
+
hasStateChanged(s1, s2) {
|
|
1389
|
+
return s1?.role !== s2?.role;
|
|
1390
|
+
}
|
|
1391
|
+
_getRole(scope, memberId) {
|
|
1392
|
+
if (this._ownerKey?.equals(memberId)) {
|
|
1393
|
+
return SpaceMember2.Role.OWNER;
|
|
1394
|
+
}
|
|
1395
|
+
const realRole = scope.state.get(memberId)?.assertion?.role ?? SpaceMember2.Role.REMOVED;
|
|
1396
|
+
if (scope.stateOverrides != null) {
|
|
1397
|
+
const override = scope.stateOverrides.get(memberId);
|
|
1398
|
+
if (override != null) {
|
|
1399
|
+
log2("member role overridden in path", () => ({
|
|
1400
|
+
headId: scope.head?.id,
|
|
1401
|
+
roleOverride: this.toLogString(override.assertion),
|
|
1402
|
+
realRole: this.toLogString(scope.state.get(memberId)?.assertion)
|
|
1403
|
+
}), {
|
|
1404
|
+
F: __dxlog_file4,
|
|
1405
|
+
L: 162,
|
|
1406
|
+
S: this,
|
|
1407
|
+
C: (f, a) => f(...a)
|
|
1408
|
+
});
|
|
1409
|
+
return override.assertion.role;
|
|
786
1410
|
}
|
|
787
1411
|
}
|
|
1412
|
+
return realRole;
|
|
788
1413
|
}
|
|
789
1414
|
};
|
|
790
1415
|
|
|
791
1416
|
// packages/core/halo/credentials/src/state-machine/space-state-machine.ts
|
|
792
|
-
var
|
|
793
|
-
var REVOCABLE_CREDENTIALS = [
|
|
794
|
-
"dxos.halo.credentials.SpaceMember"
|
|
795
|
-
];
|
|
1417
|
+
var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
|
|
796
1418
|
var SpaceStateMachine = class {
|
|
797
1419
|
constructor(_spaceKey) {
|
|
798
1420
|
this._spaceKey = _spaceKey;
|
|
@@ -800,11 +1422,11 @@ var SpaceStateMachine = class {
|
|
|
800
1422
|
this._feeds = new FeedStateMachine(this._spaceKey);
|
|
801
1423
|
this._invitations = new InvitationStateMachine();
|
|
802
1424
|
this._credentials = [];
|
|
803
|
-
this._credentialsById = new
|
|
804
|
-
this._processedCredentials = new
|
|
1425
|
+
this._credentialsById = new ComplexMap5(PublicKey7.hash);
|
|
1426
|
+
this._processedCredentials = new ComplexSet3(PublicKey7.hash);
|
|
805
1427
|
this._credentialProcessors = [];
|
|
806
1428
|
this.onCredentialProcessed = new Callback4();
|
|
807
|
-
this.
|
|
1429
|
+
this.onMemberRoleChanged = this._members.onMemberRoleChanged;
|
|
808
1430
|
this.onFeedAdmitted = this._feeds.onFeedAdmitted;
|
|
809
1431
|
this.onDelegatedInvitation = this._invitations.onDelegatedInvitation;
|
|
810
1432
|
this.onDelegatedInvitationRemoved = this._invitations.onDelegatedInvitationRemoved;
|
|
@@ -815,6 +1437,9 @@ var SpaceStateMachine = class {
|
|
|
815
1437
|
get members() {
|
|
816
1438
|
return this._members.members;
|
|
817
1439
|
}
|
|
1440
|
+
get membershipChainHeads() {
|
|
1441
|
+
return this._members.membershipChainHeads;
|
|
1442
|
+
}
|
|
818
1443
|
get feeds() {
|
|
819
1444
|
return this._feeds.feeds;
|
|
820
1445
|
}
|
|
@@ -866,9 +1491,9 @@ var SpaceStateMachine = class {
|
|
|
866
1491
|
if (!skipVerification) {
|
|
867
1492
|
const result = await verifyCredential(credential);
|
|
868
1493
|
if (result.kind !== "pass") {
|
|
869
|
-
|
|
870
|
-
F:
|
|
871
|
-
L:
|
|
1494
|
+
log3.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
|
|
1495
|
+
F: __dxlog_file5,
|
|
1496
|
+
L: 152,
|
|
872
1497
|
S: this,
|
|
873
1498
|
C: (f, a) => f(...a)
|
|
874
1499
|
});
|
|
@@ -877,61 +1502,29 @@ var SpaceStateMachine = class {
|
|
|
877
1502
|
}
|
|
878
1503
|
const assertion = getCredentialAssertion(credential);
|
|
879
1504
|
switch (assertion["@type"]) {
|
|
880
|
-
case "dxos.halo.credentials.Revocation": {
|
|
881
|
-
if (!this._canRevokeCredentials(credential.issuer)) {
|
|
882
|
-
log2.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
883
|
-
F: __dxlog_file4,
|
|
884
|
-
L: 158,
|
|
885
|
-
S: this,
|
|
886
|
-
C: (f, a) => f(...a)
|
|
887
|
-
});
|
|
888
|
-
return false;
|
|
889
|
-
}
|
|
890
|
-
const toBeRevoked = this._credentialsById.get(credential.subject.id);
|
|
891
|
-
if (!toBeRevoked) {
|
|
892
|
-
log2.warn(`Credential to revoke not found: ${credential.subject.id}`, void 0, {
|
|
893
|
-
F: __dxlog_file4,
|
|
894
|
-
L: 163,
|
|
895
|
-
S: this,
|
|
896
|
-
C: (f, a) => f(...a)
|
|
897
|
-
});
|
|
898
|
-
return false;
|
|
899
|
-
}
|
|
900
|
-
if (!this._credentialCanBeRevoked(toBeRevoked)) {
|
|
901
|
-
log2.warn(`Credential cannot be revoked: ${credential.subject.id}`, void 0, {
|
|
902
|
-
F: __dxlog_file4,
|
|
903
|
-
L: 167,
|
|
904
|
-
S: this,
|
|
905
|
-
C: (f, a) => f(...a)
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
toBeRevoked.revoked = true;
|
|
909
|
-
await this._members.onRevoked(toBeRevoked.credential, credential);
|
|
910
|
-
break;
|
|
911
|
-
}
|
|
912
1505
|
case "dxos.halo.credentials.SpaceGenesis": {
|
|
913
1506
|
if (this._genesisCredential) {
|
|
914
|
-
|
|
915
|
-
F:
|
|
916
|
-
L:
|
|
1507
|
+
log3.warn("Space already has a genesis credential.", void 0, {
|
|
1508
|
+
F: __dxlog_file5,
|
|
1509
|
+
L: 161,
|
|
917
1510
|
S: this,
|
|
918
1511
|
C: (f, a) => f(...a)
|
|
919
1512
|
});
|
|
920
1513
|
return false;
|
|
921
1514
|
}
|
|
922
1515
|
if (!credential.issuer.equals(this._spaceKey)) {
|
|
923
|
-
|
|
924
|
-
F:
|
|
925
|
-
L:
|
|
1516
|
+
log3.warn("Space genesis credential must be issued by space.", void 0, {
|
|
1517
|
+
F: __dxlog_file5,
|
|
1518
|
+
L: 165,
|
|
926
1519
|
S: this,
|
|
927
1520
|
C: (f, a) => f(...a)
|
|
928
1521
|
});
|
|
929
1522
|
return false;
|
|
930
1523
|
}
|
|
931
1524
|
if (!credential.subject.id.equals(this._spaceKey)) {
|
|
932
|
-
|
|
933
|
-
F:
|
|
934
|
-
L:
|
|
1525
|
+
log3.warn("Space genesis credential must be issued to space.", void 0, {
|
|
1526
|
+
F: __dxlog_file5,
|
|
1527
|
+
L: 169,
|
|
935
1528
|
S: this,
|
|
936
1529
|
C: (f, a) => f(...a)
|
|
937
1530
|
});
|
|
@@ -945,18 +1538,18 @@ var SpaceStateMachine = class {
|
|
|
945
1538
|
break;
|
|
946
1539
|
}
|
|
947
1540
|
if (!this._genesisCredential) {
|
|
948
|
-
|
|
949
|
-
F:
|
|
950
|
-
L:
|
|
1541
|
+
log3.warn("Space must have a genesis credential before adding members.", void 0, {
|
|
1542
|
+
F: __dxlog_file5,
|
|
1543
|
+
L: 182,
|
|
951
1544
|
S: this,
|
|
952
1545
|
C: (f, a) => f(...a)
|
|
953
1546
|
});
|
|
954
1547
|
return false;
|
|
955
1548
|
}
|
|
956
1549
|
if (!this._canInviteNewMembers(credential.issuer)) {
|
|
957
|
-
|
|
958
|
-
F:
|
|
959
|
-
L:
|
|
1550
|
+
log3.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
1551
|
+
F: __dxlog_file5,
|
|
1552
|
+
L: 186,
|
|
960
1553
|
S: this,
|
|
961
1554
|
C: (f, a) => f(...a)
|
|
962
1555
|
});
|
|
@@ -968,9 +1561,9 @@ var SpaceStateMachine = class {
|
|
|
968
1561
|
}
|
|
969
1562
|
case "dxos.halo.credentials.MemberProfile": {
|
|
970
1563
|
if (!this._genesisCredential) {
|
|
971
|
-
|
|
972
|
-
F:
|
|
973
|
-
L:
|
|
1564
|
+
log3.warn("Space must have a genesis credential before adding members.", void 0, {
|
|
1565
|
+
F: __dxlog_file5,
|
|
1566
|
+
L: 197,
|
|
974
1567
|
S: this,
|
|
975
1568
|
C: (f, a) => f(...a)
|
|
976
1569
|
});
|
|
@@ -981,18 +1574,18 @@ var SpaceStateMachine = class {
|
|
|
981
1574
|
}
|
|
982
1575
|
case "dxos.halo.credentials.AdmittedFeed": {
|
|
983
1576
|
if (!this._genesisCredential) {
|
|
984
|
-
|
|
985
|
-
F:
|
|
986
|
-
L:
|
|
1577
|
+
log3.warn("Space must have a genesis credential before admitting feeds.", void 0, {
|
|
1578
|
+
F: __dxlog_file5,
|
|
1579
|
+
L: 207,
|
|
987
1580
|
S: this,
|
|
988
1581
|
C: (f, a) => f(...a)
|
|
989
1582
|
});
|
|
990
1583
|
return false;
|
|
991
1584
|
}
|
|
992
1585
|
if (!this._canAdmitFeeds(credential.issuer)) {
|
|
993
|
-
|
|
994
|
-
F:
|
|
995
|
-
L:
|
|
1586
|
+
log3.warn(`Space member is not authorized to admit feeds: ${credential.issuer}`, void 0, {
|
|
1587
|
+
F: __dxlog_file5,
|
|
1588
|
+
L: 211,
|
|
996
1589
|
S: this,
|
|
997
1590
|
C: (f, a) => f(...a)
|
|
998
1591
|
});
|
|
@@ -1004,9 +1597,9 @@ var SpaceStateMachine = class {
|
|
|
1004
1597
|
case "dxos.halo.invitations.CancelDelegatedInvitation":
|
|
1005
1598
|
case "dxos.halo.invitations.DelegateSpaceInvitation": {
|
|
1006
1599
|
if (!this._canInviteNewMembers(credential.issuer)) {
|
|
1007
|
-
|
|
1008
|
-
F:
|
|
1009
|
-
L:
|
|
1600
|
+
log3.warn(`Invalid invitation, space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
1601
|
+
F: __dxlog_file5,
|
|
1602
|
+
L: 222,
|
|
1010
1603
|
S: this,
|
|
1011
1604
|
C: (f, a) => f(...a)
|
|
1012
1605
|
});
|
|
@@ -1034,17 +1627,11 @@ var SpaceStateMachine = class {
|
|
|
1034
1627
|
return true;
|
|
1035
1628
|
}
|
|
1036
1629
|
_canInviteNewMembers(key) {
|
|
1037
|
-
return key.equals(this._spaceKey) || this._members.getRole(key) ===
|
|
1630
|
+
return key.equals(this._spaceKey) || this._members.getRole(key) === SpaceMember3.Role.ADMIN || this._members.getRole(key) === SpaceMember3.Role.OWNER;
|
|
1038
1631
|
}
|
|
1039
1632
|
_canAdmitFeeds(key) {
|
|
1040
1633
|
const role = this._members.getRole(key);
|
|
1041
|
-
return role ===
|
|
1042
|
-
}
|
|
1043
|
-
_canRevokeCredentials(key) {
|
|
1044
|
-
return key.equals(this._spaceKey) || this._members.getRole(key) === SpaceMember2.Role.ADMIN;
|
|
1045
|
-
}
|
|
1046
|
-
_credentialCanBeRevoked(entry) {
|
|
1047
|
-
return REVOCABLE_CREDENTIALS.includes(entry.credential.subject.assertion["@type"]);
|
|
1634
|
+
return role === SpaceMember3.Role.EDITOR || role === SpaceMember3.Role.ADMIN || role === SpaceMember3.Role.OWNER;
|
|
1048
1635
|
}
|
|
1049
1636
|
};
|
|
1050
1637
|
var CredentialConsumer = class {
|
|
@@ -1077,24 +1664,24 @@ var CredentialConsumer = class {
|
|
|
1077
1664
|
|
|
1078
1665
|
// packages/core/halo/credentials/src/processor/device-state-machine.ts
|
|
1079
1666
|
import { Trigger } from "@dxos/async";
|
|
1080
|
-
import { invariant as
|
|
1081
|
-
import { PublicKey as
|
|
1082
|
-
import { log as
|
|
1083
|
-
import { ComplexMap as
|
|
1084
|
-
var
|
|
1667
|
+
import { invariant as invariant5 } from "@dxos/invariant";
|
|
1668
|
+
import { PublicKey as PublicKey8 } from "@dxos/keys";
|
|
1669
|
+
import { log as log4 } from "@dxos/log";
|
|
1670
|
+
import { ComplexMap as ComplexMap6 } from "@dxos/util";
|
|
1671
|
+
var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
|
|
1085
1672
|
var DeviceStateMachine = class {
|
|
1086
1673
|
constructor(_params) {
|
|
1087
1674
|
this._params = _params;
|
|
1088
|
-
this.authorizedDeviceKeys = new
|
|
1675
|
+
this.authorizedDeviceKeys = new ComplexMap6(PublicKey8.hash);
|
|
1089
1676
|
this.deviceChainReady = new Trigger();
|
|
1090
1677
|
}
|
|
1091
1678
|
async processCredential(credential) {
|
|
1092
|
-
|
|
1679
|
+
log4("processing credential...", {
|
|
1093
1680
|
identityKey: this._params.identityKey,
|
|
1094
1681
|
deviceKey: this._params.deviceKey,
|
|
1095
1682
|
credential
|
|
1096
1683
|
}, {
|
|
1097
|
-
F:
|
|
1684
|
+
F: __dxlog_file6,
|
|
1098
1685
|
L: 35,
|
|
1099
1686
|
S: this,
|
|
1100
1687
|
C: (f, a) => f(...a)
|
|
@@ -1108,8 +1695,8 @@ var DeviceStateMachine = class {
|
|
|
1108
1695
|
const assertion = getCredentialAssertion(credential);
|
|
1109
1696
|
switch (assertion["@type"]) {
|
|
1110
1697
|
case "dxos.halo.credentials.AuthorizedDevice": {
|
|
1111
|
-
|
|
1112
|
-
F:
|
|
1698
|
+
invariant5(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
|
|
1699
|
+
F: __dxlog_file6,
|
|
1113
1700
|
L: 51,
|
|
1114
1701
|
S: this,
|
|
1115
1702
|
A: [
|
|
@@ -1118,12 +1705,12 @@ var DeviceStateMachine = class {
|
|
|
1118
1705
|
]
|
|
1119
1706
|
});
|
|
1120
1707
|
this.authorizedDeviceKeys.set(assertion.deviceKey, {});
|
|
1121
|
-
|
|
1708
|
+
log4("added device", {
|
|
1122
1709
|
localDeviceKey: this._params.deviceKey,
|
|
1123
1710
|
deviceKey: assertion.deviceKey,
|
|
1124
1711
|
size: this.authorizedDeviceKeys.size
|
|
1125
1712
|
}, {
|
|
1126
|
-
F:
|
|
1713
|
+
F: __dxlog_file6,
|
|
1127
1714
|
L: 54,
|
|
1128
1715
|
S: this,
|
|
1129
1716
|
C: (f, a) => f(...a)
|
|
@@ -1132,8 +1719,8 @@ var DeviceStateMachine = class {
|
|
|
1132
1719
|
break;
|
|
1133
1720
|
}
|
|
1134
1721
|
case "dxos.halo.credentials.DeviceProfile": {
|
|
1135
|
-
|
|
1136
|
-
F:
|
|
1722
|
+
invariant5(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
|
|
1723
|
+
F: __dxlog_file6,
|
|
1137
1724
|
L: 64,
|
|
1138
1725
|
S: this,
|
|
1139
1726
|
A: [
|
|
@@ -1142,11 +1729,11 @@ var DeviceStateMachine = class {
|
|
|
1142
1729
|
]
|
|
1143
1730
|
});
|
|
1144
1731
|
if (assertion && credential.subject.id.equals(this._params.deviceKey)) {
|
|
1145
|
-
|
|
1732
|
+
log4.trace("dxos.halo.device", {
|
|
1146
1733
|
deviceKey: credential.subject.id,
|
|
1147
1734
|
profile: assertion.profile
|
|
1148
1735
|
}, {
|
|
1149
|
-
F:
|
|
1736
|
+
F: __dxlog_file6,
|
|
1150
1737
|
L: 67,
|
|
1151
1738
|
S: this,
|
|
1152
1739
|
C: (f, a) => f(...a)
|
|
@@ -1161,8 +1748,8 @@ var DeviceStateMachine = class {
|
|
|
1161
1748
|
};
|
|
1162
1749
|
|
|
1163
1750
|
// packages/core/halo/credentials/src/processor/profile-state-machine.ts
|
|
1164
|
-
import { log as
|
|
1165
|
-
var
|
|
1751
|
+
import { log as log5 } from "@dxos/log";
|
|
1752
|
+
var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
|
|
1166
1753
|
var ProfileStateMachine = class {
|
|
1167
1754
|
constructor(_params) {
|
|
1168
1755
|
this._params = _params;
|
|
@@ -1172,11 +1759,11 @@ var ProfileStateMachine = class {
|
|
|
1172
1759
|
switch (assertion["@type"]) {
|
|
1173
1760
|
case "dxos.halo.credentials.IdentityProfile": {
|
|
1174
1761
|
if (!credential.issuer.equals(this._params.identityKey) || !credential.subject.id.equals(this._params.identityKey)) {
|
|
1175
|
-
|
|
1762
|
+
log5.warn("Invalid profile credential", {
|
|
1176
1763
|
expectedIdentity: this._params.identityKey,
|
|
1177
1764
|
credential
|
|
1178
1765
|
}, {
|
|
1179
|
-
F:
|
|
1766
|
+
F: __dxlog_file7,
|
|
1180
1767
|
L: 34,
|
|
1181
1768
|
S: this,
|
|
1182
1769
|
C: (f, a) => f(...a)
|
|
@@ -1184,11 +1771,11 @@ var ProfileStateMachine = class {
|
|
|
1184
1771
|
return;
|
|
1185
1772
|
}
|
|
1186
1773
|
this.profile = assertion.profile;
|
|
1187
|
-
|
|
1774
|
+
log5("updated profile", {
|
|
1188
1775
|
identityKey: this._params.identityKey,
|
|
1189
1776
|
profile: this.profile
|
|
1190
1777
|
}, {
|
|
1191
|
-
F:
|
|
1778
|
+
F: __dxlog_file7,
|
|
1192
1779
|
L: 40,
|
|
1193
1780
|
S: this,
|
|
1194
1781
|
C: (f, a) => f(...a)
|
|
@@ -1211,6 +1798,7 @@ export {
|
|
|
1211
1798
|
canonicalStringify,
|
|
1212
1799
|
checkCredentialType,
|
|
1213
1800
|
createAdmissionCredentials,
|
|
1801
|
+
createCancelDelegatedSpaceInvitationCredential,
|
|
1214
1802
|
createCredential,
|
|
1215
1803
|
createCredentialMessage,
|
|
1216
1804
|
createCredentialSignerWithChain,
|