@dxos/credentials 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6
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 → neutral}/index.mjs +39 -16
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/{browser → neutral}/meta.json +1 -1
- package/dist/types/src/credentials/credential-generator.d.ts +3 -3
- 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/state-machine/space-state-machine.d.ts +7 -1
- package/dist/types/src/state-machine/space-state-machine.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/docs/README.md +17 -17
- package/moon.yml +2 -1
- package/package.json +13 -14
- package/src/credentials/credential-generator.ts +5 -0
- package/src/credentials/signing.ts +12 -0
- package/src/state-machine/space-state-machine.test.ts +243 -1
- package/src/state-machine/space-state-machine.ts +20 -1
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -1912
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
package/docs/README.md
CHANGED
|
@@ -84,20 +84,20 @@ dxos/mesh-protocol --> dxos/codec-protobuf
|
|
|
84
84
|
|
|
85
85
|
## Dependencies
|
|
86
86
|
|
|
87
|
-
| Module
|
|
88
|
-
|
|
89
|
-
| [`@dxos/async`](../../../../common/async/docs/README.md)
|
|
90
|
-
| [`@dxos/codec-protobuf`](../../../../common/codec-protobuf/docs/README.md)
|
|
91
|
-
| [`@dxos/context`](../../../../common/context/docs/README.md)
|
|
92
|
-
| [`@dxos/crypto`](../../../../common/crypto/docs/README.md)
|
|
93
|
-
| [`@dxos/debug`](../../../../common/debug/docs/README.md)
|
|
94
|
-
| [`@dxos/feed-store`](../../../../common/feed-store/docs/README.md)
|
|
95
|
-
| [`@dxos/hypercore`](../../../../common/hypercore/docs/README.md)
|
|
96
|
-
| [`@dxos/keyring`](../../keyring/docs/README.md)
|
|
97
|
-
| [`@dxos/keys`](../../../../common/keys/docs/README.md)
|
|
98
|
-
| [`@dxos/log`](../../../../common/log/docs/README.md)
|
|
99
|
-
| [`@dxos/mesh-protocol`](../../../mesh/mesh-protocol/docs/README.md)
|
|
100
|
-
| [`@dxos/protocols`](../../../protocols/docs/README.md)
|
|
101
|
-
| [`@dxos/random-access-storage`](../../../../common/random-access-storage/docs/README.md) |
|
|
102
|
-
| [`@dxos/timeframe`](../../../../common/timeframe/docs/README.md)
|
|
103
|
-
| [`@dxos/util`](../../../../common/util/docs/README.md)
|
|
87
|
+
| Module | Direct |
|
|
88
|
+
| ---------------------------------------------------------------------------------------- | ------- |
|
|
89
|
+
| [`@dxos/async`](../../../../common/async/docs/README.md) | ✓ |
|
|
90
|
+
| [`@dxos/codec-protobuf`](../../../../common/codec-protobuf/docs/README.md) | ✓ |
|
|
91
|
+
| [`@dxos/context`](../../../../common/context/docs/README.md) | |
|
|
92
|
+
| [`@dxos/crypto`](../../../../common/crypto/docs/README.md) | ✓ |
|
|
93
|
+
| [`@dxos/debug`](../../../../common/debug/docs/README.md) | ✓ |
|
|
94
|
+
| [`@dxos/feed-store`](../../../../common/feed-store/docs/README.md) | ✓ |
|
|
95
|
+
| [`@dxos/hypercore`](../../../../common/hypercore/docs/README.md) | |
|
|
96
|
+
| [`@dxos/keyring`](../../keyring/docs/README.md) | ✓ |
|
|
97
|
+
| [`@dxos/keys`](../../../../common/keys/docs/README.md) | ✓ |
|
|
98
|
+
| [`@dxos/log`](../../../../common/log/docs/README.md) | ✓ |
|
|
99
|
+
| [`@dxos/mesh-protocol`](../../../mesh/mesh-protocol/docs/README.md) | ✓ |
|
|
100
|
+
| [`@dxos/protocols`](../../../protocols/docs/README.md) | ✓ |
|
|
101
|
+
| [`@dxos/random-access-storage`](../../../../common/random-access-storage/docs/README.md) | |
|
|
102
|
+
| [`@dxos/timeframe`](../../../../common/timeframe/docs/README.md) | |
|
|
103
|
+
| [`@dxos/util`](../../../../common/util/docs/README.md) | ✓ |
|
package/moon.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/credentials",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.bcb3aa67d6",
|
|
4
4
|
"description": "DXOS Credentials",
|
|
5
5
|
"homepage": "https://github.com/dxos/dxos/tree/main/packages/core/halo/credentials#readme",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
".": {
|
|
17
17
|
"source": "./src/index.ts",
|
|
18
18
|
"types": "./dist/types/src/index.d.ts",
|
|
19
|
-
"
|
|
20
|
-
"node": "./dist/lib/node-esm/index.mjs"
|
|
19
|
+
"default": "./dist/lib/neutral/index.mjs"
|
|
21
20
|
}
|
|
22
21
|
},
|
|
23
22
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -27,17 +26,17 @@
|
|
|
27
26
|
"dependencies": {
|
|
28
27
|
"bip39": "^3.1.0",
|
|
29
28
|
"json-stable-stringify": "^1.3.0",
|
|
30
|
-
"@dxos/
|
|
31
|
-
"@dxos/
|
|
32
|
-
"@dxos/crypto": "0.8.4-main.
|
|
33
|
-
"@dxos/invariant": "0.8.4-main.
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
29
|
+
"@dxos/context": "0.8.4-main.bcb3aa67d6",
|
|
30
|
+
"@dxos/async": "0.8.4-main.bcb3aa67d6",
|
|
31
|
+
"@dxos/crypto": "0.8.4-main.bcb3aa67d6",
|
|
32
|
+
"@dxos/invariant": "0.8.4-main.bcb3aa67d6",
|
|
33
|
+
"@dxos/keyring": "0.8.4-main.bcb3aa67d6",
|
|
34
|
+
"@dxos/keys": "0.8.4-main.bcb3aa67d6",
|
|
35
|
+
"@dxos/log": "0.8.4-main.bcb3aa67d6",
|
|
36
|
+
"@dxos/protocols": "0.8.4-main.bcb3aa67d6",
|
|
37
|
+
"@dxos/node-std": "0.8.4-main.bcb3aa67d6",
|
|
38
|
+
"@dxos/timeframe": "0.8.4-main.bcb3aa67d6",
|
|
39
|
+
"@dxos/util": "0.8.4-main.bcb3aa67d6"
|
|
41
40
|
},
|
|
42
41
|
"publishConfig": {
|
|
43
42
|
"access": "public"
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
AdmittedFeed,
|
|
11
11
|
type Credential,
|
|
12
12
|
type DeviceProfileDocument,
|
|
13
|
+
MembershipPolicy,
|
|
13
14
|
type ProfileDocument,
|
|
14
15
|
SpaceMember,
|
|
15
16
|
} from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
@@ -38,6 +39,7 @@ export class CredentialGenerator {
|
|
|
38
39
|
spaceKey: PublicKey,
|
|
39
40
|
controlKey: PublicKey,
|
|
40
41
|
creatorProfile?: ProfileDocument,
|
|
42
|
+
membershipPolicy: MembershipPolicy = MembershipPolicy.INVITE,
|
|
41
43
|
): Promise<Credential[]> {
|
|
42
44
|
return [
|
|
43
45
|
await createCredential({
|
|
@@ -47,6 +49,7 @@ export class CredentialGenerator {
|
|
|
47
49
|
assertion: {
|
|
48
50
|
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
49
51
|
spaceKey,
|
|
52
|
+
membershipPolicy,
|
|
50
53
|
},
|
|
51
54
|
}),
|
|
52
55
|
|
|
@@ -221,6 +224,7 @@ export const createAdmissionCredentials = async (
|
|
|
221
224
|
membershipChainHeads: PublicKey[] = [],
|
|
222
225
|
profile?: ProfileDocument,
|
|
223
226
|
invitationCredentialId?: PublicKey,
|
|
227
|
+
tags?: string[],
|
|
224
228
|
): Promise<FeedMessage.Payload[]> => {
|
|
225
229
|
const credentials = await Promise.all([
|
|
226
230
|
await signer.createCredential({
|
|
@@ -233,6 +237,7 @@ export const createAdmissionCredentials = async (
|
|
|
233
237
|
profile,
|
|
234
238
|
genesisFeedKey,
|
|
235
239
|
invitationCredentialId,
|
|
240
|
+
tags: tags ?? [],
|
|
236
241
|
},
|
|
237
242
|
}),
|
|
238
243
|
]);
|
|
@@ -27,6 +27,18 @@ export const getCredentialProofPayload = (credential: Credential): Uint8Array =>
|
|
|
27
27
|
}
|
|
28
28
|
delete copy.id; // ID is not part of the signature payload.
|
|
29
29
|
|
|
30
|
+
// Normalize empty repeated fields in the assertion to avoid proto3 serialization asymmetry.
|
|
31
|
+
// Proto3 does not encode empty repeated fields, but the codec may restore them as [] after deserialization,
|
|
32
|
+
// causing a signature mismatch if the original signing payload did not include the field.
|
|
33
|
+
const assertion = (copy.subject as any)?.assertion;
|
|
34
|
+
if (assertion) {
|
|
35
|
+
for (const key of Object.keys(assertion)) {
|
|
36
|
+
if (Array.isArray(assertion[key]) && assertion[key].length === 0) {
|
|
37
|
+
delete assertion[key];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
return Buffer.from(canonicalStringify(copy));
|
|
31
43
|
};
|
|
32
44
|
|
|
@@ -6,7 +6,7 @@ import { describe, expect, test } from 'vitest';
|
|
|
6
6
|
|
|
7
7
|
import { Keyring } from '@dxos/keyring';
|
|
8
8
|
import { type PublicKey } from '@dxos/keys';
|
|
9
|
-
import { AdmittedFeed, type Chain, SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
9
|
+
import { AdmittedFeed, type Chain, MembershipPolicy, SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
10
10
|
|
|
11
11
|
import { createCredential, verifyCredential } from '../credentials';
|
|
12
12
|
|
|
@@ -30,6 +30,7 @@ describe('SpaceStateMachine', () => {
|
|
|
30
30
|
assertion: {
|
|
31
31
|
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
32
32
|
spaceKey: space,
|
|
33
|
+
membershipPolicy: MembershipPolicy.INVITE,
|
|
33
34
|
},
|
|
34
35
|
signer: keyring,
|
|
35
36
|
}),
|
|
@@ -130,6 +131,7 @@ describe('SpaceStateMachine', () => {
|
|
|
130
131
|
assertion: {
|
|
131
132
|
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
132
133
|
spaceKey: space,
|
|
134
|
+
membershipPolicy: MembershipPolicy.INVITE,
|
|
133
135
|
},
|
|
134
136
|
signer: keyring,
|
|
135
137
|
}),
|
|
@@ -211,6 +213,60 @@ describe('SpaceStateMachine', () => {
|
|
|
211
213
|
expect(spaceState.credentials).toHaveLength(3);
|
|
212
214
|
});
|
|
213
215
|
|
|
216
|
+
test('space genesis with tags', async () => {
|
|
217
|
+
const keyring = new Keyring();
|
|
218
|
+
const space = await keyring.createKey();
|
|
219
|
+
const identity = await keyring.createKey();
|
|
220
|
+
const feed = await keyring.createKey();
|
|
221
|
+
|
|
222
|
+
const spaceState = new SpaceStateMachine(space);
|
|
223
|
+
|
|
224
|
+
expect(spaceState.tags).toEqual([]);
|
|
225
|
+
|
|
226
|
+
expect(
|
|
227
|
+
await spaceState.process(
|
|
228
|
+
await createCredential({
|
|
229
|
+
issuer: space,
|
|
230
|
+
subject: space,
|
|
231
|
+
assertion: {
|
|
232
|
+
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
233
|
+
spaceKey: space,
|
|
234
|
+
tags: ['personal', 'test'],
|
|
235
|
+
membershipPolicy: MembershipPolicy.INVITE,
|
|
236
|
+
},
|
|
237
|
+
signer: keyring,
|
|
238
|
+
}),
|
|
239
|
+
{ sourceFeed: feed },
|
|
240
|
+
),
|
|
241
|
+
).toEqual(true);
|
|
242
|
+
|
|
243
|
+
expect(spaceState.tags).toEqual(['personal', 'test']);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('space genesis without tags returns empty array', async () => {
|
|
247
|
+
const keyring = new Keyring();
|
|
248
|
+
const space = await keyring.createKey();
|
|
249
|
+
const feed = await keyring.createKey();
|
|
250
|
+
|
|
251
|
+
const spaceState = new SpaceStateMachine(space);
|
|
252
|
+
|
|
253
|
+
await spaceState.process(
|
|
254
|
+
await createCredential({
|
|
255
|
+
issuer: space,
|
|
256
|
+
subject: space,
|
|
257
|
+
assertion: {
|
|
258
|
+
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
259
|
+
spaceKey: space,
|
|
260
|
+
membershipPolicy: MembershipPolicy.INVITE,
|
|
261
|
+
},
|
|
262
|
+
signer: keyring,
|
|
263
|
+
}),
|
|
264
|
+
{ sourceFeed: feed },
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(spaceState.tags).toEqual([]);
|
|
268
|
+
});
|
|
269
|
+
|
|
214
270
|
test('storing device credentials and building a chain', async () => {
|
|
215
271
|
const keyring = new Keyring();
|
|
216
272
|
const haloSpace = await keyring.createKey();
|
|
@@ -230,6 +286,7 @@ describe('SpaceStateMachine', () => {
|
|
|
230
286
|
assertion: {
|
|
231
287
|
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
232
288
|
spaceKey: haloSpace,
|
|
289
|
+
membershipPolicy: MembershipPolicy.INVITE,
|
|
233
290
|
},
|
|
234
291
|
signer: keyring,
|
|
235
292
|
}),
|
|
@@ -339,4 +396,189 @@ describe('SpaceStateMachine', () => {
|
|
|
339
396
|
|
|
340
397
|
expect(await verifyCredential(credential)).toEqual({ kind: 'pass' });
|
|
341
398
|
});
|
|
399
|
+
|
|
400
|
+
test('space genesis with membership policy', async () => {
|
|
401
|
+
const keyring = new Keyring();
|
|
402
|
+
const space = await keyring.createKey();
|
|
403
|
+
const feed = await keyring.createKey();
|
|
404
|
+
|
|
405
|
+
const spaceState = new SpaceStateMachine(space);
|
|
406
|
+
|
|
407
|
+
await spaceState.process(
|
|
408
|
+
await createCredential({
|
|
409
|
+
issuer: space,
|
|
410
|
+
subject: space,
|
|
411
|
+
assertion: {
|
|
412
|
+
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
413
|
+
spaceKey: space,
|
|
414
|
+
membershipPolicy: MembershipPolicy.LOCKED,
|
|
415
|
+
},
|
|
416
|
+
signer: keyring,
|
|
417
|
+
}),
|
|
418
|
+
{ sourceFeed: feed },
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
expect(spaceState.membershipPolicy).toEqual(MembershipPolicy.LOCKED);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test('space genesis without membership policy defaults to INVITE', async () => {
|
|
425
|
+
const keyring = new Keyring();
|
|
426
|
+
const space = await keyring.createKey();
|
|
427
|
+
const feed = await keyring.createKey();
|
|
428
|
+
|
|
429
|
+
const spaceState = new SpaceStateMachine(space);
|
|
430
|
+
|
|
431
|
+
// MembershipPolicy.INVITE is the proto zero-value default.
|
|
432
|
+
// Existing spaces without the field will have this value.
|
|
433
|
+
await spaceState.process(
|
|
434
|
+
await createCredential({
|
|
435
|
+
issuer: space,
|
|
436
|
+
subject: space,
|
|
437
|
+
assertion: {
|
|
438
|
+
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
439
|
+
spaceKey: space,
|
|
440
|
+
membershipPolicy: MembershipPolicy.INVITE,
|
|
441
|
+
},
|
|
442
|
+
signer: keyring,
|
|
443
|
+
}),
|
|
444
|
+
{ sourceFeed: feed },
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
expect(spaceState.membershipPolicy).toEqual(MembershipPolicy.INVITE);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test('locked space rejects new members', async ({ expect }) => {
|
|
451
|
+
const keyring = new Keyring();
|
|
452
|
+
const space = await keyring.createKey();
|
|
453
|
+
const identity = await keyring.createKey();
|
|
454
|
+
const identity2 = await keyring.createKey();
|
|
455
|
+
const feed = await keyring.createKey();
|
|
456
|
+
|
|
457
|
+
const spaceState = new SpaceStateMachine(space);
|
|
458
|
+
|
|
459
|
+
await spaceState.process(
|
|
460
|
+
await createCredential({
|
|
461
|
+
issuer: space,
|
|
462
|
+
subject: space,
|
|
463
|
+
assertion: {
|
|
464
|
+
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
465
|
+
spaceKey: space,
|
|
466
|
+
membershipPolicy: MembershipPolicy.LOCKED,
|
|
467
|
+
},
|
|
468
|
+
signer: keyring,
|
|
469
|
+
}),
|
|
470
|
+
{ sourceFeed: feed },
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
await spaceState.process(
|
|
474
|
+
await createCredential({
|
|
475
|
+
issuer: space,
|
|
476
|
+
subject: identity,
|
|
477
|
+
assertion: {
|
|
478
|
+
'@type': 'dxos.halo.credentials.SpaceMember',
|
|
479
|
+
spaceKey: space,
|
|
480
|
+
role: SpaceMember.Role.OWNER,
|
|
481
|
+
genesisFeedKey: feed,
|
|
482
|
+
},
|
|
483
|
+
signer: keyring,
|
|
484
|
+
}),
|
|
485
|
+
{ sourceFeed: feed },
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// Attempt to add a second member — should be rejected.
|
|
489
|
+
expect(
|
|
490
|
+
await spaceState.process(
|
|
491
|
+
await createCredential({
|
|
492
|
+
issuer: space,
|
|
493
|
+
subject: identity2,
|
|
494
|
+
assertion: {
|
|
495
|
+
'@type': 'dxos.halo.credentials.SpaceMember',
|
|
496
|
+
spaceKey: space,
|
|
497
|
+
role: SpaceMember.Role.EDITOR,
|
|
498
|
+
genesisFeedKey: feed,
|
|
499
|
+
},
|
|
500
|
+
signer: keyring,
|
|
501
|
+
}),
|
|
502
|
+
{ sourceFeed: feed },
|
|
503
|
+
),
|
|
504
|
+
).toEqual(false);
|
|
505
|
+
|
|
506
|
+
expect(spaceState.members.size).toEqual(1);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test('locked space allows admitted feeds for existing members', async ({ expect }) => {
|
|
510
|
+
const keyring = new Keyring();
|
|
511
|
+
const space = await keyring.createKey();
|
|
512
|
+
const identity = await keyring.createKey();
|
|
513
|
+
const device = await keyring.createKey();
|
|
514
|
+
const feed = await keyring.createKey();
|
|
515
|
+
const newFeed = await keyring.createKey();
|
|
516
|
+
|
|
517
|
+
const spaceState = new SpaceStateMachine(space);
|
|
518
|
+
|
|
519
|
+
await spaceState.process(
|
|
520
|
+
await createCredential({
|
|
521
|
+
issuer: space,
|
|
522
|
+
subject: space,
|
|
523
|
+
assertion: {
|
|
524
|
+
'@type': 'dxos.halo.credentials.SpaceGenesis',
|
|
525
|
+
spaceKey: space,
|
|
526
|
+
membershipPolicy: MembershipPolicy.LOCKED,
|
|
527
|
+
},
|
|
528
|
+
signer: keyring,
|
|
529
|
+
}),
|
|
530
|
+
{ sourceFeed: feed },
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
await spaceState.process(
|
|
534
|
+
await createCredential({
|
|
535
|
+
issuer: space,
|
|
536
|
+
subject: identity,
|
|
537
|
+
assertion: {
|
|
538
|
+
'@type': 'dxos.halo.credentials.SpaceMember',
|
|
539
|
+
spaceKey: space,
|
|
540
|
+
role: SpaceMember.Role.OWNER,
|
|
541
|
+
genesisFeedKey: feed,
|
|
542
|
+
},
|
|
543
|
+
signer: keyring,
|
|
544
|
+
}),
|
|
545
|
+
{ sourceFeed: feed },
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
const chain: Chain = {
|
|
549
|
+
credential: await createCredential({
|
|
550
|
+
assertion: {
|
|
551
|
+
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
552
|
+
deviceKey: device,
|
|
553
|
+
identityKey: identity,
|
|
554
|
+
},
|
|
555
|
+
subject: device,
|
|
556
|
+
issuer: identity,
|
|
557
|
+
signer: keyring,
|
|
558
|
+
}),
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
// AdmittedFeed should still work on locked space.
|
|
562
|
+
expect(
|
|
563
|
+
await spaceState.process(
|
|
564
|
+
await createCredential({
|
|
565
|
+
issuer: identity,
|
|
566
|
+
subject: newFeed,
|
|
567
|
+
assertion: {
|
|
568
|
+
'@type': 'dxos.halo.credentials.AdmittedFeed',
|
|
569
|
+
spaceKey: space,
|
|
570
|
+
identityKey: identity,
|
|
571
|
+
deviceKey: device,
|
|
572
|
+
designation: AdmittedFeed.Designation.CONTROL,
|
|
573
|
+
},
|
|
574
|
+
signer: keyring,
|
|
575
|
+
signingKey: device,
|
|
576
|
+
chain,
|
|
577
|
+
}),
|
|
578
|
+
{ sourceFeed: feed },
|
|
579
|
+
),
|
|
580
|
+
).toEqual(true);
|
|
581
|
+
|
|
582
|
+
expect(spaceState.feeds.size).toEqual(1);
|
|
583
|
+
});
|
|
342
584
|
});
|
|
@@ -7,7 +7,7 @@ import { Context } from '@dxos/context';
|
|
|
7
7
|
import { PublicKey } from '@dxos/keys';
|
|
8
8
|
import { log } from '@dxos/log';
|
|
9
9
|
import { type TypedMessage } from '@dxos/protocols/proto';
|
|
10
|
-
import { type Credential, SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
10
|
+
import { type Credential, MembershipPolicy, SpaceMember } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
11
11
|
import { type DelegateSpaceInvitation } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
12
12
|
import { type AsyncCallback, Callback, ComplexMap, ComplexSet } from '@dxos/util';
|
|
13
13
|
|
|
@@ -24,6 +24,8 @@ export interface SpaceState {
|
|
|
24
24
|
readonly feeds: ReadonlyMap<PublicKey, FeedInfo>;
|
|
25
25
|
readonly credentials: Credential[];
|
|
26
26
|
readonly genesisCredential: Credential | undefined;
|
|
27
|
+
readonly tags: string[];
|
|
28
|
+
readonly membershipPolicy: MembershipPolicy;
|
|
27
29
|
readonly creator: MemberInfo | undefined;
|
|
28
30
|
readonly invitations: ReadonlyMap<PublicKey, DelegateSpaceInvitation>;
|
|
29
31
|
|
|
@@ -61,6 +63,8 @@ export class SpaceStateMachine implements SpaceState {
|
|
|
61
63
|
private readonly _processedCredentials = new ComplexSet<PublicKey>(PublicKey.hash);
|
|
62
64
|
|
|
63
65
|
private _genesisCredential: Credential | undefined;
|
|
66
|
+
private _tags: string[] = [];
|
|
67
|
+
private _membershipPolicy: MembershipPolicy = MembershipPolicy.INVITE;
|
|
64
68
|
private _credentialProcessors: CredentialConsumer<any>[] = [];
|
|
65
69
|
|
|
66
70
|
readonly onCredentialProcessed = new Callback<AsyncCallback<Credential>>();
|
|
@@ -104,6 +108,14 @@ export class SpaceStateMachine implements SpaceState {
|
|
|
104
108
|
return this._genesisCredential;
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
get tags(): string[] {
|
|
112
|
+
return this._tags;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get membershipPolicy(): MembershipPolicy {
|
|
116
|
+
return this._membershipPolicy;
|
|
117
|
+
}
|
|
118
|
+
|
|
107
119
|
get invitations(): ReadonlyMap<PublicKey, DelegateSpaceInvitation> {
|
|
108
120
|
return this._invitations.invitations;
|
|
109
121
|
}
|
|
@@ -180,6 +192,8 @@ export class SpaceStateMachine implements SpaceState {
|
|
|
180
192
|
return false;
|
|
181
193
|
}
|
|
182
194
|
this._genesisCredential = credential;
|
|
195
|
+
this._tags = assertion.tags ?? [];
|
|
196
|
+
this._membershipPolicy = assertion.membershipPolicy ?? MembershipPolicy.INVITE;
|
|
183
197
|
break;
|
|
184
198
|
}
|
|
185
199
|
|
|
@@ -260,6 +274,11 @@ export class SpaceStateMachine implements SpaceState {
|
|
|
260
274
|
}
|
|
261
275
|
|
|
262
276
|
private _canInviteNewMembers(key: PublicKey): boolean {
|
|
277
|
+
if (this._membershipPolicy === MembershipPolicy.LOCKED) {
|
|
278
|
+
// When locked, only the space key can add the initial owner during genesis.
|
|
279
|
+
// Once a member exists, no new members can be added.
|
|
280
|
+
return key.equals(this._spaceKey) && this._members.members.size === 0;
|
|
281
|
+
}
|
|
263
282
|
return (
|
|
264
283
|
key.equals(this._spaceKey) ||
|
|
265
284
|
this._members.getRole(key) === SpaceMember.Role.ADMIN ||
|