@dxos/credentials 0.5.0 → 0.5.1-main.0ec204c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +755 -167
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +729 -144
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/credentials/credential-factory.d.ts +3 -1
- package/dist/types/src/credentials/credential-factory.d.ts.map +1 -1
- package/dist/types/src/credentials/credential-generator.d.ts +18 -2
- package/dist/types/src/credentials/credential-generator.d.ts.map +1 -1
- package/dist/types/src/credentials/signing.d.ts.map +1 -1
- package/dist/types/src/credentials/verifier.d.ts +1 -1
- package/dist/types/src/credentials/verifier.d.ts.map +1 -1
- package/dist/types/src/graph/credential-graph.d.ts +152 -0
- package/dist/types/src/graph/credential-graph.d.ts.map +1 -0
- package/dist/types/src/state-machine/member-state-machine.d.ts +17 -12
- package/dist/types/src/state-machine/member-state-machine.d.ts.map +1 -1
- package/dist/types/src/state-machine/member-state-machine.test.d.ts +2 -0
- package/dist/types/src/state-machine/member-state-machine.test.d.ts.map +1 -0
- package/dist/types/src/state-machine/space-state-machine.d.ts +3 -3
- package/dist/types/src/state-machine/space-state-machine.d.ts.map +1 -1
- package/package.json +16 -16
- package/project.json +3 -0
- package/src/credentials/credential-factory.ts +9 -1
- package/src/credentials/credential-generator.ts +35 -2
- package/src/credentials/signing.ts +1 -0
- package/src/credentials/verifier.test.ts +25 -0
- package/src/credentials/verifier.ts +5 -1
- package/src/graph/credential-graph.ts +557 -0
- package/src/state-machine/invitation-state-machine.test.ts +1 -1
- package/src/state-machine/member-state-machine.test.ts +580 -0
- package/src/state-machine/member-state-machine.ts +111 -53
- package/src/state-machine/space-state-machine.test.ts +20 -16
- package/src/state-machine/space-state-machine.ts +12 -33
package/dist/lib/node/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(node_exports, {
|
|
|
39
39
|
canonicalStringify: () => canonicalStringify,
|
|
40
40
|
checkCredentialType: () => checkCredentialType,
|
|
41
41
|
createAdmissionCredentials: () => createAdmissionCredentials,
|
|
42
|
+
createCancelDelegatedSpaceInvitationCredential: () => createCancelDelegatedSpaceInvitationCredential,
|
|
42
43
|
createCredential: () => createCredential,
|
|
43
44
|
createCredentialMessage: () => createCredentialMessage,
|
|
44
45
|
createCredentialSignerWithChain: () => createCredentialSignerWithChain,
|
|
@@ -87,13 +88,18 @@ var import_util4 = require("@dxos/util");
|
|
|
87
88
|
var import_invariant3 = require("@dxos/invariant");
|
|
88
89
|
var import_keys6 = require("@dxos/keys");
|
|
89
90
|
var import_log2 = require("@dxos/log");
|
|
91
|
+
var import_credentials3 = require("@dxos/protocols/proto/dxos/halo/credentials");
|
|
90
92
|
var import_util5 = require("@dxos/util");
|
|
91
|
-
var import_async2 = require("@dxos/async");
|
|
92
93
|
var import_invariant4 = require("@dxos/invariant");
|
|
93
94
|
var import_keys7 = require("@dxos/keys");
|
|
94
95
|
var import_log3 = require("@dxos/log");
|
|
95
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");
|
|
96
100
|
var import_log4 = require("@dxos/log");
|
|
101
|
+
var import_util7 = require("@dxos/util");
|
|
102
|
+
var import_log5 = require("@dxos/log");
|
|
97
103
|
var defaultSecretProvider = async () => Buffer.from("0000");
|
|
98
104
|
var defaultSecretValidator = async (invitation, secret) => true;
|
|
99
105
|
var generatePasscode = (length = 4) => {
|
|
@@ -178,6 +184,9 @@ var checkCredentialType = (credential, type) => credential.subject.assertion["@t
|
|
|
178
184
|
var credentialTypeFilter = (type) => (credential) => checkCredentialType(credential, type);
|
|
179
185
|
var SIGNATURE_TYPE_ED25519 = "ED25519Signature";
|
|
180
186
|
var verifyCredential = async (credential) => {
|
|
187
|
+
if (credential.parentCredentialIds?.length === 0) {
|
|
188
|
+
delete credential.parentCredentialIds;
|
|
189
|
+
}
|
|
181
190
|
if (!credential.issuer.equals(credential.proof.signer)) {
|
|
182
191
|
if (!credential.proof.chain) {
|
|
183
192
|
return {
|
|
@@ -240,10 +249,10 @@ var verifyChain = async (chain, authority, subject) => {
|
|
|
240
249
|
};
|
|
241
250
|
};
|
|
242
251
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/credentials/credential-factory.ts";
|
|
243
|
-
var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce }) => {
|
|
252
|
+
var createCredential = async ({ signer, issuer, subject, assertion, signingKey, chain, nonce, parentCredentialIds }) => {
|
|
244
253
|
(0, import_invariant.invariant)(assertion["@type"], "Invalid assertion.", {
|
|
245
254
|
F: __dxlog_file,
|
|
246
|
-
L:
|
|
255
|
+
L: 48,
|
|
247
256
|
S: void 0,
|
|
248
257
|
A: [
|
|
249
258
|
"assertion['@type']",
|
|
@@ -252,7 +261,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
252
261
|
});
|
|
253
262
|
(0, import_invariant.invariant)(!!signingKey === !!chain, "Chain must be provided if and only if the signing key differs from the issuer.", {
|
|
254
263
|
F: __dxlog_file,
|
|
255
|
-
L:
|
|
264
|
+
L: 49,
|
|
256
265
|
S: void 0,
|
|
257
266
|
A: [
|
|
258
267
|
"!!signingKey === !!chain",
|
|
@@ -263,7 +272,7 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
263
272
|
const result = await verifyChain(chain, issuer, signingKey);
|
|
264
273
|
(0, import_invariant.invariant)(result.kind === "pass", "Invalid chain.", {
|
|
265
274
|
F: __dxlog_file,
|
|
266
|
-
L:
|
|
275
|
+
L: 52,
|
|
267
276
|
S: void 0,
|
|
268
277
|
A: [
|
|
269
278
|
"result.kind === 'pass'",
|
|
@@ -286,6 +295,9 @@ var createCredential = async ({ signer, issuer, subject, assertion, signingKey,
|
|
|
286
295
|
nonce
|
|
287
296
|
}
|
|
288
297
|
};
|
|
298
|
+
if ((parentCredentialIds?.length ?? 0) > 0) {
|
|
299
|
+
credential.parentCredentialIds = parentCredentialIds;
|
|
300
|
+
}
|
|
289
301
|
const signedPayload = getCredentialProofPayload(credential);
|
|
290
302
|
credential.proof.value = await signer.sign(signingKey ?? issuer, signedPayload);
|
|
291
303
|
if (chain) {
|
|
@@ -302,12 +314,13 @@ var createCredentialMessage = (credential) => {
|
|
|
302
314
|
};
|
|
303
315
|
var createCredentialSignerWithKey = (signer, issuer) => ({
|
|
304
316
|
getIssuer: () => issuer,
|
|
305
|
-
createCredential: ({ subject, assertion, nonce }) => createCredential({
|
|
317
|
+
createCredential: ({ subject, assertion, nonce, parentCredentialIds }) => createCredential({
|
|
306
318
|
signer,
|
|
307
319
|
issuer,
|
|
308
320
|
subject,
|
|
309
321
|
assertion,
|
|
310
|
-
nonce
|
|
322
|
+
nonce,
|
|
323
|
+
parentCredentialIds
|
|
311
324
|
})
|
|
312
325
|
});
|
|
313
326
|
var createCredentialSignerWithChain = (signer, chain, signingKey) => ({
|
|
@@ -371,7 +384,7 @@ var CredentialGenerator = class {
|
|
|
371
384
|
assertion: {
|
|
372
385
|
"@type": "dxos.halo.credentials.SpaceMember",
|
|
373
386
|
spaceKey,
|
|
374
|
-
role: import_credentials.SpaceMember.Role.
|
|
387
|
+
role: import_credentials.SpaceMember.Role.EDITOR,
|
|
375
388
|
genesisFeedKey
|
|
376
389
|
}
|
|
377
390
|
}),
|
|
@@ -466,14 +479,15 @@ var createDeviceAuthorization = async (signer, identityKey, deviceKey) => {
|
|
|
466
479
|
credential
|
|
467
480
|
}));
|
|
468
481
|
};
|
|
469
|
-
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) => {
|
|
470
483
|
const credentials = await Promise.all([
|
|
471
484
|
await signer.createCredential({
|
|
472
485
|
subject: identityKey,
|
|
486
|
+
parentCredentialIds: membershipChainHeads,
|
|
473
487
|
assertion: {
|
|
474
488
|
"@type": "dxos.halo.credentials.SpaceMember",
|
|
475
489
|
spaceKey,
|
|
476
|
-
role
|
|
490
|
+
role,
|
|
477
491
|
profile,
|
|
478
492
|
genesisFeedKey,
|
|
479
493
|
invitationCredentialId
|
|
@@ -506,6 +520,20 @@ var createDelegatedSpaceInvitationCredential = async (signer, subject, invitatio
|
|
|
506
520
|
}
|
|
507
521
|
};
|
|
508
522
|
};
|
|
523
|
+
var createCancelDelegatedSpaceInvitationCredential = async (signer, subject, invitationCredentialId) => {
|
|
524
|
+
const credential = await signer.createCredential({
|
|
525
|
+
subject,
|
|
526
|
+
assertion: {
|
|
527
|
+
"@type": "dxos.halo.invitations.CancelDelegatedInvitation",
|
|
528
|
+
credentialId: invitationCredentialId
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
return {
|
|
532
|
+
credential: {
|
|
533
|
+
credential
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
};
|
|
509
537
|
var signPresentation = async ({ presentation, signer, signerKey, chain, nonce }) => {
|
|
510
538
|
const proof = {
|
|
511
539
|
type: SIGNATURE_TYPE_ED25519,
|
|
@@ -720,21 +748,574 @@ var InvitationStateMachine = class {
|
|
|
720
748
|
}
|
|
721
749
|
}
|
|
722
750
|
};
|
|
723
|
-
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";
|
|
724
1301
|
var MemberStateMachine = class {
|
|
725
1302
|
constructor(_spaceKey) {
|
|
726
1303
|
this._spaceKey = _spaceKey;
|
|
727
|
-
this.
|
|
728
|
-
this.
|
|
1304
|
+
this._memberProfiles = new import_util5.ComplexMap(import_keys6.PublicKey.hash);
|
|
1305
|
+
this._hashgraph = new CredentialGraph(this);
|
|
1306
|
+
this.onMemberRoleChanged = this._hashgraph.onSubjectStateChanged;
|
|
729
1307
|
}
|
|
730
1308
|
get creator() {
|
|
731
|
-
return this.
|
|
1309
|
+
return this._ownerKey && this._hashgraph.getSubjectState(this._ownerKey);
|
|
732
1310
|
}
|
|
733
1311
|
get members() {
|
|
734
|
-
return this.
|
|
1312
|
+
return this._hashgraph.getState();
|
|
1313
|
+
}
|
|
1314
|
+
get membershipChainHeads() {
|
|
1315
|
+
return this._hashgraph.getLeafIds();
|
|
735
1316
|
}
|
|
736
1317
|
getRole(member) {
|
|
737
|
-
return this.
|
|
1318
|
+
return this._getRole(this._hashgraph.getGlobalStateScope(), member);
|
|
738
1319
|
}
|
|
739
1320
|
/**
|
|
740
1321
|
* Processes the SpaceMember credential.
|
|
@@ -745,84 +1326,122 @@ var MemberStateMachine = class {
|
|
|
745
1326
|
switch (assertion["@type"]) {
|
|
746
1327
|
case "dxos.halo.credentials.SpaceMember": {
|
|
747
1328
|
(0, import_invariant3.invariant)(assertion.spaceKey.equals(this._spaceKey), void 0, {
|
|
748
|
-
F:
|
|
749
|
-
L:
|
|
1329
|
+
F: __dxlog_file4,
|
|
1330
|
+
L: 66,
|
|
750
1331
|
S: this,
|
|
751
1332
|
A: [
|
|
752
1333
|
"assertion.spaceKey.equals(this._spaceKey)",
|
|
753
1334
|
""
|
|
754
1335
|
]
|
|
755
1336
|
});
|
|
756
|
-
if (this.
|
|
757
|
-
|
|
1337
|
+
if (this._ownerKey == null && credential.issuer === this._spaceKey) {
|
|
1338
|
+
this._ownerKey = credential.subject.id;
|
|
758
1339
|
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
credential,
|
|
762
|
-
assertion,
|
|
763
|
-
removed: false,
|
|
764
|
-
profile: assertion.profile
|
|
765
|
-
};
|
|
766
|
-
if (!this._creator) {
|
|
767
|
-
this._creator = info;
|
|
1340
|
+
if (assertion.profile != null) {
|
|
1341
|
+
this._memberProfiles.set(credential.subject.id, assertion.profile);
|
|
768
1342
|
}
|
|
769
|
-
this.
|
|
770
|
-
(0, import_log2.log)("member added", {
|
|
771
|
-
member: credential.subject.id,
|
|
772
|
-
space: this._spaceKey,
|
|
773
|
-
role: assertion.role,
|
|
774
|
-
profile: assertion.profile
|
|
775
|
-
}, {
|
|
776
|
-
F: __dxlog_file3,
|
|
777
|
-
L: 79,
|
|
778
|
-
S: this,
|
|
779
|
-
C: (f, a) => f(...a)
|
|
780
|
-
});
|
|
781
|
-
await this.onMemberAdmitted.callIfSet(info);
|
|
1343
|
+
await this._hashgraph.addVertex(credential, assertion);
|
|
782
1344
|
break;
|
|
783
1345
|
}
|
|
784
1346
|
case "dxos.halo.credentials.MemberProfile": {
|
|
785
|
-
const member = this.
|
|
786
|
-
if (
|
|
1347
|
+
const member = this._hashgraph.getSubjectState(credential.subject.id);
|
|
1348
|
+
if (member) {
|
|
1349
|
+
member.profile = assertion.profile;
|
|
1350
|
+
} else {
|
|
787
1351
|
import_log2.log.warn("Member not found", {
|
|
788
1352
|
id: credential.subject.id
|
|
789
1353
|
}, {
|
|
790
|
-
F:
|
|
791
|
-
L:
|
|
1354
|
+
F: __dxlog_file4,
|
|
1355
|
+
L: 81,
|
|
792
1356
|
S: this,
|
|
793
1357
|
C: (f, a) => f(...a)
|
|
794
1358
|
});
|
|
795
|
-
} else {
|
|
796
|
-
member.profile = assertion.profile;
|
|
797
1359
|
}
|
|
1360
|
+
this._memberProfiles.set(credential.subject.id, assertion.profile);
|
|
798
1361
|
break;
|
|
799
1362
|
}
|
|
800
1363
|
default:
|
|
801
1364
|
throw new Error("Invalid assertion type");
|
|
802
1365
|
}
|
|
803
1366
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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;
|
|
818
1439
|
}
|
|
819
1440
|
}
|
|
1441
|
+
return realRole;
|
|
820
1442
|
}
|
|
821
1443
|
};
|
|
822
|
-
var
|
|
823
|
-
var REVOCABLE_CREDENTIALS = [
|
|
824
|
-
"dxos.halo.credentials.SpaceMember"
|
|
825
|
-
];
|
|
1444
|
+
var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/state-machine/space-state-machine.ts";
|
|
826
1445
|
var SpaceStateMachine = class {
|
|
827
1446
|
constructor(_spaceKey) {
|
|
828
1447
|
this._spaceKey = _spaceKey;
|
|
@@ -834,7 +1453,7 @@ var SpaceStateMachine = class {
|
|
|
834
1453
|
this._processedCredentials = new import_util2.ComplexSet(import_keys3.PublicKey.hash);
|
|
835
1454
|
this._credentialProcessors = [];
|
|
836
1455
|
this.onCredentialProcessed = new import_util2.Callback();
|
|
837
|
-
this.
|
|
1456
|
+
this.onMemberRoleChanged = this._members.onMemberRoleChanged;
|
|
838
1457
|
this.onFeedAdmitted = this._feeds.onFeedAdmitted;
|
|
839
1458
|
this.onDelegatedInvitation = this._invitations.onDelegatedInvitation;
|
|
840
1459
|
this.onDelegatedInvitationRemoved = this._invitations.onDelegatedInvitationRemoved;
|
|
@@ -845,6 +1464,9 @@ var SpaceStateMachine = class {
|
|
|
845
1464
|
get members() {
|
|
846
1465
|
return this._members.members;
|
|
847
1466
|
}
|
|
1467
|
+
get membershipChainHeads() {
|
|
1468
|
+
return this._members.membershipChainHeads;
|
|
1469
|
+
}
|
|
848
1470
|
get feeds() {
|
|
849
1471
|
return this._feeds.feeds;
|
|
850
1472
|
}
|
|
@@ -897,8 +1519,8 @@ var SpaceStateMachine = class {
|
|
|
897
1519
|
const result = await verifyCredential(credential);
|
|
898
1520
|
if (result.kind !== "pass") {
|
|
899
1521
|
import_log.log.warn(`Invalid credential: ${result.errors.join(", ")}`, void 0, {
|
|
900
|
-
F:
|
|
901
|
-
L:
|
|
1522
|
+
F: __dxlog_file5,
|
|
1523
|
+
L: 152,
|
|
902
1524
|
S: this,
|
|
903
1525
|
C: (f, a) => f(...a)
|
|
904
1526
|
});
|
|
@@ -907,43 +1529,11 @@ var SpaceStateMachine = class {
|
|
|
907
1529
|
}
|
|
908
1530
|
const assertion = getCredentialAssertion(credential);
|
|
909
1531
|
switch (assertion["@type"]) {
|
|
910
|
-
case "dxos.halo.credentials.Revocation": {
|
|
911
|
-
if (!this._canRevokeCredentials(credential.issuer)) {
|
|
912
|
-
import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
913
|
-
F: __dxlog_file4,
|
|
914
|
-
L: 158,
|
|
915
|
-
S: this,
|
|
916
|
-
C: (f, a) => f(...a)
|
|
917
|
-
});
|
|
918
|
-
return false;
|
|
919
|
-
}
|
|
920
|
-
const toBeRevoked = this._credentialsById.get(credential.subject.id);
|
|
921
|
-
if (!toBeRevoked) {
|
|
922
|
-
import_log.log.warn(`Credential to revoke not found: ${credential.subject.id}`, void 0, {
|
|
923
|
-
F: __dxlog_file4,
|
|
924
|
-
L: 163,
|
|
925
|
-
S: this,
|
|
926
|
-
C: (f, a) => f(...a)
|
|
927
|
-
});
|
|
928
|
-
return false;
|
|
929
|
-
}
|
|
930
|
-
if (!this._credentialCanBeRevoked(toBeRevoked)) {
|
|
931
|
-
import_log.log.warn(`Credential cannot be revoked: ${credential.subject.id}`, void 0, {
|
|
932
|
-
F: __dxlog_file4,
|
|
933
|
-
L: 167,
|
|
934
|
-
S: this,
|
|
935
|
-
C: (f, a) => f(...a)
|
|
936
|
-
});
|
|
937
|
-
}
|
|
938
|
-
toBeRevoked.revoked = true;
|
|
939
|
-
await this._members.onRevoked(toBeRevoked.credential, credential);
|
|
940
|
-
break;
|
|
941
|
-
}
|
|
942
1532
|
case "dxos.halo.credentials.SpaceGenesis": {
|
|
943
1533
|
if (this._genesisCredential) {
|
|
944
1534
|
import_log.log.warn("Space already has a genesis credential.", void 0, {
|
|
945
|
-
F:
|
|
946
|
-
L:
|
|
1535
|
+
F: __dxlog_file5,
|
|
1536
|
+
L: 161,
|
|
947
1537
|
S: this,
|
|
948
1538
|
C: (f, a) => f(...a)
|
|
949
1539
|
});
|
|
@@ -951,8 +1541,8 @@ var SpaceStateMachine = class {
|
|
|
951
1541
|
}
|
|
952
1542
|
if (!credential.issuer.equals(this._spaceKey)) {
|
|
953
1543
|
import_log.log.warn("Space genesis credential must be issued by space.", void 0, {
|
|
954
|
-
F:
|
|
955
|
-
L:
|
|
1544
|
+
F: __dxlog_file5,
|
|
1545
|
+
L: 165,
|
|
956
1546
|
S: this,
|
|
957
1547
|
C: (f, a) => f(...a)
|
|
958
1548
|
});
|
|
@@ -960,8 +1550,8 @@ var SpaceStateMachine = class {
|
|
|
960
1550
|
}
|
|
961
1551
|
if (!credential.subject.id.equals(this._spaceKey)) {
|
|
962
1552
|
import_log.log.warn("Space genesis credential must be issued to space.", void 0, {
|
|
963
|
-
F:
|
|
964
|
-
L:
|
|
1553
|
+
F: __dxlog_file5,
|
|
1554
|
+
L: 169,
|
|
965
1555
|
S: this,
|
|
966
1556
|
C: (f, a) => f(...a)
|
|
967
1557
|
});
|
|
@@ -976,8 +1566,8 @@ var SpaceStateMachine = class {
|
|
|
976
1566
|
}
|
|
977
1567
|
if (!this._genesisCredential) {
|
|
978
1568
|
import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
|
|
979
|
-
F:
|
|
980
|
-
L:
|
|
1569
|
+
F: __dxlog_file5,
|
|
1570
|
+
L: 182,
|
|
981
1571
|
S: this,
|
|
982
1572
|
C: (f, a) => f(...a)
|
|
983
1573
|
});
|
|
@@ -985,8 +1575,8 @@ var SpaceStateMachine = class {
|
|
|
985
1575
|
}
|
|
986
1576
|
if (!this._canInviteNewMembers(credential.issuer)) {
|
|
987
1577
|
import_log.log.warn(`Space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
988
|
-
F:
|
|
989
|
-
L:
|
|
1578
|
+
F: __dxlog_file5,
|
|
1579
|
+
L: 186,
|
|
990
1580
|
S: this,
|
|
991
1581
|
C: (f, a) => f(...a)
|
|
992
1582
|
});
|
|
@@ -999,8 +1589,8 @@ var SpaceStateMachine = class {
|
|
|
999
1589
|
case "dxos.halo.credentials.MemberProfile": {
|
|
1000
1590
|
if (!this._genesisCredential) {
|
|
1001
1591
|
import_log.log.warn("Space must have a genesis credential before adding members.", void 0, {
|
|
1002
|
-
F:
|
|
1003
|
-
L:
|
|
1592
|
+
F: __dxlog_file5,
|
|
1593
|
+
L: 197,
|
|
1004
1594
|
S: this,
|
|
1005
1595
|
C: (f, a) => f(...a)
|
|
1006
1596
|
});
|
|
@@ -1012,8 +1602,8 @@ var SpaceStateMachine = class {
|
|
|
1012
1602
|
case "dxos.halo.credentials.AdmittedFeed": {
|
|
1013
1603
|
if (!this._genesisCredential) {
|
|
1014
1604
|
import_log.log.warn("Space must have a genesis credential before admitting feeds.", void 0, {
|
|
1015
|
-
F:
|
|
1016
|
-
L:
|
|
1605
|
+
F: __dxlog_file5,
|
|
1606
|
+
L: 207,
|
|
1017
1607
|
S: this,
|
|
1018
1608
|
C: (f, a) => f(...a)
|
|
1019
1609
|
});
|
|
@@ -1021,8 +1611,8 @@ var SpaceStateMachine = class {
|
|
|
1021
1611
|
}
|
|
1022
1612
|
if (!this._canAdmitFeeds(credential.issuer)) {
|
|
1023
1613
|
import_log.log.warn(`Space member is not authorized to admit feeds: ${credential.issuer}`, void 0, {
|
|
1024
|
-
F:
|
|
1025
|
-
L:
|
|
1614
|
+
F: __dxlog_file5,
|
|
1615
|
+
L: 211,
|
|
1026
1616
|
S: this,
|
|
1027
1617
|
C: (f, a) => f(...a)
|
|
1028
1618
|
});
|
|
@@ -1035,8 +1625,8 @@ var SpaceStateMachine = class {
|
|
|
1035
1625
|
case "dxos.halo.invitations.DelegateSpaceInvitation": {
|
|
1036
1626
|
if (!this._canInviteNewMembers(credential.issuer)) {
|
|
1037
1627
|
import_log.log.warn(`Invalid invitation, space member is not authorized to invite new members: ${credential.issuer}`, void 0, {
|
|
1038
|
-
F:
|
|
1039
|
-
L:
|
|
1628
|
+
F: __dxlog_file5,
|
|
1629
|
+
L: 222,
|
|
1040
1630
|
S: this,
|
|
1041
1631
|
C: (f, a) => f(...a)
|
|
1042
1632
|
});
|
|
@@ -1064,17 +1654,11 @@ var SpaceStateMachine = class {
|
|
|
1064
1654
|
return true;
|
|
1065
1655
|
}
|
|
1066
1656
|
_canInviteNewMembers(key) {
|
|
1067
|
-
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;
|
|
1068
1658
|
}
|
|
1069
1659
|
_canAdmitFeeds(key) {
|
|
1070
1660
|
const role = this._members.getRole(key);
|
|
1071
|
-
return role === import_credentials2.SpaceMember.Role.
|
|
1072
|
-
}
|
|
1073
|
-
_canRevokeCredentials(key) {
|
|
1074
|
-
return key.equals(this._spaceKey) || this._members.getRole(key) === import_credentials2.SpaceMember.Role.ADMIN;
|
|
1075
|
-
}
|
|
1076
|
-
_credentialCanBeRevoked(entry) {
|
|
1077
|
-
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;
|
|
1078
1662
|
}
|
|
1079
1663
|
};
|
|
1080
1664
|
var CredentialConsumer = class {
|
|
@@ -1104,20 +1688,20 @@ var CredentialConsumer = class {
|
|
|
1104
1688
|
await this._onClose();
|
|
1105
1689
|
}
|
|
1106
1690
|
};
|
|
1107
|
-
var
|
|
1691
|
+
var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/device-state-machine.ts";
|
|
1108
1692
|
var DeviceStateMachine = class {
|
|
1109
1693
|
constructor(_params) {
|
|
1110
1694
|
this._params = _params;
|
|
1111
|
-
this.authorizedDeviceKeys = new
|
|
1695
|
+
this.authorizedDeviceKeys = new import_util7.ComplexMap(import_keys8.PublicKey.hash);
|
|
1112
1696
|
this.deviceChainReady = new import_async2.Trigger();
|
|
1113
1697
|
}
|
|
1114
1698
|
async processCredential(credential) {
|
|
1115
|
-
(0,
|
|
1699
|
+
(0, import_log4.log)("processing credential...", {
|
|
1116
1700
|
identityKey: this._params.identityKey,
|
|
1117
1701
|
deviceKey: this._params.deviceKey,
|
|
1118
1702
|
credential
|
|
1119
1703
|
}, {
|
|
1120
|
-
F:
|
|
1704
|
+
F: __dxlog_file6,
|
|
1121
1705
|
L: 35,
|
|
1122
1706
|
S: this,
|
|
1123
1707
|
C: (f, a) => f(...a)
|
|
@@ -1131,8 +1715,8 @@ var DeviceStateMachine = class {
|
|
|
1131
1715
|
const assertion = getCredentialAssertion(credential);
|
|
1132
1716
|
switch (assertion["@type"]) {
|
|
1133
1717
|
case "dxos.halo.credentials.AuthorizedDevice": {
|
|
1134
|
-
(0,
|
|
1135
|
-
F:
|
|
1718
|
+
(0, import_invariant5.invariant)(!this.authorizedDeviceKeys.has(assertion.deviceKey), "Device already added.", {
|
|
1719
|
+
F: __dxlog_file6,
|
|
1136
1720
|
L: 51,
|
|
1137
1721
|
S: this,
|
|
1138
1722
|
A: [
|
|
@@ -1141,12 +1725,12 @@ var DeviceStateMachine = class {
|
|
|
1141
1725
|
]
|
|
1142
1726
|
});
|
|
1143
1727
|
this.authorizedDeviceKeys.set(assertion.deviceKey, {});
|
|
1144
|
-
(0,
|
|
1728
|
+
(0, import_log4.log)("added device", {
|
|
1145
1729
|
localDeviceKey: this._params.deviceKey,
|
|
1146
1730
|
deviceKey: assertion.deviceKey,
|
|
1147
1731
|
size: this.authorizedDeviceKeys.size
|
|
1148
1732
|
}, {
|
|
1149
|
-
F:
|
|
1733
|
+
F: __dxlog_file6,
|
|
1150
1734
|
L: 54,
|
|
1151
1735
|
S: this,
|
|
1152
1736
|
C: (f, a) => f(...a)
|
|
@@ -1155,8 +1739,8 @@ var DeviceStateMachine = class {
|
|
|
1155
1739
|
break;
|
|
1156
1740
|
}
|
|
1157
1741
|
case "dxos.halo.credentials.DeviceProfile": {
|
|
1158
|
-
(0,
|
|
1159
|
-
F:
|
|
1742
|
+
(0, import_invariant5.invariant)(this.authorizedDeviceKeys.has(credential.subject.id), "Device not found.", {
|
|
1743
|
+
F: __dxlog_file6,
|
|
1160
1744
|
L: 64,
|
|
1161
1745
|
S: this,
|
|
1162
1746
|
A: [
|
|
@@ -1165,11 +1749,11 @@ var DeviceStateMachine = class {
|
|
|
1165
1749
|
]
|
|
1166
1750
|
});
|
|
1167
1751
|
if (assertion && credential.subject.id.equals(this._params.deviceKey)) {
|
|
1168
|
-
|
|
1752
|
+
import_log4.log.trace("dxos.halo.device", {
|
|
1169
1753
|
deviceKey: credential.subject.id,
|
|
1170
1754
|
profile: assertion.profile
|
|
1171
1755
|
}, {
|
|
1172
|
-
F:
|
|
1756
|
+
F: __dxlog_file6,
|
|
1173
1757
|
L: 67,
|
|
1174
1758
|
S: this,
|
|
1175
1759
|
C: (f, a) => f(...a)
|
|
@@ -1182,7 +1766,7 @@ var DeviceStateMachine = class {
|
|
|
1182
1766
|
}
|
|
1183
1767
|
}
|
|
1184
1768
|
};
|
|
1185
|
-
var
|
|
1769
|
+
var __dxlog_file7 = "/home/runner/work/dxos/dxos/packages/core/halo/credentials/src/processor/profile-state-machine.ts";
|
|
1186
1770
|
var ProfileStateMachine = class {
|
|
1187
1771
|
constructor(_params) {
|
|
1188
1772
|
this._params = _params;
|
|
@@ -1192,11 +1776,11 @@ var ProfileStateMachine = class {
|
|
|
1192
1776
|
switch (assertion["@type"]) {
|
|
1193
1777
|
case "dxos.halo.credentials.IdentityProfile": {
|
|
1194
1778
|
if (!credential.issuer.equals(this._params.identityKey) || !credential.subject.id.equals(this._params.identityKey)) {
|
|
1195
|
-
|
|
1779
|
+
import_log5.log.warn("Invalid profile credential", {
|
|
1196
1780
|
expectedIdentity: this._params.identityKey,
|
|
1197
1781
|
credential
|
|
1198
1782
|
}, {
|
|
1199
|
-
F:
|
|
1783
|
+
F: __dxlog_file7,
|
|
1200
1784
|
L: 34,
|
|
1201
1785
|
S: this,
|
|
1202
1786
|
C: (f, a) => f(...a)
|
|
@@ -1204,11 +1788,11 @@ var ProfileStateMachine = class {
|
|
|
1204
1788
|
return;
|
|
1205
1789
|
}
|
|
1206
1790
|
this.profile = assertion.profile;
|
|
1207
|
-
(0,
|
|
1791
|
+
(0, import_log5.log)("updated profile", {
|
|
1208
1792
|
identityKey: this._params.identityKey,
|
|
1209
1793
|
profile: this.profile
|
|
1210
1794
|
}, {
|
|
1211
|
-
F:
|
|
1795
|
+
F: __dxlog_file7,
|
|
1212
1796
|
L: 40,
|
|
1213
1797
|
S: this,
|
|
1214
1798
|
C: (f, a) => f(...a)
|
|
@@ -1232,6 +1816,7 @@ var ProfileStateMachine = class {
|
|
|
1232
1816
|
canonicalStringify,
|
|
1233
1817
|
checkCredentialType,
|
|
1234
1818
|
createAdmissionCredentials,
|
|
1819
|
+
createCancelDelegatedSpaceInvitationCredential,
|
|
1235
1820
|
createCredential,
|
|
1236
1821
|
createCredentialMessage,
|
|
1237
1822
|
createCredentialSignerWithChain,
|