@dxos/credentials 0.5.1-next.f040be1 → 0.5.2-main.80497e2
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 +740 -167
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +713 -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 +12 -2
- package/dist/types/src/credentials/credential-generator.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/src/credentials/credential-factory.ts +9 -1
- package/src/credentials/credential-generator.ts +15 -2
- 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
package/dist/lib/node/index.cjs
CHANGED
|
@@ -88,13 +88,18 @@ var import_util4 = require("@dxos/util");
|
|
|
88
88
|
var import_invariant3 = require("@dxos/invariant");
|
|
89
89
|
var import_keys6 = require("@dxos/keys");
|
|
90
90
|
var import_log2 = require("@dxos/log");
|
|
91
|
+
var import_credentials3 = require("@dxos/protocols/proto/dxos/halo/credentials");
|
|
91
92
|
var import_util5 = require("@dxos/util");
|
|
92
|
-
var import_async2 = require("@dxos/async");
|
|
93
93
|
var import_invariant4 = require("@dxos/invariant");
|
|
94
94
|
var import_keys7 = require("@dxos/keys");
|
|
95
95
|
var import_log3 = require("@dxos/log");
|
|
96
96
|
var import_util6 = require("@dxos/util");
|
|
97
|
+
var import_async2 = require("@dxos/async");
|
|
98
|
+
var import_invariant5 = require("@dxos/invariant");
|
|
99
|
+
var import_keys8 = require("@dxos/keys");
|
|
97
100
|
var import_log4 = require("@dxos/log");
|
|
101
|
+
var import_util7 = require("@dxos/util");
|
|
102
|
+
var import_log5 = require("@dxos/log");
|
|
98
103
|
var defaultSecretProvider = async () => Buffer.from("0000");
|
|
99
104
|
var defaultSecretValidator = async (invitation, secret) => true;
|
|
100
105
|
var generatePasscode = (length = 4) => {
|
|
@@ -179,6 +184,9 @@ var checkCredentialType = (credential, type) => credential.subject.assertion["@t
|
|
|
179
184
|
var credentialTypeFilter = (type) => (credential) => checkCredentialType(credential, type);
|
|
180
185
|
var SIGNATURE_TYPE_ED25519 = "ED25519Signature";
|
|
181
186
|
var verifyCredential = async (credential) => {
|
|
187
|
+
if (credential.parentCredentialIds?.length === 0) {
|
|
188
|
+
delete credential.parentCredentialIds;
|
|
189
|
+
}
|
|
182
190
|
if (!credential.issuer.equals(credential.proof.signer)) {
|
|
183
191
|
if (!credential.proof.chain) {
|
|
184
192
|
return {
|
|
@@ -241,10 +249,10 @@ var verifyChain = async (chain, authority, subject) => {
|
|
|
241
249
|
};
|
|
242
250
|
};
|
|
243
251
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/credentials/credential-factory.ts";
|
|
244
|
-
var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce }) => {
|
|
252
|
+
var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce, parentCredentialIds }) => {
|
|
245
253
|
(0, import_invariant.invariant)(assertion["@type"], "Invalid assertion.", {
|
|
246
254
|
F: __dxlog_file,
|
|
247
|
-
L:
|
|
255
|
+
L: 48,
|
|
248
256
|
S: void 0,
|
|
249
257
|
A: [
|
|
250
258
|
"assertion['@type']",
|
|
@@ -253,7 +261,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
253
261
|
});
|
|
254
262
|
(0, import_invariant.invariant)(!!signingKey === !!chain, "Chain must be provided if and only if the signing key differs from the issuer.", {
|
|
255
263
|
F: __dxlog_file,
|
|
256
|
-
L:
|
|
264
|
+
L: 49,
|
|
257
265
|
S: void 0,
|
|
258
266
|
A: [
|
|
259
267
|
"!!signingKey === !!chain",
|
|
@@ -264,7 +272,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
264
272
|
const result = await verifyChain(chain, issuer, signingKey);
|
|
265
273
|
(0, import_invariant.invariant)(result.kind === "pass", "Invalid chain.", {
|
|
266
274
|
F: __dxlog_file,
|
|
267
|
-
L:
|
|
275
|
+
L: 52,
|
|
268
276
|
S: void 0,
|
|
269
277
|
A: [
|
|
270
278
|
"result.kind === 'pass'",
|
|
@@ -287,6 +295,9 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
287
295
|
nonce
|
|
288
296
|
}
|
|
289
297
|
};
|
|
298
|
+
if ((parentCredentialIds?.length ?? 0) > 0) {
|
|
299
|
+
credential.parentCredentialIds = parentCredentialIds;
|
|
300
|
+
}
|
|
290
301
|
const signedPayload = getCredentialProofPayload(credential);
|
|
291
302
|
credential.proof.value = await signer.sign(signingKey ?? issuer, signedPayload);
|
|
292
303
|
if (chain) {
|
|
@@ -303,12 +314,13 @@ var createCredentialMessage = (credential) => {
|
|
|
303
314
|
};
|
|
304
315
|
var createCredentialSignerWithKey = (signer, issuer) => ({
|
|
305
316
|
getIssuer: () => issuer,
|
|
306
|
-
createCredential: ({ subject, assertion, nonce }) => createCredential({
|
|
317
|
+
createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
|
|
307
318
|
signer,
|
|
308
319
|
issuer,
|
|
309
320
|
subject,
|
|
310
321
|
assertion,
|
|
311
|
-
nonce
|
|
322
|
+
nonce,
|
|
323
|
+
parentCredentialIds
|
|
312
324
|
})
|
|
313
325
|
});
|
|
314
326
|
var createCredentialSignerWithChain = (signer, chain, signingKey) => ({
|
|
@@ -372,7 +384,7 @@ var CredentialGenerator = class {
|
|
|
372
384
|
assertion: {
|
|
373
385
|
"@type": "dxos.halo.credentials.SpaceMember",
|
|
374
386
|
spaceKey,
|
|
375
|
-
role: import_credentials.SpaceMember.Role.
|
|
387
|
+
role: import_credentials.SpaceMember.Role.EDITOR,
|
|
376
388
|
genesisFeedKey
|
|
377
389
|
}
|
|
378
390
|
}),
|
|
@@ -467,14 +479,15 @@ var createDeviceAuthorization = async (signer, identityKey, deviceKey) => {
|
|
|
467
479
|
credential
|
|
468
480
|
}));
|
|
469
481
|
};
|
|
470
|
-
var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, profile, invitationCredentialId) => {
|
|
482
|
+
var createAdmissionCredentials = async (signer, identityKey, spaceKey, genesisFeedKey, role = import_credentials.SpaceMember.Role.ADMIN, membershipChainHeads = [], profile, invitationCredentialId) => {
|
|
471
483
|
const credentials = await Promise.all([
|
|
472
484
|
await signer.createCredential({
|
|
473
485
|
subject: identityKey,
|
|
486
|
+
parentCredentialIds: membershipChainHeads,
|
|
474
487
|
assertion: {
|
|
475
488
|
"@type": "dxos.halo.credentials.SpaceMember",
|
|
476
489
|
spaceKey,
|
|
477
|
-
role
|
|
490
|
+
role,
|
|
478
491
|
profile,
|
|
479
492
|
genesisFeedKey,
|
|
480
493
|
invitationCredentialId
|
|
@@ -735,21 +748,574 @@ var InvitationStateMachine = class {
|
|
|
735
748
|
}
|
|
736
749
|
}
|
|
737
750
|
};
|
|
738
|
-
var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/
|
|
751
|
+
var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/graph/credential-graph.ts";
|
|
752
|
+
var CredentialGraph = class {
|
|
753
|
+
constructor(_stateHandler) {
|
|
754
|
+
this._stateHandler = _stateHandler;
|
|
755
|
+
this._vertexIdGenerator = 1;
|
|
756
|
+
this._root = {
|
|
757
|
+
id: -1,
|
|
758
|
+
parents: [],
|
|
759
|
+
children: []
|
|
760
|
+
};
|
|
761
|
+
this._sentinel = {
|
|
762
|
+
id: -2,
|
|
763
|
+
parents: [],
|
|
764
|
+
children: []
|
|
765
|
+
};
|
|
766
|
+
this._vertexByCredentialId = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
|
|
767
|
+
this._subjectToVertex = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
|
|
768
|
+
this._subjectToState = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
|
|
769
|
+
this.onSubjectStateChanged = new import_util6.Callback();
|
|
770
|
+
}
|
|
771
|
+
getSubjectState(subjectId) {
|
|
772
|
+
return this._subjectToState.get(subjectId);
|
|
773
|
+
}
|
|
774
|
+
getState() {
|
|
775
|
+
return this._subjectToState;
|
|
776
|
+
}
|
|
777
|
+
getLeafIds() {
|
|
778
|
+
return this._sentinel.parents.map((v) => v.credential.id);
|
|
779
|
+
}
|
|
780
|
+
getGlobalStateScope() {
|
|
781
|
+
return {
|
|
782
|
+
state: this._subjectToVertex
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
addVertex(credential, assertion) {
|
|
786
|
+
const newVertex = {
|
|
787
|
+
id: this._vertexIdGenerator++,
|
|
788
|
+
credential,
|
|
789
|
+
assertion,
|
|
790
|
+
parents: [],
|
|
791
|
+
children: []
|
|
792
|
+
};
|
|
793
|
+
this._vertexByCredentialId.set(credential.id, newVertex);
|
|
794
|
+
const parentIds = credential.parentCredentialIds ?? [];
|
|
795
|
+
if (parentIds.length === 0) {
|
|
796
|
+
this._root.children.push(newVertex);
|
|
797
|
+
newVertex.parents.push(this._root);
|
|
798
|
+
} else {
|
|
799
|
+
for (const parentId of parentIds) {
|
|
800
|
+
const parentVertex = this._vertexByCredentialId.get(parentId);
|
|
801
|
+
if (parentVertex == null) {
|
|
802
|
+
import_log3.log.error("credential skipped because of the unknown parent", {
|
|
803
|
+
credential,
|
|
804
|
+
parentId
|
|
805
|
+
}, {
|
|
806
|
+
F: __dxlog_file3,
|
|
807
|
+
L: 72,
|
|
808
|
+
S: this,
|
|
809
|
+
C: (f, a) => f(...a)
|
|
810
|
+
});
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
parentVertex.children.push(newVertex);
|
|
814
|
+
newVertex.parents.push(parentVertex);
|
|
815
|
+
this._removeSentinelConnection(parentVertex);
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
newVertex.children.push(this._sentinel);
|
|
819
|
+
this._sentinel.parents.push(newVertex);
|
|
820
|
+
return this._onVertexInserted(newVertex);
|
|
821
|
+
}
|
|
822
|
+
_removeSentinelConnection(vertex) {
|
|
823
|
+
const sentinelIdx = vertex.children.indexOf(this._sentinel);
|
|
824
|
+
if (sentinelIdx >= 0) {
|
|
825
|
+
vertex.children.splice(sentinelIdx, 1);
|
|
826
|
+
const vertexInSentinelIdx = this._sentinel.parents.indexOf(vertex);
|
|
827
|
+
(0, import_invariant4.invariant)(vertexInSentinelIdx >= 0, void 0, {
|
|
828
|
+
F: __dxlog_file3,
|
|
829
|
+
L: 90,
|
|
830
|
+
S: this,
|
|
831
|
+
A: [
|
|
832
|
+
"vertexInSentinelIdx >= 0",
|
|
833
|
+
""
|
|
834
|
+
]
|
|
835
|
+
});
|
|
836
|
+
this._sentinel.parents.splice(vertexInSentinelIdx, 1);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
async _onVertexInserted(newVertex) {
|
|
840
|
+
const { credential, assertion } = newVertex;
|
|
841
|
+
(0, import_invariant4.invariant)(credential, void 0, {
|
|
842
|
+
F: __dxlog_file3,
|
|
843
|
+
L: 97,
|
|
844
|
+
S: this,
|
|
845
|
+
A: [
|
|
846
|
+
"credential",
|
|
847
|
+
""
|
|
848
|
+
]
|
|
849
|
+
});
|
|
850
|
+
let changedSubjects = [];
|
|
851
|
+
const isUpdateAppliedOnTopOfThePreviousState = this._sentinel.parents.length === 1;
|
|
852
|
+
if (isUpdateAppliedOnTopOfThePreviousState) {
|
|
853
|
+
const subjectId = credential.subject.id;
|
|
854
|
+
if (this._stateHandler.isUpdateAllowed(this.getGlobalStateScope(), credential, assertion)) {
|
|
855
|
+
const newSubjectState = this._stateHandler.createState(credential, newVertex.assertion);
|
|
856
|
+
const prevSubjectState = this._subjectToState.get(subjectId);
|
|
857
|
+
this._subjectToState.set(subjectId, newSubjectState);
|
|
858
|
+
this._subjectToVertex.set(subjectId, newVertex);
|
|
859
|
+
if (this._stateHandler.hasStateChanged(newSubjectState, prevSubjectState)) {
|
|
860
|
+
changedSubjects.push(newSubjectState);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
} else {
|
|
864
|
+
changedSubjects = this._recomputeState();
|
|
865
|
+
}
|
|
866
|
+
if (changedSubjects.length > 0) {
|
|
867
|
+
await this.onSubjectStateChanged.callIfSet(changedSubjects);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* DFS the graph from root to sentinel pausing on merge points (nodes with multiple parents).
|
|
872
|
+
* Continue after all paths leading to a merge point converge by merging their states.
|
|
873
|
+
* In case of a concurrent update paths are replayed taking into account the state set
|
|
874
|
+
* by the winning branch.
|
|
875
|
+
*/
|
|
876
|
+
_recomputeState() {
|
|
877
|
+
const pendingPaths = /* @__PURE__ */ new Map();
|
|
878
|
+
const paths = [
|
|
879
|
+
this._createRootPath()
|
|
880
|
+
];
|
|
881
|
+
let lastPath = null;
|
|
882
|
+
while (lastPath == null) {
|
|
883
|
+
const path = paths.pop();
|
|
884
|
+
(0, import_log3.log)("visit vertex", {
|
|
885
|
+
id: path.head.id
|
|
886
|
+
}, {
|
|
887
|
+
F: __dxlog_file3,
|
|
888
|
+
L: 132,
|
|
889
|
+
S: this,
|
|
890
|
+
C: (f, a) => f(...a)
|
|
891
|
+
});
|
|
892
|
+
this._updatePathState(path);
|
|
893
|
+
const convergedPaths = this._handleMergePoint(paths, pendingPaths, path);
|
|
894
|
+
if (convergedPaths == null) {
|
|
895
|
+
(0, import_log3.log)("waiting for other paths", void 0, {
|
|
896
|
+
F: __dxlog_file3,
|
|
897
|
+
L: 136,
|
|
898
|
+
S: this,
|
|
899
|
+
C: (f, a) => f(...a)
|
|
900
|
+
});
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
const mergeResult = this._mergePaths(convergedPaths);
|
|
904
|
+
if (mergeResult.type === "replay_required") {
|
|
905
|
+
this._replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths);
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
const merged = mergeResult.path;
|
|
909
|
+
if (merged.head.children.length === 0) {
|
|
910
|
+
lastPath = merged;
|
|
911
|
+
} else if (merged.head.children.length === 1) {
|
|
912
|
+
merged.head = merged.head.children[0];
|
|
913
|
+
paths.push(merged);
|
|
914
|
+
} else {
|
|
915
|
+
this._forkTraversal(paths, merged);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
if (paths.length > 0) {
|
|
919
|
+
import_log3.log.error("traversal finished while there were active paths", {
|
|
920
|
+
paths: paths.map((p) => ({
|
|
921
|
+
path: toChosenPath(p),
|
|
922
|
+
head: p.head.id
|
|
923
|
+
}))
|
|
924
|
+
}, {
|
|
925
|
+
F: __dxlog_file3,
|
|
926
|
+
L: 155,
|
|
927
|
+
S: this,
|
|
928
|
+
C: (f, a) => f(...a)
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
return this._setCurrentState(lastPath);
|
|
932
|
+
}
|
|
933
|
+
_replayFailedPaths(paths, pendingPaths, mergeResult, convergedPaths) {
|
|
934
|
+
paths.push(...mergeResult.replay.map((path) => {
|
|
935
|
+
const stateOverrides = path.stateOverrides ?? new import_util6.ComplexMap(import_keys7.PublicKey.hash);
|
|
936
|
+
mergeResult.stateOverrides.forEach((value, key) => stateOverrides.set(key, value));
|
|
937
|
+
return {
|
|
938
|
+
...mergeResult.from,
|
|
939
|
+
chosenPath: path.chosenPath,
|
|
940
|
+
stateOverrides
|
|
941
|
+
};
|
|
942
|
+
}));
|
|
943
|
+
(0, import_log3.log)("replay paths", () => ({
|
|
944
|
+
count: paths.length,
|
|
945
|
+
paths: paths.map((path) => ({
|
|
946
|
+
from: mergeResult.from.head.id,
|
|
947
|
+
path: toChosenPath(path),
|
|
948
|
+
overrides: path?.stateOverrides?.mapValues((v) => this._stateHandler.toLogString(v.assertion))
|
|
949
|
+
}))
|
|
950
|
+
}), {
|
|
951
|
+
F: __dxlog_file3,
|
|
952
|
+
L: 175,
|
|
953
|
+
S: this,
|
|
954
|
+
C: (f, a) => f(...a)
|
|
955
|
+
});
|
|
956
|
+
const clearedPending = convergedPaths.filter((l) => !mergeResult.replay.includes(l));
|
|
957
|
+
pendingPaths.set(convergedPaths[0].head.id, clearedPending);
|
|
958
|
+
}
|
|
959
|
+
_handleMergePoint(paths, pendingPaths, path) {
|
|
960
|
+
const pendingList = pendingPaths.get(path.head.id) ?? [];
|
|
961
|
+
pendingPaths.set(path.head.id, pendingList);
|
|
962
|
+
pendingList.push(path);
|
|
963
|
+
if (pendingList.length < path.head.parents.length) {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
if (path.head.id === this._sentinel.id && paths.length > 0) {
|
|
967
|
+
(0, import_log3.log)("waiting for all the active paths to converge on the sentinel", void 0, {
|
|
968
|
+
F: __dxlog_file3,
|
|
969
|
+
L: 199,
|
|
970
|
+
S: this,
|
|
971
|
+
C: (f, a) => f(...a)
|
|
972
|
+
});
|
|
973
|
+
return null;
|
|
974
|
+
}
|
|
975
|
+
pendingPaths.delete(path.head.id);
|
|
976
|
+
return pendingList;
|
|
977
|
+
}
|
|
978
|
+
_updatePathState(path) {
|
|
979
|
+
const headCredential = path.head.credential;
|
|
980
|
+
if (headCredential == null) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const updatedSubject = headCredential.subject.id;
|
|
984
|
+
path.credentials.add(headCredential.id);
|
|
985
|
+
let isUpdateAllowed = this._stateHandler.isUpdateAllowed(path, headCredential, path.head.assertion);
|
|
986
|
+
if (!isUpdateAllowed && path.head.parents[0]?.id === this._root.id) {
|
|
987
|
+
const globalState = this.getGlobalStateScope();
|
|
988
|
+
isUpdateAllowed = this._stateHandler.isUpdateAllowed(globalState, headCredential, path.head.assertion);
|
|
989
|
+
}
|
|
990
|
+
if (isUpdateAllowed) {
|
|
991
|
+
path.forkChangedSubjects.add(updatedSubject);
|
|
992
|
+
path.forkIssuers.add(headCredential.issuer);
|
|
993
|
+
path.state.set(updatedSubject, path.head);
|
|
994
|
+
(0, import_log3.log)("path state updated", () => ({
|
|
995
|
+
subject: updatedSubject,
|
|
996
|
+
newState: this._stateHandler.toLogString(path.head.assertion)
|
|
997
|
+
}), {
|
|
998
|
+
F: __dxlog_file3,
|
|
999
|
+
L: 223,
|
|
1000
|
+
S: this,
|
|
1001
|
+
C: (f, a) => f(...a)
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
_forkTraversal(paths, path) {
|
|
1006
|
+
const replayChoice = path.chosenPath?.[path.head.id];
|
|
1007
|
+
const choices = replayChoice ?? path.head.children;
|
|
1008
|
+
for (const choice of choices) {
|
|
1009
|
+
(0, import_log3.log)("edge traversal", {
|
|
1010
|
+
from: path.head.id,
|
|
1011
|
+
to: choice.id
|
|
1012
|
+
}, {
|
|
1013
|
+
F: __dxlog_file3,
|
|
1014
|
+
L: 234,
|
|
1015
|
+
S: this,
|
|
1016
|
+
C: (f, a) => f(...a)
|
|
1017
|
+
});
|
|
1018
|
+
const fork = {
|
|
1019
|
+
forkPoint: path,
|
|
1020
|
+
chosenPath: {
|
|
1021
|
+
...path.chosenPath,
|
|
1022
|
+
[path.head.id]: [
|
|
1023
|
+
choice
|
|
1024
|
+
]
|
|
1025
|
+
},
|
|
1026
|
+
head: choice,
|
|
1027
|
+
credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash, path.credentials),
|
|
1028
|
+
state: new import_util6.ComplexMap(import_keys7.PublicKey.hash, [
|
|
1029
|
+
...path.state.entries()
|
|
1030
|
+
]),
|
|
1031
|
+
forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
|
|
1032
|
+
forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
|
|
1033
|
+
stateOverrides: path.stateOverrides
|
|
1034
|
+
};
|
|
1035
|
+
paths.push(fork);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Updates the current graph state.
|
|
1040
|
+
* @returns changed states.
|
|
1041
|
+
*/
|
|
1042
|
+
_setCurrentState(path) {
|
|
1043
|
+
const changedSubjects = [];
|
|
1044
|
+
const newStateMap = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
|
|
1045
|
+
const newVertexMap = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
|
|
1046
|
+
for (const [subjectKey, subjectVertex] of path.state.entries()) {
|
|
1047
|
+
const newState = this._stateHandler.createState(subjectVertex.credential, subjectVertex.assertion);
|
|
1048
|
+
const prevState = this._subjectToState.get(subjectKey);
|
|
1049
|
+
newStateMap.set(subjectKey, newState);
|
|
1050
|
+
newVertexMap.set(subjectKey, subjectVertex);
|
|
1051
|
+
if (this._stateHandler.hasStateChanged(newState, prevState)) {
|
|
1052
|
+
changedSubjects.push(newState);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
this._subjectToState = newStateMap;
|
|
1056
|
+
this._subjectToVertex = newVertexMap;
|
|
1057
|
+
return changedSubjects;
|
|
1058
|
+
}
|
|
1059
|
+
/*
|
|
1060
|
+
* Walk up all the fork points and return the first one present in all the paths.
|
|
1061
|
+
* We use local id to determine vertex position in the graph, because nodes can't
|
|
1062
|
+
* be inserted in the middle (between a parent and a child) and ids are monotonically increasing.
|
|
1063
|
+
*/
|
|
1064
|
+
_leastCommonAncestor(paths) {
|
|
1065
|
+
const uniqueForkPoints = paths.reduce((acc, path) => {
|
|
1066
|
+
let it = path.forkPoint;
|
|
1067
|
+
while (it) {
|
|
1068
|
+
acc.set(it.head.id, it);
|
|
1069
|
+
it = it.forkPoint;
|
|
1070
|
+
}
|
|
1071
|
+
return acc;
|
|
1072
|
+
}, /* @__PURE__ */ new Map());
|
|
1073
|
+
let maxId = this._root.id;
|
|
1074
|
+
let maxState = null;
|
|
1075
|
+
for (const [id, state] of uniqueForkPoints.entries()) {
|
|
1076
|
+
const headCredential = state.head.credential;
|
|
1077
|
+
if (headCredential != null) {
|
|
1078
|
+
const isPointInEveryPath = paths.every((p) => p.credentials.has(headCredential.id));
|
|
1079
|
+
if (isPointInEveryPath && id > maxId) {
|
|
1080
|
+
maxId = id;
|
|
1081
|
+
maxState = state;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return maxState ?? this._createRootPath();
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* We might be merging paths where some of them had fork points after the initial forking.
|
|
1089
|
+
* We need all the paths to point to the least common fork point and contain all the changes
|
|
1090
|
+
* that happened after it.
|
|
1091
|
+
*/
|
|
1092
|
+
_moveUpToForkPoint(forkPoint, path) {
|
|
1093
|
+
const isForkPointInPath = path.chosenPath[forkPoint.head.id] == null || path.forkPoint == null;
|
|
1094
|
+
if (isForkPointInPath) {
|
|
1095
|
+
return path;
|
|
1096
|
+
}
|
|
1097
|
+
if (forkPoint.head.id === path.forkPoint?.head.id) {
|
|
1098
|
+
return path;
|
|
1099
|
+
}
|
|
1100
|
+
let it = path.forkPoint;
|
|
1101
|
+
while (it.head.id !== forkPoint.head.id) {
|
|
1102
|
+
it.forkIssuers.forEach((iss) => path.forkIssuers.add(iss));
|
|
1103
|
+
it.forkChangedSubjects.forEach((m) => path.forkChangedSubjects.add(m));
|
|
1104
|
+
it = it.forkPoint;
|
|
1105
|
+
path.forkPoint = it;
|
|
1106
|
+
}
|
|
1107
|
+
return path;
|
|
1108
|
+
}
|
|
1109
|
+
_mergePaths(convergedPaths) {
|
|
1110
|
+
(0, import_invariant4.invariant)(convergedPaths.length >= 1, void 0, {
|
|
1111
|
+
F: __dxlog_file3,
|
|
1112
|
+
L: 324,
|
|
1113
|
+
S: this,
|
|
1114
|
+
A: [
|
|
1115
|
+
"convergedPaths.length >= 1",
|
|
1116
|
+
""
|
|
1117
|
+
]
|
|
1118
|
+
});
|
|
1119
|
+
if (convergedPaths.length === 1) {
|
|
1120
|
+
return {
|
|
1121
|
+
type: "merged",
|
|
1122
|
+
path: convergedPaths[0]
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
const forkPoint = this._leastCommonAncestor(convergedPaths);
|
|
1126
|
+
(0, import_log3.log)("merging paths", () => ({
|
|
1127
|
+
forkPointId: forkPoint.head.id,
|
|
1128
|
+
pathCount: convergedPaths.length,
|
|
1129
|
+
forkPoints: convergedPaths.map((fp) => fp.forkPoint?.head.id)
|
|
1130
|
+
}), {
|
|
1131
|
+
F: __dxlog_file3,
|
|
1132
|
+
L: 329,
|
|
1133
|
+
S: this,
|
|
1134
|
+
C: (f, a) => f(...a)
|
|
1135
|
+
});
|
|
1136
|
+
const paths = convergedPaths.map((p) => this._moveUpToForkPoint(forkPoint, p));
|
|
1137
|
+
(0, import_invariant4.invariant)(forkPoint, void 0, {
|
|
1138
|
+
F: __dxlog_file3,
|
|
1139
|
+
L: 335,
|
|
1140
|
+
S: this,
|
|
1141
|
+
A: [
|
|
1142
|
+
"forkPoint",
|
|
1143
|
+
""
|
|
1144
|
+
]
|
|
1145
|
+
});
|
|
1146
|
+
const result = {
|
|
1147
|
+
forkPoint: forkPoint.forkPoint,
|
|
1148
|
+
chosenPath: {
|
|
1149
|
+
...forkPoint.chosenPath,
|
|
1150
|
+
[forkPoint.head.id]: []
|
|
1151
|
+
},
|
|
1152
|
+
stateOverrides: forkPoint.stateOverrides,
|
|
1153
|
+
credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.credentials),
|
|
1154
|
+
forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.forkIssuers),
|
|
1155
|
+
forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash, forkPoint.forkChangedSubjects),
|
|
1156
|
+
state: forkPoint.state.mapValues((v) => v),
|
|
1157
|
+
head: paths[0].head
|
|
1158
|
+
};
|
|
1159
|
+
const subjectToBranch = new import_util6.ComplexMap(import_keys7.PublicKey.hash);
|
|
1160
|
+
for (const path of paths) {
|
|
1161
|
+
(0, import_log3.log)("processing a path", () => ({
|
|
1162
|
+
choices: toChosenPath(path),
|
|
1163
|
+
modified: path.forkChangedSubjects,
|
|
1164
|
+
forkIssuers: path.forkIssuers,
|
|
1165
|
+
state: path.state.mapValues((v) => this._stateHandler.toLogString(v.assertion))
|
|
1166
|
+
}), {
|
|
1167
|
+
F: __dxlog_file3,
|
|
1168
|
+
L: 348,
|
|
1169
|
+
S: this,
|
|
1170
|
+
C: (f, a) => f(...a)
|
|
1171
|
+
});
|
|
1172
|
+
path.forkIssuers.forEach((iss) => result.forkIssuers.add(iss));
|
|
1173
|
+
path.credentials.forEach((cred) => result.credentials.add(cred));
|
|
1174
|
+
result.chosenPath[forkPoint.head.id].push(...path.chosenPath[forkPoint.head.id] ?? []);
|
|
1175
|
+
for (const modifiedSubject of path.forkChangedSubjects) {
|
|
1176
|
+
const existingBranch = subjectToBranch.get(modifiedSubject);
|
|
1177
|
+
if (existingBranch == null || this._shouldOverrideCredential(existingBranch, path, modifiedSubject)) {
|
|
1178
|
+
subjectToBranch.set(modifiedSubject, path);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
const replayPaths = /* @__PURE__ */ new Set();
|
|
1183
|
+
const addReplayPath = replayPaths.add.bind(replayPaths);
|
|
1184
|
+
for (const [subject, branch] of subjectToBranch.entries()) {
|
|
1185
|
+
result.forkChangedSubjects.add(subject);
|
|
1186
|
+
const vertex = branch.state.get(subject);
|
|
1187
|
+
result.state.set(subject, vertex);
|
|
1188
|
+
(0, import_log3.log)("set subject state", () => ({
|
|
1189
|
+
subject,
|
|
1190
|
+
state: this._stateHandler.toLogString(vertex.assertion)
|
|
1191
|
+
}), {
|
|
1192
|
+
F: __dxlog_file3,
|
|
1193
|
+
L: 370,
|
|
1194
|
+
S: this,
|
|
1195
|
+
C: (f, a) => f(...a)
|
|
1196
|
+
});
|
|
1197
|
+
const otherPaths = paths.filter((p) => p !== branch);
|
|
1198
|
+
this._stateHandler.getConflictingPaths(otherPaths, vertex).forEach(addReplayPath);
|
|
1199
|
+
}
|
|
1200
|
+
if (replayPaths.size > 0) {
|
|
1201
|
+
return {
|
|
1202
|
+
type: "replay_required",
|
|
1203
|
+
replay: [
|
|
1204
|
+
...replayPaths.values()
|
|
1205
|
+
],
|
|
1206
|
+
from: forkPoint,
|
|
1207
|
+
stateOverrides: subjectToBranch.mapValues((v, key) => v.state.get(key))
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
type: "merged",
|
|
1212
|
+
path: result
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* A candidate credential is preferred over the existing credential if:
|
|
1217
|
+
* 1. It is the merge-point, because it's the last credential that was issued in awareness of all
|
|
1218
|
+
* the previously existing ones.
|
|
1219
|
+
* 2. A path where candidate was set contains existing credential in it, which means that the candidate
|
|
1220
|
+
* was issued after the existing credential by a legitimate issuer.
|
|
1221
|
+
* 3. A state-specific logic (_stateHandler) is able to justify using the candidate credential.
|
|
1222
|
+
* 4. The path where candidate was set has more issuers than the existing path (longer branch).
|
|
1223
|
+
* 5. The issuance time of the candidate is after the issuance time of the existing credential (LWW).
|
|
1224
|
+
*/
|
|
1225
|
+
_shouldOverrideCredential(existing, candidate, modifiedSubject) {
|
|
1226
|
+
const candidateVertex = candidate.state.get(modifiedSubject);
|
|
1227
|
+
const currentVertex = existing.state.get(modifiedSubject);
|
|
1228
|
+
if (candidateVertex.id === currentVertex.id) {
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
const mergePointId = existing.head.id;
|
|
1232
|
+
if (candidateVertex.id === mergePointId || currentVertex.id === mergePointId) {
|
|
1233
|
+
(0, import_log3.log)("merge point chosen to break the tie", {
|
|
1234
|
+
mergePointId: existing.head.id
|
|
1235
|
+
}, {
|
|
1236
|
+
F: __dxlog_file3,
|
|
1237
|
+
L: 408,
|
|
1238
|
+
S: this,
|
|
1239
|
+
C: (f, a) => f(...a)
|
|
1240
|
+
});
|
|
1241
|
+
return mergePointId === candidateVertex.id;
|
|
1242
|
+
}
|
|
1243
|
+
const candidateCredential = candidateVertex.credential;
|
|
1244
|
+
const currentCredential = currentVertex.credential;
|
|
1245
|
+
if (existing.credentials.has(candidateCredential.id) !== candidate.credentials.has(currentCredential.id)) {
|
|
1246
|
+
(0, import_log3.log)("one of the credentials was overridden in another branch", {
|
|
1247
|
+
current: currentVertex.id,
|
|
1248
|
+
candidate: candidateVertex.id
|
|
1249
|
+
}, {
|
|
1250
|
+
F: __dxlog_file3,
|
|
1251
|
+
L: 415,
|
|
1252
|
+
S: this,
|
|
1253
|
+
C: (f, a) => f(...a)
|
|
1254
|
+
});
|
|
1255
|
+
return candidate.credentials.has(currentCredential.id);
|
|
1256
|
+
}
|
|
1257
|
+
const winningCredential = this._stateHandler.tryPickWinningUpdate(existing, currentCredential, candidate, candidateCredential);
|
|
1258
|
+
if (winningCredential != null) {
|
|
1259
|
+
return winningCredential === candidateCredential;
|
|
1260
|
+
}
|
|
1261
|
+
if (candidate.forkIssuers.size !== existing.forkIssuers.size) {
|
|
1262
|
+
(0, import_log3.log)("longer issuers branch used to break the tie", {
|
|
1263
|
+
issuerCount: [
|
|
1264
|
+
existing.forkIssuers.size,
|
|
1265
|
+
candidate.forkIssuers.size
|
|
1266
|
+
]
|
|
1267
|
+
}, {
|
|
1268
|
+
F: __dxlog_file3,
|
|
1269
|
+
L: 432,
|
|
1270
|
+
S: this,
|
|
1271
|
+
C: (f, a) => f(...a)
|
|
1272
|
+
});
|
|
1273
|
+
return candidate.forkIssuers.size > existing.forkIssuers.size;
|
|
1274
|
+
}
|
|
1275
|
+
(0, import_log3.log)("issuance date used to break the tie", void 0, {
|
|
1276
|
+
F: __dxlog_file3,
|
|
1277
|
+
L: 437,
|
|
1278
|
+
S: this,
|
|
1279
|
+
C: (f, a) => f(...a)
|
|
1280
|
+
});
|
|
1281
|
+
return candidateCredential.issuanceDate.getTime() > currentCredential.issuanceDate.getTime();
|
|
1282
|
+
}
|
|
1283
|
+
_createRootPath() {
|
|
1284
|
+
return {
|
|
1285
|
+
head: this._root,
|
|
1286
|
+
chosenPath: {},
|
|
1287
|
+
forkIssuers: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
|
|
1288
|
+
forkChangedSubjects: new import_util6.ComplexSet(import_keys7.PublicKey.hash),
|
|
1289
|
+
state: new import_util6.ComplexMap(import_keys7.PublicKey.hash),
|
|
1290
|
+
credentials: new import_util6.ComplexSet(import_keys7.PublicKey.hash)
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
var toChosenPath = (path) => {
|
|
1295
|
+
return Object.fromEntries(Object.entries(path.chosenPath).map(([k, vs]) => [
|
|
1296
|
+
k,
|
|
1297
|
+
vs.map((v) => v.id)
|
|
1298
|
+
]));
|
|
1299
|
+
};
|
|
1300
|
+
var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/member-state-machine.ts";
|
|
739
1301
|
var MemberStateMachine = class {
|
|
740
1302
|
constructor(_spaceKey) {
|
|
741
1303
|
this._spaceKey = _spaceKey;
|
|
742
|
-
this.
|
|
743
|
-
this.
|
|
1304
|
+
this._memberProfiles = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
|
|
1305
|
+
this._hashgraph = new CredentialGraph(this);
|
|
1306
|
+
this.onMemberRoleChanged = this._hashgraph.onSubjectStateChanged;
|
|
744
1307
|
}
|
|
745
1308
|
get creator() {
|
|
746
|
-
return this.
|
|
1309
|
+
return this._ownerKey && this._hashgraph.getSubjectState(this._ownerKey);
|
|
747
1310
|
}
|
|
748
1311
|
get members() {
|
|
749
|
-
return this.
|
|
1312
|
+
return this._hashgraph.getState();
|
|
1313
|
+
}
|
|
1314
|
+
get membershipChainHeads() {
|
|
1315
|
+
return this._hashgraph.getLeafIds();
|
|
750
1316
|
}
|
|
751
1317
|
getRole(member) {
|
|
752
|
-
return this.
|
|
1318
|
+
return this._getRole(this._hashgraph.getGlobalStateScope(), member);
|
|
753
1319
|
}
|
|
754
1320
|
/**
|
|
755
1321
|
* Processes the SpaceMember credential.
|
|
@@ -760,84 +1326,122 @@ var MemberStateMachine = class {
|
|
|
760
1326
|
switch (assertion["@type"]) {
|
|
761
1327
|
case "dxos.halo.credentials.SpaceMember": {
|
|
762
1328
|
(0, import_invariant3.invariant)(assertion.spaceKey.equals(this._spaceKey), void 0, {
|
|
763
|
-
F:
|
|
764
|
-
L:
|
|
1329
|
+
F: __dxlog_file4,
|
|
1330
|
+
L: 66,
|
|
765
1331
|
S: this,
|
|
766
1332
|
A: [
|
|
767
1333
|
"assertion.spaceKey.equals(this._spaceKey)",
|
|
768
1334
|
""
|
|
769
1335
|
]
|
|
770
1336
|
});
|
|
771
|
-
if (this.
|
|
772
|
-
|
|
1337
|
+
if (this._ownerKey == null && credential.issuer === this._spaceKey) {
|
|
1338
|
+
this._ownerKey = credential.subject.id;
|
|
773
1339
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
credential,
|
|
777
|
-
assertion,
|
|
778
|
-
removed: false,
|
|
779
|
-
profile: assertion.profile
|
|
780
|
-
};
|
|
781
|
-
if (!this._creator) {
|
|
782
|
-
this._creator = info;
|
|
1340
|
+
if (assertion.profile != null) {
|
|
1341
|
+
this._memberProfiles.set(credential.subject.id, assertion.profile);
|
|
783
1342
|
}
|
|
784
|
-
this.
|
|
785
|
-
(0, import_log2.log)("member added", {
|
|
786
|
-
member: credential.subject.id,
|
|
787
|
-
space: this._spaceKey,
|
|
788
|
-
role: assertion.role,
|
|
789
|
-
profile: assertion.profile
|
|
790
|
-
}, {
|
|
791
|
-
F: __dxlog_file3,
|
|
792
|
-
L: 79,
|
|
793
|
-
S: this,
|
|
794
|
-
C: (f, a) => f(...a)
|
|
795
|
-
});
|
|
796
|
-
await this.onMemberAdmitted.callIfSet(info);
|
|
1343
|
+
await this._hashgraph.addVertex(credential, assertion);
|
|
797
1344
|
break;
|
|
798
1345
|
}
|
|
799
1346
|
case "dxos.halo.credentials.MemberProfile": {
|
|
800
|
-
const member = this.
|
|
801
|
-
if (
|
|
1347
|
+
const member = this._hashgraph.getSubjectState(credential.subject.id);
|
|
1348
|
+
if (member) {
|
|
1349
|
+
member.profile = assertion.profile;
|
|
1350
|
+
} else {
|
|
802
1351
|
import_log2.log.warn("Member not found", {
|
|
803
1352
|
id: credential.subject.id
|
|
804
1353
|
}, {
|
|
805
|
-
F:
|
|
806
|
-
L:
|
|
1354
|
+
F: __dxlog_file4,
|
|
1355
|
+
L: 81,
|
|
807
1356
|
S: this,
|
|
808
1357
|
C: (f, a) => f(...a)
|
|
809
1358
|
});
|
|
810
|
-
} else {
|
|
811
|
-
member.profile = assertion.profile;
|
|
812
1359
|
}
|
|
1360
|
+
this._memberProfiles.set(credential.subject.id, assertion.profile);
|
|
813
1361
|
break;
|
|
814
1362
|
}
|
|
815
1363
|
default:
|
|
816
1364
|
throw new Error("Invalid assertion type");
|
|
817
1365
|
}
|
|
818
1366
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1367
|
+
createState(credential, assertion) {
|
|
1368
|
+
const memberKey = credential.subject.id;
|
|
1369
|
+
return {
|
|
1370
|
+
key: memberKey,
|
|
1371
|
+
role: assertion.role,
|
|
1372
|
+
credential,
|
|
1373
|
+
assertion,
|
|
1374
|
+
profile: this._memberProfiles.get(memberKey)
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
isUpdateAllowed(scope, credential, assertion) {
|
|
1378
|
+
if (assertion.role === import_credentials3.SpaceMember.Role.OWNER) {
|
|
1379
|
+
return credential.issuer.equals(this._spaceKey);
|
|
1380
|
+
}
|
|
1381
|
+
const issuer = credential.issuer;
|
|
1382
|
+
const isChangingOwnRole = issuer.equals(credential.subject.id);
|
|
1383
|
+
if (isChangingOwnRole) {
|
|
1384
|
+
return false;
|
|
1385
|
+
}
|
|
1386
|
+
if (issuer.equals(assertion.spaceKey)) {
|
|
1387
|
+
return true;
|
|
1388
|
+
}
|
|
1389
|
+
const issuerRole = this._getRole(scope, issuer);
|
|
1390
|
+
return issuerRole === import_credentials3.SpaceMember.Role.ADMIN || issuerRole === import_credentials3.SpaceMember.Role.OWNER;
|
|
1391
|
+
}
|
|
1392
|
+
getConflictingPaths(paths, update) {
|
|
1393
|
+
if (update.assertion.role !== import_credentials3.SpaceMember.Role.REMOVED && update.assertion.role !== import_credentials3.SpaceMember.Role.EDITOR) {
|
|
1394
|
+
return [];
|
|
1395
|
+
}
|
|
1396
|
+
const memberId = update.credential.subject.id;
|
|
1397
|
+
return paths.filter((p) => p.forkIssuers.has(memberId));
|
|
1398
|
+
}
|
|
1399
|
+
tryPickWinningUpdate(scope1, update1, scope2, update2) {
|
|
1400
|
+
const path1IssuerRole = this._getRole(scope1, update1.issuer);
|
|
1401
|
+
const path2IssuerRole = this._getRole(scope2, update2.issuer);
|
|
1402
|
+
if (path2IssuerRole === import_credentials3.SpaceMember.Role.OWNER !== (path1IssuerRole === import_credentials3.SpaceMember.Role.OWNER)) {
|
|
1403
|
+
(0, import_log2.log)("owner decision used to break the tie", void 0, {
|
|
1404
|
+
F: __dxlog_file4,
|
|
1405
|
+
L: 139,
|
|
1406
|
+
S: this,
|
|
1407
|
+
C: (f, a) => f(...a)
|
|
1408
|
+
});
|
|
1409
|
+
return path1IssuerRole === import_credentials3.SpaceMember.Role.OWNER ? update1 : update2;
|
|
1410
|
+
}
|
|
1411
|
+
return null;
|
|
1412
|
+
}
|
|
1413
|
+
toLogString(assertion) {
|
|
1414
|
+
const role = assertion?.role ?? import_credentials3.SpaceMember.Role.REMOVED;
|
|
1415
|
+
return Object.entries(import_credentials3.SpaceMember.Role).find(([_, value]) => value === role)[0];
|
|
1416
|
+
}
|
|
1417
|
+
hasStateChanged(s1, s2) {
|
|
1418
|
+
return s1?.role !== s2?.role;
|
|
1419
|
+
}
|
|
1420
|
+
_getRole(scope, memberId) {
|
|
1421
|
+
if (this._ownerKey?.equals(memberId)) {
|
|
1422
|
+
return import_credentials3.SpaceMember.Role.OWNER;
|
|
1423
|
+
}
|
|
1424
|
+
const realRole = scope.state.get(memberId)?.assertion?.role ?? import_credentials3.SpaceMember.Role.REMOVED;
|
|
1425
|
+
if (scope.stateOverrides != null) {
|
|
1426
|
+
const override = scope.stateOverrides.get(memberId);
|
|
1427
|
+
if (override != null) {
|
|
1428
|
+
(0, import_log2.log)("member role overridden in path", () => ({
|
|
1429
|
+
headId: scope.head?.id,
|
|
1430
|
+
roleOverride: this.toLogString(override.assertion),
|
|
1431
|
+
realRole: this.toLogString(scope.state.get(memberId)?.assertion)
|
|
1432
|
+
}), {
|
|
1433
|
+
F: __dxlog_file4,
|
|
1434
|
+
L: 162,
|
|
1435
|
+
S: this,
|
|
1436
|
+
C: (f, a) => f(...a)
|
|
1437
|
+
});
|
|
1438
|
+
return override.assertion.role;
|
|
833
1439
|
}
|
|
834
1440
|
}
|
|
1441
|
+
return realRole;
|
|
835
1442
|
}
|
|
836
1443
|
};
|
|
837
|
-
var
|
|
838
|
-
var REVOCABLE_CREDENTIALS = [
|
|
839
|
-
"dxos.halo.credentials.SpaceMember"
|
|
840
|
-
];
|
|
1444
|
+
var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
|
|
841
1445
|
var SpaceStateMachine = class {
|
|
842
1446
|
constructor(_spaceKey) {
|
|
843
1447
|
this._spaceKey = _spaceKey;
|
|
@@ -849,7 +1453,7 @@ var SpaceStateMachine = class {
|
|
|
849
1453
|
this._processedCredentials = new import_util2.ComplexSet(import_keys3.PublicKey.hash);
|
|
850
1454
|
this._credentialProcessors = [];
|
|
851
1455
|
this.onCredentialProcessed = new import_util2.Callback();
|
|
852
|
-
this.
|
|
1456
|
+
this.onMemberRoleChanged = this._members.onMemberRoleChanged;
|
|
853
1457
|
this.onFeedAdmitted = this._feeds.onFeedAdmitted;
|
|
854
1458
|
this.onDelegatedInvitation = this._invitations.onDelegatedInvitation;
|
|
855
1459
|
this.onDelegatedInvitationRemoved = this._invitations.onDelegatedInvitationRemoved;
|
|
@@ -860,6 +1464,9 @@ var SpaceStateMachine = class {
|
|
|
860
1464
|
get members() {
|
|
861
1465
|
return this._members.members;
|
|
862
1466
|
}
|
|
1467
|
+
get membershipChainHeads() {
|
|
1468
|
+
return this._members.membershipChainHeads;
|
|
1469
|
+
}
|
|
863
1470
|
get feeds() {
|
|
864
1471
|
return this._feeds.feeds;
|
|
865
1472
|
}
|
|
@@ -912,8 +1519,8 @@ var SpaceStateMachine = class {
|
|
|
912
1519
|
const result = await verifyCredential(credential);
|
|
913
1520
|
if (result.kind !== "pass") {
|
|
914
1521
|
import_log.log.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
|
|
915
|
-
F:
|
|
916
|
-
L:
|
|
1522
|
+
F: __dxlog_file5,
|
|
1523
|
+
L: 152,
|
|
917
1524
|
S: this,
|
|
918
1525
|
C: (f, a) => f(...a)
|
|
919
1526
|
});
|
|
@@ -922,43 +1529,11 @@ var SpaceStateMachine = class {
|
|
|
922
1529
|
}
|
|
923
1530
|
const assertion = getCredentialAssertion(credential);
|
|
924
1531
|
switch (assertion["@type"]) {
|
|
925
|
-
case "dxos.halo.credentials.Revocation": {
|
|
926
|
-
if (!this._canRevokeCredentials(credential.issuer)) {
|
|
927
|
-
import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
928
|
-
F: __dxlog_file4,
|
|
929
|
-
L: 158,
|
|
930
|
-
S: this,
|
|
931
|
-
C: (f, a) => f(...a)
|
|
932
|
-
});
|
|
933
|
-
return false;
|
|
934
|
-
}
|
|
935
|
-
const toBeRevoked = this._credentialsById.get(credential.subject.id);
|
|
936
|
-
if (!toBeRevoked) {
|
|
937
|
-
import_log.log.warn(`Credential to revoke not found: ${credential.subject.id}`, void 0, {
|
|
938
|
-
F: __dxlog_file4,
|
|
939
|
-
L: 163,
|
|
940
|
-
S: this,
|
|
941
|
-
C: (f, a) => f(...a)
|
|
942
|
-
});
|
|
943
|
-
return false;
|
|
944
|
-
}
|
|
945
|
-
if (!this._credentialCanBeRevoked(toBeRevoked)) {
|
|
946
|
-
import_log.log.warn(`Credential cannot be revoked: ${credential.subject.id}`, void 0, {
|
|
947
|
-
F: __dxlog_file4,
|
|
948
|
-
L: 167,
|
|
949
|
-
S: this,
|
|
950
|
-
C: (f, a) => f(...a)
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
toBeRevoked.revoked = true;
|
|
954
|
-
await this._members.onRevoked(toBeRevoked.credential, credential);
|
|
955
|
-
break;
|
|
956
|
-
}
|
|
957
1532
|
case "dxos.halo.credentials.SpaceGenesis": {
|
|
958
1533
|
if (this._genesisCredential) {
|
|
959
1534
|
import_log.log.warn("Space already has a genesis credential.", void 0, {
|
|
960
|
-
F:
|
|
961
|
-
L:
|
|
1535
|
+
F: __dxlog_file5,
|
|
1536
|
+
L: 161,
|
|
962
1537
|
S: this,
|
|
963
1538
|
C: (f, a) => f(...a)
|
|
964
1539
|
});
|
|
@@ -966,8 +1541,8 @@ var SpaceStateMachine = class {
|
|
|
966
1541
|
}
|
|
967
1542
|
if (!credential.issuer.equals(this._spaceKey)) {
|
|
968
1543
|
import_log.log.warn("Space genesis credential must be issued by space.", void 0, {
|
|
969
|
-
F:
|
|
970
|
-
L:
|
|
1544
|
+
F: __dxlog_file5,
|
|
1545
|
+
L: 165,
|
|
971
1546
|
S: this,
|
|
972
1547
|
C: (f, a) => f(...a)
|
|
973
1548
|
});
|
|
@@ -975,8 +1550,8 @@ var SpaceStateMachine = class {
|
|
|
975
1550
|
}
|
|
976
1551
|
if (!credential.subject.id.equals(this._spaceKey)) {
|
|
977
1552
|
import_log.log.warn("Space genesis credential must be issued to space.", void 0, {
|
|
978
|
-
F:
|
|
979
|
-
L:
|
|
1553
|
+
F: __dxlog_file5,
|
|
1554
|
+
L: 169,
|
|
980
1555
|
S: this,
|
|
981
1556
|
C: (f, a) => f(...a)
|
|
982
1557
|
});
|
|
@@ -991,8 +1566,8 @@ var SpaceStateMachine = class {
|
|
|
991
1566
|
}
|
|
992
1567
|
if (!this._genesisCredential) {
|
|
993
1568
|
import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
|
|
994
|
-
F:
|
|
995
|
-
L:
|
|
1569
|
+
F: __dxlog_file5,
|
|
1570
|
+
L: 182,
|
|
996
1571
|
S: this,
|
|
997
1572
|
C: (f, a) => f(...a)
|
|
998
1573
|
});
|
|
@@ -1000,8 +1575,8 @@ var SpaceStateMachine = class {
|
|
|
1000
1575
|
}
|
|
1001
1576
|
if (!this._canInviteNewMembers(credential.issuer)) {
|
|
1002
1577
|
import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
1003
|
-
F:
|
|
1004
|
-
L:
|
|
1578
|
+
F: __dxlog_file5,
|
|
1579
|
+
L: 186,
|
|
1005
1580
|
S: this,
|
|
1006
1581
|
C: (f, a) => f(...a)
|
|
1007
1582
|
});
|
|
@@ -1014,8 +1589,8 @@ var SpaceStateMachine = class {
|
|
|
1014
1589
|
case "dxos.halo.credentials.MemberProfile": {
|
|
1015
1590
|
if (!this._genesisCredential) {
|
|
1016
1591
|
import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
|
|
1017
|
-
F:
|
|
1018
|
-
L:
|
|
1592
|
+
F: __dxlog_file5,
|
|
1593
|
+
L: 197,
|
|
1019
1594
|
S: this,
|
|
1020
1595
|
C: (f, a) => f(...a)
|
|
1021
1596
|
});
|
|
@@ -1027,8 +1602,8 @@ var SpaceStateMachine = class {
|
|
|
1027
1602
|
case "dxos.halo.credentials.AdmittedFeed": {
|
|
1028
1603
|
if (!this._genesisCredential) {
|
|
1029
1604
|
import_log.log.warn("Space must have a genesis credential before admitting feeds.", void 0, {
|
|
1030
|
-
F:
|
|
1031
|
-
L:
|
|
1605
|
+
F: __dxlog_file5,
|
|
1606
|
+
L: 207,
|
|
1032
1607
|
S: this,
|
|
1033
1608
|
C: (f, a) => f(...a)
|
|
1034
1609
|
});
|
|
@@ -1036,8 +1611,8 @@ var SpaceStateMachine = class {
|
|
|
1036
1611
|
}
|
|
1037
1612
|
if (!this._canAdmitFeeds(credential.issuer)) {
|
|
1038
1613
|
import_log.log.warn(`Space member is not authorized to admit feeds: ${credential.issuer}`, void 0, {
|
|
1039
|
-
F:
|
|
1040
|
-
L:
|
|
1614
|
+
F: __dxlog_file5,
|
|
1615
|
+
L: 211,
|
|
1041
1616
|
S: this,
|
|
1042
1617
|
C: (f, a) => f(...a)
|
|
1043
1618
|
});
|
|
@@ -1050,8 +1625,8 @@ var SpaceStateMachine = class {
|
|
|
1050
1625
|
case "dxos.halo.invitations.DelegateSpaceInvitation": {
|
|
1051
1626
|
if (!this._canInviteNewMembers(credential.issuer)) {
|
|
1052
1627
|
import_log.log.warn(`Invalid invitation, space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
1053
|
-
F:
|
|
1054
|
-
L:
|
|
1628
|
+
F: __dxlog_file5,
|
|
1629
|
+
L: 222,
|
|
1055
1630
|
S: this,
|
|
1056
1631
|
C: (f, a) => f(...a)
|
|
1057
1632
|
});
|
|
@@ -1079,17 +1654,11 @@ var SpaceStateMachine = class {
|
|
|
1079
1654
|
return true;
|
|
1080
1655
|
}
|
|
1081
1656
|
_canInviteNewMembers(key) {
|
|
1082
|
-
return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN;
|
|
1657
|
+
return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN || this._members.getRole(key) === import_credentials2.SpaceMember.Role.OWNER;
|
|
1083
1658
|
}
|
|
1084
1659
|
_canAdmitFeeds(key) {
|
|
1085
1660
|
const role = this._members.getRole(key);
|
|
1086
|
-
return role === import_credentials2.SpaceMember.Role.
|
|
1087
|
-
}
|
|
1088
|
-
_canRevokeCredentials(key) {
|
|
1089
|
-
return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN;
|
|
1090
|
-
}
|
|
1091
|
-
_credentialCanBeRevoked(entry) {
|
|
1092
|
-
return REVOCABLE_CREDENTIALS.includes(entry.credential.subject.assertion["@type"]);
|
|
1661
|
+
return role === import_credentials2.SpaceMember.Role.EDITOR || role === import_credentials2.SpaceMember.Role.ADMIN || role === import_credentials2.SpaceMember.Role.OWNER;
|
|
1093
1662
|
}
|
|
1094
1663
|
};
|
|
1095
1664
|
var CredentialConsumer = class {
|
|
@@ -1119,20 +1688,20 @@ var CredentialConsumer = class {
|
|
|
1119
1688
|
await this._onClose();
|
|
1120
1689
|
}
|
|
1121
1690
|
};
|
|
1122
|
-
var
|
|
1691
|
+
var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
|
|
1123
1692
|
var DeviceStateMachine = class {
|
|
1124
1693
|
constructor(_params) {
|
|
1125
1694
|
this._params = _params;
|
|
1126
|
-
this.authorizedDeviceKeys = new
|
|
1695
|
+
this.authorizedDeviceKeys = new import_util7.ComplexMap(import_keys8.PublicKey.hash);
|
|
1127
1696
|
this.deviceChainReady = new import_async2.Trigger();
|
|
1128
1697
|
}
|
|
1129
1698
|
async processCredential(credential) {
|
|
1130
|
-
(0,
|
|
1699
|
+
(0, import_log4.log)("processing credential...", {
|
|
1131
1700
|
identityKey: this._params.identityKey,
|
|
1132
1701
|
deviceKey: this._params.deviceKey,
|
|
1133
1702
|
credential
|
|
1134
1703
|
}, {
|
|
1135
|
-
F:
|
|
1704
|
+
F: __dxlog_file6,
|
|
1136
1705
|
L: 35,
|
|
1137
1706
|
S: this,
|
|
1138
1707
|
C: (f, a) => f(...a)
|
|
@@ -1146,8 +1715,8 @@ var DeviceStateMachine = class {
|
|
|
1146
1715
|
const assertion = getCredentialAssertion(credential);
|
|
1147
1716
|
switch (assertion["@type"]) {
|
|
1148
1717
|
case "dxos.halo.credentials.AuthorizedDevice": {
|
|
1149
|
-
(0,
|
|
1150
|
-
F:
|
|
1718
|
+
(0, import_invariant5.invariant)(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
|
|
1719
|
+
F: __dxlog_file6,
|
|
1151
1720
|
L: 51,
|
|
1152
1721
|
S: this,
|
|
1153
1722
|
A: [
|
|
@@ -1156,12 +1725,12 @@ var DeviceStateMachine = class {
|
|
|
1156
1725
|
]
|
|
1157
1726
|
});
|
|
1158
1727
|
this.authorizedDeviceKeys.set(assertion.deviceKey, {});
|
|
1159
|
-
(0,
|
|
1728
|
+
(0, import_log4.log)("added device", {
|
|
1160
1729
|
localDeviceKey: this._params.deviceKey,
|
|
1161
1730
|
deviceKey: assertion.deviceKey,
|
|
1162
1731
|
size: this.authorizedDeviceKeys.size
|
|
1163
1732
|
}, {
|
|
1164
|
-
F:
|
|
1733
|
+
F: __dxlog_file6,
|
|
1165
1734
|
L: 54,
|
|
1166
1735
|
S: this,
|
|
1167
1736
|
C: (f, a) => f(...a)
|
|
@@ -1170,8 +1739,8 @@ var DeviceStateMachine = class {
|
|
|
1170
1739
|
break;
|
|
1171
1740
|
}
|
|
1172
1741
|
case "dxos.halo.credentials.DeviceProfile": {
|
|
1173
|
-
(0,
|
|
1174
|
-
F:
|
|
1742
|
+
(0, import_invariant5.invariant)(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
|
|
1743
|
+
F: __dxlog_file6,
|
|
1175
1744
|
L: 64,
|
|
1176
1745
|
S: this,
|
|
1177
1746
|
A: [
|
|
@@ -1180,11 +1749,11 @@ var DeviceStateMachine = class {
|
|
|
1180
1749
|
]
|
|
1181
1750
|
});
|
|
1182
1751
|
if (assertion && credential.subject.id.equals(this._params.deviceKey)) {
|
|
1183
|
-
|
|
1752
|
+
import_log4.log.trace("dxos.halo.device", {
|
|
1184
1753
|
deviceKey: credential.subject.id,
|
|
1185
1754
|
profile: assertion.profile
|
|
1186
1755
|
}, {
|
|
1187
|
-
F:
|
|
1756
|
+
F: __dxlog_file6,
|
|
1188
1757
|
L: 67,
|
|
1189
1758
|
S: this,
|
|
1190
1759
|
C: (f, a) => f(...a)
|
|
@@ -1197,7 +1766,7 @@ var DeviceStateMachine = class {
|
|
|
1197
1766
|
}
|
|
1198
1767
|
}
|
|
1199
1768
|
};
|
|
1200
|
-
var
|
|
1769
|
+
var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
|
|
1201
1770
|
var ProfileStateMachine = class {
|
|
1202
1771
|
constructor(_params) {
|
|
1203
1772
|
this._params = _params;
|
|
@@ -1207,11 +1776,11 @@ var ProfileStateMachine = class {
|
|
|
1207
1776
|
switch (assertion["@type"]) {
|
|
1208
1777
|
case "dxos.halo.credentials.IdentityProfile": {
|
|
1209
1778
|
if (!credential.issuer.equals(this._params.identityKey) || !credential.subject.id.equals(this._params.identityKey)) {
|
|
1210
|
-
|
|
1779
|
+
import_log5.log.warn("Invalid profile credential", {
|
|
1211
1780
|
expectedIdentity: this._params.identityKey,
|
|
1212
1781
|
credential
|
|
1213
1782
|
}, {
|
|
1214
|
-
F:
|
|
1783
|
+
F: __dxlog_file7,
|
|
1215
1784
|
L: 34,
|
|
1216
1785
|
S: this,
|
|
1217
1786
|
C: (f, a) => f(...a)
|
|
@@ -1219,11 +1788,11 @@ var ProfileStateMachine = class {
|
|
|
1219
1788
|
return;
|
|
1220
1789
|
}
|
|
1221
1790
|
this.profile = assertion.profile;
|
|
1222
|
-
(0,
|
|
1791
|
+
(0, import_log5.log)("updated profile", {
|
|
1223
1792
|
identityKey: this._params.identityKey,
|
|
1224
1793
|
profile: this.profile
|
|
1225
1794
|
}, {
|
|
1226
|
-
F:
|
|
1795
|
+
F: __dxlog_file7,
|
|
1227
1796
|
L: 40,
|
|
1228
1797
|
S: this,
|
|
1229
1798
|
C: (f, a) => f(...a)
|