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