@dxos/client-services 0.6.13 → 0.6.14-main.7bd9c89
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/{chunk-CRXXOI45.mjs → chunk-URZYQU7T.mjs} +6383 -5185
- package/dist/lib/browser/chunk-URZYQU7T.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +7 -3
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +12 -7
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/{chunk-PZ3JJJ3K.cjs → chunk-APHU7U5B.cjs} +6294 -5106
- package/dist/lib/node/chunk-APHU7U5B.cjs.map +7 -0
- package/dist/lib/node/index.cjs +50 -46
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs +18 -13
- package/dist/lib/node/testing/index.cjs.map +3 -3
- package/dist/lib/node-esm/chunk-L5NEKNEY.mjs +8914 -0
- package/dist/lib/node-esm/chunk-L5NEKNEY.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +420 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/testing/index.mjs +424 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/packlets/agents/edge-agent-manager.d.ts +35 -0
- package/dist/types/src/packlets/agents/edge-agent-manager.d.ts.map +1 -0
- package/dist/types/src/packlets/agents/edge-agent-service.d.ts +10 -0
- package/dist/types/src/packlets/agents/edge-agent-service.d.ts.map +1 -0
- package/dist/types/src/packlets/agents/index.d.ts +3 -0
- package/dist/types/src/packlets/agents/index.d.ts.map +1 -0
- package/dist/types/src/packlets/diagnostics/diagnostics-broadcast.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts +2 -0
- package/dist/types/src/packlets/identity/authenticator.node.test.d.ts.map +1 -0
- package/dist/types/src/packlets/identity/contacts-service.d.ts +1 -1
- package/dist/types/src/packlets/identity/contacts-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +28 -9
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts +18 -0
- package/dist/types/src/packlets/identity/identity-recovery-manager.d.ts.map +1 -0
- package/dist/types/src/packlets/identity/identity-service.d.ts +7 -2
- package/dist/types/src/packlets/identity/identity-service.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +12 -3
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/device-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts +30 -0
- package/dist/types/src/packlets/invitations/edge-invitation-handler.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts +2 -1
- package/dist/types/src/packlets/invitations/invitation-guest-extenstion.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts +2 -1
- package/dist/types/src/packlets/invitations/invitation-host-extension.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitation-state.d.ts +19 -0
- package/dist/types/src/packlets/invitations/invitation-state.d.ts.map +1 -0
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts +8 -8
- package/dist/types/src/packlets/invitations/invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitation-protocol.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +14 -9
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +2 -0
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +7 -3
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +5 -3
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts +3 -0
- package/dist/types/src/packlets/spaces/edge-feed-replicator.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts +2 -0
- package/dist/types/src/packlets/spaces/edge-feed-replicator.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts +1 -1
- package/dist/types/src/packlets/spaces/epoch-migrations.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +35 -6
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +1 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/storage/storage.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/test-builder.d.ts +1 -2
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/worker/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/testing/setup.d.ts +3 -0
- package/dist/types/src/testing/setup.d.ts.map +1 -0
- package/dist/types/src/version.d.ts +1 -1
- package/dist/types/src/version.d.ts.map +1 -1
- package/package.json +43 -39
- package/src/index.ts +1 -0
- package/src/packlets/agents/edge-agent-manager.ts +163 -0
- package/src/packlets/agents/edge-agent-service.ts +42 -0
- package/src/packlets/agents/index.ts +6 -0
- package/src/packlets/devices/devices-service.test.ts +4 -5
- package/src/packlets/diagnostics/diagnostics-broadcast.ts +1 -0
- package/src/packlets/identity/{authenticator.test.ts → authenticator.node.test.ts} +2 -3
- package/src/packlets/identity/authenticator.ts +5 -2
- package/src/packlets/identity/contacts-service.ts +1 -1
- package/src/packlets/identity/identity-manager.test.ts +31 -16
- package/src/packlets/identity/identity-manager.ts +76 -32
- package/src/packlets/identity/identity-recovery-manager.ts +95 -0
- package/src/packlets/identity/identity-service.test.ts +5 -8
- package/src/packlets/identity/identity-service.ts +11 -5
- package/src/packlets/identity/identity.test.ts +130 -239
- package/src/packlets/identity/identity.ts +60 -17
- package/src/packlets/invitations/device-invitation-protocol.test.ts +7 -4
- package/src/packlets/invitations/device-invitation-protocol.ts +5 -1
- package/src/packlets/invitations/edge-invitation-handler.ts +185 -0
- package/src/packlets/invitations/invitation-guest-extenstion.ts +8 -4
- package/src/packlets/invitations/invitation-host-extension.ts +8 -7
- package/src/packlets/invitations/invitation-state.ts +111 -0
- package/src/packlets/invitations/invitations-handler.test.ts +16 -9
- package/src/packlets/invitations/invitations-handler.ts +23 -93
- package/src/packlets/invitations/space-invitation-protocol.test.ts +4 -3
- package/src/packlets/invitations/space-invitation-protocol.ts +4 -0
- package/src/packlets/logging/logging.test.ts +1 -2
- package/src/packlets/network/network-service.test.ts +2 -3
- package/src/packlets/services/service-context.test.ts +3 -1
- package/src/packlets/services/service-context.ts +110 -35
- package/src/packlets/services/service-host.test.ts +8 -12
- package/src/packlets/services/service-host.ts +25 -7
- package/src/packlets/services/service-registry.test.ts +1 -2
- package/src/packlets/spaces/data-space-manager.test.ts +2 -2
- package/src/packlets/spaces/data-space-manager.ts +44 -7
- package/src/packlets/spaces/data-space.ts +37 -6
- package/src/packlets/spaces/edge-feed-replicator.test.ts +252 -0
- package/src/packlets/spaces/edge-feed-replicator.ts +80 -22
- package/src/packlets/spaces/epoch-migrations.ts +2 -2
- package/src/packlets/spaces/notarization-plugin.test.ts +10 -7
- package/src/packlets/spaces/notarization-plugin.ts +196 -29
- package/src/packlets/spaces/spaces-service.test.ts +5 -9
- package/src/packlets/spaces/spaces-service.ts +6 -1
- package/src/packlets/storage/storage.ts +0 -1
- package/src/packlets/system/system-service.test.ts +1 -2
- package/src/packlets/testing/test-builder.ts +7 -4
- package/src/packlets/worker/worker-runtime.ts +2 -2
- package/src/testing/setup.ts +11 -0
- package/src/version.ts +1 -5
- package/dist/lib/browser/chunk-CRXXOI45.mjs.map +0 -7
- package/dist/lib/node/chunk-PZ3JJJ3K.cjs.map +0 -7
- package/dist/types/src/packlets/identity/authenticator.test.d.ts +0 -2
- package/dist/types/src/packlets/identity/authenticator.test.d.ts.map +0 -1
- package/dist/types/src/packlets/services/automerge-host.test.d.ts +0 -2
- package/dist/types/src/packlets/services/automerge-host.test.d.ts.map +0 -1
- package/src/packlets/services/automerge-host.test.ts +0 -60
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import expect from '
|
|
5
|
+
import { onTestFinished, describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
7
|
+
import { Event } from '@dxos/async';
|
|
7
8
|
import { Context } from '@dxos/context';
|
|
8
9
|
import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
|
|
9
10
|
import {
|
|
@@ -15,7 +16,9 @@ import {
|
|
|
15
16
|
SpaceProtocol,
|
|
16
17
|
valueEncoding,
|
|
17
18
|
} from '@dxos/echo-pipeline';
|
|
19
|
+
import { type EdgeConnection, type MessageListener } from '@dxos/edge-client';
|
|
18
20
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
21
|
+
import { type FeedWrapper } from '@dxos/feed-store';
|
|
19
22
|
import { Keyring } from '@dxos/keyring';
|
|
20
23
|
import { type PublicKey } from '@dxos/keys';
|
|
21
24
|
import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
|
|
@@ -24,7 +27,6 @@ import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
|
24
27
|
import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
25
28
|
import { createStorage, StorageType } from '@dxos/random-access-storage';
|
|
26
29
|
import { BlobStore } from '@dxos/teleport-extension-object-sync';
|
|
27
|
-
import { afterTest, describe, test } from '@dxos/test';
|
|
28
30
|
|
|
29
31
|
import { Identity } from './identity';
|
|
30
32
|
|
|
@@ -42,16 +44,108 @@ const createStores = () => {
|
|
|
42
44
|
|
|
43
45
|
describe('identity/identity', () => {
|
|
44
46
|
test('create', async () => {
|
|
47
|
+
const setup = await setupIdentity();
|
|
48
|
+
|
|
49
|
+
await writeGenesisCredential(setup);
|
|
50
|
+
|
|
51
|
+
// Wait for identity to be ready.
|
|
52
|
+
await setup.identity.ready();
|
|
53
|
+
const identitySigner = setup.identity.getIdentityCredentialSigner();
|
|
54
|
+
const credential = await identitySigner.createCredential({
|
|
55
|
+
subject: setup.identityKey,
|
|
56
|
+
assertion: {
|
|
57
|
+
'@type': 'dxos.halo.credentials.IdentityProfile',
|
|
58
|
+
profile: {
|
|
59
|
+
displayName: 'Alice',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(credential.issuer).toEqual(setup.identityKey);
|
|
65
|
+
expect(await verifyCredential(credential)).toEqual({ kind: 'pass' });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('two devices', async () => {
|
|
69
|
+
const signalContext = new MemorySignalManagerContext();
|
|
70
|
+
|
|
71
|
+
const owner = await setupIdentity({ signalContext });
|
|
72
|
+
await writeGenesisCredential(owner);
|
|
73
|
+
await owner.identity.ready();
|
|
74
|
+
|
|
75
|
+
const secondDevice = (
|
|
76
|
+
await setupIdentity({
|
|
77
|
+
signalContext,
|
|
78
|
+
spaceKey: owner.spaceKey,
|
|
79
|
+
identityKey: owner.identityKey,
|
|
80
|
+
genesisFeedKey: owner.controlFeed.key,
|
|
81
|
+
})
|
|
82
|
+
).identity;
|
|
83
|
+
|
|
84
|
+
//
|
|
85
|
+
// Second device admission
|
|
86
|
+
//
|
|
87
|
+
{
|
|
88
|
+
const signer = owner.identity.getIdentityCredentialSigner();
|
|
89
|
+
void owner.identity.controlPipeline.writer.write({
|
|
90
|
+
credential: {
|
|
91
|
+
credential: await signer.createCredential({
|
|
92
|
+
subject: secondDevice.deviceKey,
|
|
93
|
+
assertion: {
|
|
94
|
+
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
95
|
+
identityKey: owner.identityKey,
|
|
96
|
+
deviceKey: secondDevice.deviceKey,
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await secondDevice.ready();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
expect(Array.from(owner.identity.authorizedDeviceKeys.keys())).toEqual([owner.deviceKey, secondDevice.deviceKey]);
|
|
106
|
+
expect(Array.from(secondDevice.authorizedDeviceKeys.keys())).toEqual([owner.deviceKey, secondDevice.deviceKey]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('edge feed replicator', async () => {
|
|
110
|
+
let replicationStarted = false;
|
|
111
|
+
const connectedEvent = new Event();
|
|
112
|
+
const setup = await setupIdentity({
|
|
113
|
+
edgeConnection: {
|
|
114
|
+
connected: connectedEvent,
|
|
115
|
+
open: async () => {},
|
|
116
|
+
close: async () => {},
|
|
117
|
+
addListener: (_: MessageListener): (() => void) => {
|
|
118
|
+
return () => {};
|
|
119
|
+
},
|
|
120
|
+
send: async (_) => {
|
|
121
|
+
replicationStarted = true;
|
|
122
|
+
},
|
|
123
|
+
} as EdgeConnection,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await writeGenesisCredential(setup);
|
|
127
|
+
connectedEvent.emit();
|
|
128
|
+
|
|
129
|
+
await expect.poll(() => replicationStarted).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const setupIdentity = async (args?: {
|
|
133
|
+
signalContext?: MemorySignalManagerContext;
|
|
134
|
+
spaceKey?: PublicKey;
|
|
135
|
+
identityKey?: PublicKey;
|
|
136
|
+
genesisFeedKey?: PublicKey;
|
|
137
|
+
edgeConnection?: EdgeConnection;
|
|
138
|
+
}): Promise<TestIdentitySetup> => {
|
|
45
139
|
const { storage, metadataStore, blobStore } = createStores();
|
|
46
140
|
|
|
47
141
|
const keyring = new Keyring();
|
|
48
|
-
const identityKey = await keyring.createKey();
|
|
49
142
|
const deviceKey = await keyring.createKey();
|
|
50
|
-
const
|
|
143
|
+
const identityKey = args?.identityKey ?? (await keyring.createKey());
|
|
144
|
+
const spaceKey = args?.spaceKey ?? (await keyring.createKey());
|
|
51
145
|
|
|
52
146
|
const feedStore = new FeedStore<FeedMessage>({
|
|
53
147
|
factory: new FeedFactory<FeedMessage>({
|
|
54
|
-
root: storage.createDirectory(
|
|
148
|
+
root: storage.createDirectory(),
|
|
55
149
|
signer: keyring,
|
|
56
150
|
hypercore: {
|
|
57
151
|
valueEncoding,
|
|
@@ -77,8 +171,9 @@ describe('identity/identity', () => {
|
|
|
77
171
|
},
|
|
78
172
|
blobStore,
|
|
79
173
|
networkManager: new SwarmNetworkManager({
|
|
80
|
-
signalManager: new MemorySignalManager(new MemorySignalManagerContext()),
|
|
174
|
+
signalManager: new MemorySignalManager(args?.signalContext ?? new MemorySignalManagerContext()),
|
|
81
175
|
transportFactory: MemoryTransportFactory,
|
|
176
|
+
peerInfo: { identityKey: identityKey.toHex(), peerKey: deviceKey.toHex() },
|
|
82
177
|
}),
|
|
83
178
|
});
|
|
84
179
|
|
|
@@ -87,7 +182,7 @@ describe('identity/identity', () => {
|
|
|
87
182
|
id: await createIdFromSpaceKey(spaceKey),
|
|
88
183
|
spaceKey,
|
|
89
184
|
protocol,
|
|
90
|
-
genesisFeed: controlFeed,
|
|
185
|
+
genesisFeed: args?.genesisFeedKey ? await feedStore.openFeed(args.genesisFeedKey) : controlFeed,
|
|
91
186
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
92
187
|
memberKey: identityKey,
|
|
93
188
|
metadataStore,
|
|
@@ -103,242 +198,38 @@ describe('identity/identity', () => {
|
|
|
103
198
|
identityKey,
|
|
104
199
|
deviceKey,
|
|
105
200
|
space,
|
|
201
|
+
edgeFeatures: args?.edgeConnection && { feedReplicator: true },
|
|
202
|
+
edgeConnection: args?.edgeConnection,
|
|
106
203
|
});
|
|
107
204
|
|
|
108
205
|
await identity.open(new Context());
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
{
|
|
115
|
-
const generator = new CredentialGenerator(keyring, identityKey, deviceKey);
|
|
116
|
-
const credentials = [
|
|
117
|
-
...(await generator.createSpaceGenesis(spaceKey, controlFeed.key)),
|
|
118
|
-
await generator.createDeviceAuthorization(deviceKey),
|
|
119
|
-
await generator.createFeedAdmission(spaceKey, dataFeed.key, AdmittedFeed.Designation.DATA),
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
for (const credential of credentials) {
|
|
123
|
-
await identity.controlPipeline.writer.write({
|
|
124
|
-
credential: { credential },
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Wait for identity to be ready.
|
|
130
|
-
await identity.ready();
|
|
131
|
-
const identitySigner = identity.getIdentityCredentialSigner();
|
|
132
|
-
const credential = await identitySigner.createCredential({
|
|
133
|
-
subject: identityKey,
|
|
134
|
-
assertion: {
|
|
135
|
-
'@type': 'dxos.halo.credentials.IdentityProfile',
|
|
136
|
-
profile: {
|
|
137
|
-
displayName: 'Alice',
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
expect(credential.issuer).toEqual(identityKey);
|
|
143
|
-
expect(await verifyCredential(credential)).toEqual({ kind: 'pass' });
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('two devices', async () => {
|
|
147
|
-
const signalContext = new MemorySignalManagerContext();
|
|
148
|
-
|
|
149
|
-
let spaceKey: PublicKey;
|
|
150
|
-
let identityKey: PublicKey;
|
|
151
|
-
let genesisFeedKey: PublicKey;
|
|
152
|
-
let identity1: Identity;
|
|
153
|
-
let identity2: Identity;
|
|
154
|
-
|
|
155
|
-
//
|
|
156
|
-
// First device
|
|
157
|
-
//
|
|
158
|
-
{
|
|
159
|
-
const { storage, metadataStore, blobStore } = createStores();
|
|
160
|
-
|
|
161
|
-
const keyring = new Keyring();
|
|
162
|
-
identityKey = await keyring.createKey();
|
|
163
|
-
const deviceKey = await keyring.createKey();
|
|
164
|
-
spaceKey = await keyring.createKey();
|
|
165
|
-
|
|
166
|
-
const feedStore = new FeedStore<FeedMessage>({
|
|
167
|
-
factory: new FeedFactory<FeedMessage>({
|
|
168
|
-
root: storage.createDirectory(),
|
|
169
|
-
signer: keyring,
|
|
170
|
-
hypercore: {
|
|
171
|
-
valueEncoding,
|
|
172
|
-
},
|
|
173
|
-
}),
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
const createFeed = async () => {
|
|
177
|
-
const feedKey = await keyring.createKey();
|
|
178
|
-
return feedStore.openFeed(feedKey, { writable: true });
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const controlFeed = await createFeed();
|
|
182
|
-
genesisFeedKey = controlFeed.key;
|
|
183
|
-
|
|
184
|
-
const dataFeed = await createFeed();
|
|
185
|
-
|
|
186
|
-
const protocol = new SpaceProtocol({
|
|
187
|
-
topic: spaceKey,
|
|
188
|
-
swarmIdentity: {
|
|
189
|
-
peerKey: deviceKey,
|
|
190
|
-
identityKey,
|
|
191
|
-
credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
|
|
192
|
-
credentialAuthenticator: MOCK_AUTH_VERIFIER, // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
|
|
193
|
-
},
|
|
194
|
-
blobStore,
|
|
195
|
-
networkManager: new SwarmNetworkManager({
|
|
196
|
-
signalManager: new MemorySignalManager(signalContext),
|
|
197
|
-
transportFactory: MemoryTransportFactory,
|
|
198
|
-
}),
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
await metadataStore.setIdentityRecord({ haloSpace: { key: spaceKey }, identityKey, deviceKey });
|
|
202
|
-
const space = new Space({
|
|
203
|
-
id: await createIdFromSpaceKey(spaceKey),
|
|
204
|
-
spaceKey,
|
|
205
|
-
protocol,
|
|
206
|
-
genesisFeed: controlFeed,
|
|
207
|
-
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
208
|
-
memberKey: identityKey,
|
|
209
|
-
metadataStore,
|
|
210
|
-
onDelegatedInvitationStatusChange: async () => {},
|
|
211
|
-
onMemberRolesChanged: async () => {},
|
|
212
|
-
});
|
|
213
|
-
await space.setControlFeed(controlFeed);
|
|
214
|
-
await space.setDataFeed(dataFeed);
|
|
215
|
-
|
|
216
|
-
const identity = (identity1 = new Identity({
|
|
217
|
-
signer: keyring,
|
|
218
|
-
identityKey,
|
|
219
|
-
deviceKey,
|
|
220
|
-
space,
|
|
221
|
-
}));
|
|
222
|
-
|
|
223
|
-
await identity.open(new Context());
|
|
224
|
-
afterTest(() => identity.close(new Context()));
|
|
225
|
-
|
|
226
|
-
//
|
|
227
|
-
// Identity genesis
|
|
228
|
-
//
|
|
229
|
-
{
|
|
230
|
-
const generator = new CredentialGenerator(keyring, identityKey, deviceKey);
|
|
231
|
-
const credentials = [
|
|
232
|
-
...(await generator.createSpaceGenesis(spaceKey, controlFeed.key)),
|
|
233
|
-
await generator.createDeviceAuthorization(deviceKey),
|
|
234
|
-
await generator.createFeedAdmission(spaceKey, dataFeed.key, AdmittedFeed.Designation.DATA),
|
|
235
|
-
];
|
|
236
|
-
|
|
237
|
-
for (const credential of credentials) {
|
|
238
|
-
await identity.controlPipeline.writer.write({
|
|
239
|
-
credential: { credential },
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Wait for identity to be ready.
|
|
245
|
-
await identity.ready();
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
// Second device
|
|
250
|
-
//
|
|
251
|
-
{
|
|
252
|
-
const { storage, metadataStore, blobStore } = createStores();
|
|
253
|
-
|
|
254
|
-
const keyring = new Keyring();
|
|
255
|
-
const deviceKey = await keyring.createKey();
|
|
256
|
-
|
|
257
|
-
const feedStore = new FeedStore<FeedMessage>({
|
|
258
|
-
factory: new FeedFactory<FeedMessage>({
|
|
259
|
-
root: storage.createDirectory(),
|
|
260
|
-
signer: keyring,
|
|
261
|
-
hypercore: {
|
|
262
|
-
valueEncoding,
|
|
263
|
-
},
|
|
264
|
-
}),
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const createFeed = async () => {
|
|
268
|
-
const feedKey = await keyring.createKey();
|
|
269
|
-
return feedStore.openFeed(feedKey, { writable: true });
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const controlFeed = await createFeed();
|
|
273
|
-
const dataFeed = await createFeed();
|
|
274
|
-
|
|
275
|
-
const protocol = new SpaceProtocol({
|
|
276
|
-
topic: spaceKey,
|
|
277
|
-
swarmIdentity: {
|
|
278
|
-
peerKey: deviceKey,
|
|
279
|
-
identityKey,
|
|
280
|
-
credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
|
|
281
|
-
credentialAuthenticator: MOCK_AUTH_VERIFIER, // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
|
|
282
|
-
},
|
|
283
|
-
blobStore,
|
|
284
|
-
networkManager: new SwarmNetworkManager({
|
|
285
|
-
signalManager: new MemorySignalManager(signalContext),
|
|
286
|
-
transportFactory: MemoryTransportFactory,
|
|
287
|
-
}),
|
|
288
|
-
});
|
|
206
|
+
await identity.joinNetwork();
|
|
207
|
+
onTestFinished(() => identity.close(new Context()));
|
|
208
|
+
return { identity, identityKey, keyring, deviceKey, controlFeed, spaceKey, dataFeed };
|
|
209
|
+
};
|
|
289
210
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
memberKey: identityKey,
|
|
302
|
-
metadataStore,
|
|
303
|
-
onDelegatedInvitationStatusChange: async () => {},
|
|
304
|
-
onMemberRolesChanged: async () => {},
|
|
211
|
+
const writeGenesisCredential = async (setup: TestIdentitySetup) => {
|
|
212
|
+
const generator = new CredentialGenerator(setup.keyring, setup.identityKey, setup.deviceKey);
|
|
213
|
+
const credentials = [
|
|
214
|
+
...(await generator.createSpaceGenesis(setup.spaceKey, setup.controlFeed.key)),
|
|
215
|
+
await generator.createDeviceAuthorization(setup.deviceKey),
|
|
216
|
+
await generator.createFeedAdmission(setup.spaceKey, setup.dataFeed.key, AdmittedFeed.Designation.DATA),
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
for (const credential of credentials) {
|
|
220
|
+
await setup.identity.controlPipeline.writer.write({
|
|
221
|
+
credential: { credential },
|
|
305
222
|
});
|
|
306
|
-
await space.setControlFeed(controlFeed);
|
|
307
|
-
await space.setDataFeed(dataFeed);
|
|
308
|
-
|
|
309
|
-
const identity = (identity2 = new Identity({
|
|
310
|
-
signer: keyring,
|
|
311
|
-
identityKey: identity1.identityKey,
|
|
312
|
-
deviceKey,
|
|
313
|
-
space,
|
|
314
|
-
}));
|
|
315
|
-
|
|
316
|
-
await identity.open(new Context());
|
|
317
|
-
afterTest(() => identity.close(new Context()));
|
|
318
223
|
}
|
|
319
|
-
|
|
320
|
-
//
|
|
321
|
-
// Second device admission
|
|
322
|
-
//
|
|
323
|
-
{
|
|
324
|
-
const signer = identity1.getIdentityCredentialSigner();
|
|
325
|
-
void identity1.controlPipeline.writer.write({
|
|
326
|
-
credential: {
|
|
327
|
-
credential: await signer.createCredential({
|
|
328
|
-
subject: identity2.deviceKey,
|
|
329
|
-
assertion: {
|
|
330
|
-
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
331
|
-
identityKey: identity1.identityKey,
|
|
332
|
-
deviceKey: identity2.deviceKey,
|
|
333
|
-
},
|
|
334
|
-
}),
|
|
335
|
-
},
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
await identity2.ready();
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
expect(Array.from(identity1.authorizedDeviceKeys.keys())).toEqual([identity1.deviceKey, identity2.deviceKey]);
|
|
342
|
-
expect(Array.from(identity2.authorizedDeviceKeys.keys())).toEqual([identity1.deviceKey, identity2.deviceKey]);
|
|
343
|
-
});
|
|
224
|
+
};
|
|
344
225
|
});
|
|
226
|
+
|
|
227
|
+
type TestIdentitySetup = {
|
|
228
|
+
identity: Identity;
|
|
229
|
+
keyring: Keyring;
|
|
230
|
+
identityKey: PublicKey;
|
|
231
|
+
deviceKey: PublicKey;
|
|
232
|
+
spaceKey: PublicKey;
|
|
233
|
+
controlFeed: FeedWrapper<FeedMessage>;
|
|
234
|
+
dataFeed: FeedWrapper<FeedMessage>;
|
|
235
|
+
};
|
|
@@ -14,15 +14,18 @@ import {
|
|
|
14
14
|
} from '@dxos/credentials';
|
|
15
15
|
import { type Signer } from '@dxos/crypto';
|
|
16
16
|
import { type Space } from '@dxos/echo-pipeline';
|
|
17
|
-
import {
|
|
17
|
+
import { type EdgeConnection } from '@dxos/edge-client';
|
|
18
|
+
import { writeMessages, type FeedWrapper } from '@dxos/feed-store';
|
|
18
19
|
import { invariant } from '@dxos/invariant';
|
|
19
20
|
import { PublicKey, type SpaceId } from '@dxos/keys';
|
|
20
21
|
import { log } from '@dxos/log';
|
|
22
|
+
import { type Runtime } from '@dxos/protocols/proto/dxos/config';
|
|
21
23
|
import { type FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
24
|
import {
|
|
23
25
|
AdmittedFeed,
|
|
24
26
|
type DeviceProfileDocument,
|
|
25
27
|
type ProfileDocument,
|
|
28
|
+
type Credential,
|
|
26
29
|
} from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
27
30
|
import { type DeviceAdmissionRequest } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
28
31
|
import { type Presence } from '@dxos/teleport-extension-gossip';
|
|
@@ -32,6 +35,7 @@ import { type ComplexMap, ComplexSet } from '@dxos/util';
|
|
|
32
35
|
|
|
33
36
|
import { TrustedKeySetAuthVerifier } from './authenticator';
|
|
34
37
|
import { DefaultSpaceStateMachine } from './default-space-state-machine';
|
|
38
|
+
import { EdgeFeedReplicator } from '../spaces';
|
|
35
39
|
|
|
36
40
|
export type IdentityParams = {
|
|
37
41
|
identityKey: PublicKey;
|
|
@@ -39,6 +43,9 @@ export type IdentityParams = {
|
|
|
39
43
|
signer: Signer;
|
|
40
44
|
space: Space;
|
|
41
45
|
presence?: Presence;
|
|
46
|
+
|
|
47
|
+
edgeConnection?: EdgeConnection;
|
|
48
|
+
edgeFeatures?: Runtime.Client.EdgeFeatures;
|
|
42
49
|
};
|
|
43
50
|
|
|
44
51
|
/**
|
|
@@ -52,6 +59,8 @@ export class Identity {
|
|
|
52
59
|
private readonly _deviceStateMachine: DeviceStateMachine;
|
|
53
60
|
private readonly _profileStateMachine: ProfileStateMachine;
|
|
54
61
|
private readonly _defaultSpaceStateMachine: DefaultSpaceStateMachine;
|
|
62
|
+
private readonly _edgeFeedReplicator?: EdgeFeedReplicator = undefined;
|
|
63
|
+
|
|
55
64
|
public readonly authVerifier: TrustedKeySetAuthVerifier;
|
|
56
65
|
|
|
57
66
|
public readonly identityKey: PublicKey;
|
|
@@ -59,15 +68,15 @@ export class Identity {
|
|
|
59
68
|
|
|
60
69
|
public readonly stateUpdate = new Event();
|
|
61
70
|
|
|
62
|
-
constructor(
|
|
63
|
-
this.space = space;
|
|
64
|
-
this._signer = signer;
|
|
65
|
-
this._presence = presence;
|
|
71
|
+
constructor(params: IdentityParams) {
|
|
72
|
+
this.space = params.space;
|
|
73
|
+
this._signer = params.signer;
|
|
74
|
+
this._presence = params.presence;
|
|
66
75
|
|
|
67
|
-
this.identityKey = identityKey;
|
|
68
|
-
this.deviceKey = deviceKey;
|
|
76
|
+
this.identityKey = params.identityKey;
|
|
77
|
+
this.deviceKey = params.deviceKey;
|
|
69
78
|
|
|
70
|
-
log.trace('dxos.halo.device', { deviceKey });
|
|
79
|
+
log.trace('dxos.halo.device', { deviceKey: params.deviceKey });
|
|
71
80
|
|
|
72
81
|
this._deviceStateMachine = new DeviceStateMachine({
|
|
73
82
|
identityKey: this.identityKey,
|
|
@@ -88,6 +97,10 @@ export class Identity {
|
|
|
88
97
|
update: this.stateUpdate,
|
|
89
98
|
authTimeout: AUTH_TIMEOUT,
|
|
90
99
|
});
|
|
100
|
+
|
|
101
|
+
if (params.edgeConnection && params.edgeFeatures?.feedReplicator) {
|
|
102
|
+
this._edgeFeedReplicator = new EdgeFeedReplicator({ messenger: params.edgeConnection, spaceId: this.space.id });
|
|
103
|
+
}
|
|
91
104
|
}
|
|
92
105
|
|
|
93
106
|
// TODO(burdon): Expose state object?
|
|
@@ -105,9 +118,17 @@ export class Identity {
|
|
|
105
118
|
await this.space.spaceState.addCredentialProcessor(this._deviceStateMachine);
|
|
106
119
|
await this.space.spaceState.addCredentialProcessor(this._profileStateMachine);
|
|
107
120
|
await this.space.spaceState.addCredentialProcessor(this._defaultSpaceStateMachine);
|
|
121
|
+
if (this._edgeFeedReplicator) {
|
|
122
|
+
this.space.protocol.feedAdded.append(this._onFeedAdded);
|
|
123
|
+
}
|
|
108
124
|
await this.space.open(ctx);
|
|
109
125
|
}
|
|
110
126
|
|
|
127
|
+
public async joinNetwork() {
|
|
128
|
+
await this.space.startProtocol();
|
|
129
|
+
await this._edgeFeedReplicator?.open();
|
|
130
|
+
}
|
|
131
|
+
|
|
111
132
|
@trace.span()
|
|
112
133
|
async close(ctx: Context) {
|
|
113
134
|
await this._presence?.close();
|
|
@@ -115,6 +136,13 @@ export class Identity {
|
|
|
115
136
|
await this.space.spaceState.removeCredentialProcessor(this._defaultSpaceStateMachine);
|
|
116
137
|
await this.space.spaceState.removeCredentialProcessor(this._profileStateMachine);
|
|
117
138
|
await this.space.spaceState.removeCredentialProcessor(this._deviceStateMachine);
|
|
139
|
+
|
|
140
|
+
if (this._edgeFeedReplicator) {
|
|
141
|
+
this.space.protocol.feedAdded.remove(this._onFeedAdded);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await this._edgeFeedReplicator?.close();
|
|
145
|
+
|
|
118
146
|
await this.space.close();
|
|
119
147
|
}
|
|
120
148
|
|
|
@@ -135,6 +163,10 @@ export class Identity {
|
|
|
135
163
|
return this.space.controlPipeline;
|
|
136
164
|
}
|
|
137
165
|
|
|
166
|
+
get haloSpaceId() {
|
|
167
|
+
return this.space.id;
|
|
168
|
+
}
|
|
169
|
+
|
|
138
170
|
get haloSpaceKey() {
|
|
139
171
|
return this.space.key;
|
|
140
172
|
}
|
|
@@ -151,6 +183,10 @@ export class Identity {
|
|
|
151
183
|
return this._presence;
|
|
152
184
|
}
|
|
153
185
|
|
|
186
|
+
get signer() {
|
|
187
|
+
return this._signer;
|
|
188
|
+
}
|
|
189
|
+
|
|
154
190
|
/**
|
|
155
191
|
* Issues credentials as identity.
|
|
156
192
|
* Requires identity to be ready.
|
|
@@ -180,7 +216,7 @@ export class Identity {
|
|
|
180
216
|
await this.controlPipeline.state.waitUntilTimeframe(new Timeframe([[receipt.feedKey, receipt.seq]]));
|
|
181
217
|
}
|
|
182
218
|
|
|
183
|
-
async admitDevice({ deviceKey, controlFeedKey, dataFeedKey }: DeviceAdmissionRequest) {
|
|
219
|
+
async admitDevice({ deviceKey, controlFeedKey, dataFeedKey }: DeviceAdmissionRequest): Promise<Credential> {
|
|
184
220
|
log('Admitting device:', {
|
|
185
221
|
identityKey: this.identityKey,
|
|
186
222
|
hostDevice: this.deviceKey,
|
|
@@ -189,17 +225,18 @@ export class Identity {
|
|
|
189
225
|
dataFeedKey,
|
|
190
226
|
});
|
|
191
227
|
const signer = this.getIdentityCredentialSigner();
|
|
228
|
+
const deviceCredential = await signer.createCredential({
|
|
229
|
+
subject: deviceKey,
|
|
230
|
+
assertion: {
|
|
231
|
+
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
232
|
+
identityKey: this.identityKey,
|
|
233
|
+
deviceKey,
|
|
234
|
+
},
|
|
235
|
+
});
|
|
192
236
|
await writeMessages(
|
|
193
237
|
this.controlPipeline.writer,
|
|
194
238
|
[
|
|
195
|
-
|
|
196
|
-
subject: deviceKey,
|
|
197
|
-
assertion: {
|
|
198
|
-
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
199
|
-
identityKey: this.identityKey,
|
|
200
|
-
deviceKey,
|
|
201
|
-
},
|
|
202
|
-
}),
|
|
239
|
+
deviceCredential,
|
|
203
240
|
await signer.createCredential({
|
|
204
241
|
subject: controlFeedKey,
|
|
205
242
|
assertion: {
|
|
@@ -222,5 +259,11 @@ export class Identity {
|
|
|
222
259
|
}),
|
|
223
260
|
].map((credential): FeedMessage.Payload => ({ credential: { credential } })),
|
|
224
261
|
);
|
|
262
|
+
|
|
263
|
+
return deviceCredential;
|
|
225
264
|
}
|
|
265
|
+
|
|
266
|
+
private _onFeedAdded = async (feed: FeedWrapper<any>) => {
|
|
267
|
+
await this._edgeFeedReplicator!.addFeed(feed);
|
|
268
|
+
};
|
|
226
269
|
}
|
|
@@ -2,19 +2,20 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { expect } from '
|
|
5
|
+
import { describe, expect, test, onTestFinished } from 'vitest';
|
|
6
6
|
|
|
7
7
|
import { asyncChain } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { AlreadyJoinedError } from '@dxos/protocols';
|
|
10
10
|
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
11
|
-
import { describe, test, afterTest } from '@dxos/test';
|
|
12
11
|
|
|
13
12
|
import { type ServiceContext } from '../services';
|
|
14
13
|
import { createPeers, createServiceContext, performInvitation } from '../testing';
|
|
15
14
|
|
|
16
15
|
const closeAfterTest = async (peer: ServiceContext) => {
|
|
17
|
-
|
|
16
|
+
onTestFinished(async () => {
|
|
17
|
+
await peer.close();
|
|
18
|
+
});
|
|
18
19
|
return peer;
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -22,7 +23,9 @@ describe('services/device', () => {
|
|
|
22
23
|
test('creates identity', async () => {
|
|
23
24
|
const peer = await createServiceContext();
|
|
24
25
|
await peer.open(new Context());
|
|
25
|
-
|
|
26
|
+
onTestFinished(async () => {
|
|
27
|
+
await peer.close();
|
|
28
|
+
});
|
|
26
29
|
|
|
27
30
|
const identity = await peer.createIdentity();
|
|
28
31
|
expect(identity).not.to.be.undefined;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { getCredentialAssertion } from '@dxos/credentials';
|
|
5
6
|
import { invariant } from '@dxos/invariant';
|
|
6
7
|
import { type Keyring } from '@dxos/keyring';
|
|
7
8
|
import { type PublicKey } from '@dxos/keys';
|
|
@@ -49,7 +50,8 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
|
|
|
49
50
|
async admit(_: Invitation, request: AdmissionRequest): Promise<AdmissionResponse> {
|
|
50
51
|
invariant(request.device);
|
|
51
52
|
const identity = this._getIdentity();
|
|
52
|
-
await identity.admitDevice(request.device);
|
|
53
|
+
const credential = await identity.admitDevice(request.device);
|
|
54
|
+
invariant(getCredentialAssertion(credential)['@type'] === 'dxos.halo.credentials.AuthorizedDevice');
|
|
53
55
|
|
|
54
56
|
return {
|
|
55
57
|
device: {
|
|
@@ -57,6 +59,7 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
|
|
|
57
59
|
haloSpaceKey: identity.haloSpaceKey,
|
|
58
60
|
genesisFeedKey: identity.haloGenesisFeedKey,
|
|
59
61
|
controlTimeframe: identity.controlPipeline.state.timeframe,
|
|
62
|
+
credential,
|
|
60
63
|
},
|
|
61
64
|
};
|
|
62
65
|
}
|
|
@@ -109,6 +112,7 @@ export class DeviceInvitationProtocol implements InvitationProtocol {
|
|
|
109
112
|
dataFeedKey,
|
|
110
113
|
controlTimeframe,
|
|
111
114
|
deviceProfile: profile,
|
|
115
|
+
authorizedDeviceCredential: response.device.credential,
|
|
112
116
|
});
|
|
113
117
|
|
|
114
118
|
return { identityKey };
|