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