@dxos/client-services 0.1.17 → 0.1.19
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 +459 -314
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +497 -352
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/packlets/deprecated/space.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts +21 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +3 -9
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +5 -4
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts +6 -4
- package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitations-service.d.ts +5 -4
- package/dist/types/src/packlets/invitations/space-invitations-service.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +3 -1
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +28 -0
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/data-space.d.ts +23 -0
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -0
- package/package.json +31 -28
- package/src/packlets/deprecated/space.ts +16 -8
- package/src/packlets/devtools/spaces.ts +1 -1
- package/src/packlets/identity/authenticator.test.ts +8 -3
- package/src/packlets/identity/authenticator.ts +68 -24
- package/src/packlets/identity/identity-manager.test.ts +11 -9
- package/src/packlets/identity/identity-manager.ts +33 -47
- package/src/packlets/identity/identity.test.ts +40 -17
- package/src/packlets/identity/identity.ts +26 -16
- package/src/packlets/invitations/halo-invitations-handler.ts +1 -2
- package/src/packlets/invitations/space-invitations-handler.test.ts +7 -7
- package/src/packlets/invitations/space-invitations-handler.ts +9 -8
- package/src/packlets/invitations/space-invitations-service.test.ts +10 -10
- package/src/packlets/invitations/space-invitations-service.ts +6 -5
- package/src/packlets/services/service-context.ts +21 -21
- package/src/packlets/services/service-host.ts +1 -1
- package/src/packlets/services/service-registry.test.ts +3 -3
- package/src/packlets/spaces/data-space-manager.ts +118 -0
- package/src/packlets/spaces/data-space.ts +66 -0
- package/dist/types/src/packlets/invitations/rpc-extension.d.ts +0 -17
- package/dist/types/src/packlets/invitations/rpc-extension.d.ts.map +0 -1
- package/src/packlets/invitations/rpc-extension.ts +0 -56
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
import expect from 'expect';
|
|
6
6
|
import assert from 'node:assert';
|
|
7
7
|
|
|
8
|
+
import { Event } from '@dxos/async';
|
|
8
9
|
import { createCredentialSignerWithKey } from '@dxos/credentials';
|
|
9
10
|
import { Keyring } from '@dxos/keyring';
|
|
10
11
|
import { PublicKey } from '@dxos/keys';
|
|
11
12
|
import { describe, test } from '@dxos/test';
|
|
12
13
|
import { ComplexSet } from '@dxos/util';
|
|
13
14
|
|
|
14
|
-
import { createHaloAuthProvider,
|
|
15
|
+
import { createHaloAuthProvider, HaloAuthVerifier } from './authenticator';
|
|
15
16
|
|
|
16
17
|
describe('identity/authenticator', () => {
|
|
17
18
|
test('verifies credentials', async () => {
|
|
@@ -19,11 +20,15 @@ describe('identity/authenticator', () => {
|
|
|
19
20
|
const deviceKey = await keyring.createKey();
|
|
20
21
|
const signer = createCredentialSignerWithKey(keyring, deviceKey);
|
|
21
22
|
const authProvider = createHaloAuthProvider(signer);
|
|
22
|
-
const authVerifier =
|
|
23
|
+
const authVerifier = new HaloAuthVerifier({
|
|
24
|
+
trustedDevicesProvider: () => new ComplexSet(PublicKey.hash, [deviceKey]),
|
|
25
|
+
update: new Event(),
|
|
26
|
+
authTimeout: 10
|
|
27
|
+
});
|
|
23
28
|
|
|
24
29
|
const nonce = new Uint8Array([2, 1, 3, 7]);
|
|
25
30
|
const credential = await authProvider(nonce);
|
|
26
31
|
assert(credential);
|
|
27
|
-
expect(await authVerifier(nonce, credential)).toBeTruthy();
|
|
32
|
+
expect(await authVerifier.verifier(nonce, credential)).toBeTruthy();
|
|
28
33
|
}).onlyEnvironments('nodejs');
|
|
29
34
|
});
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { Event, Trigger } from '@dxos/async';
|
|
6
|
+
import { Context } from '@dxos/context';
|
|
5
7
|
import { verifyCredential, CredentialSigner } from '@dxos/credentials';
|
|
6
8
|
import { AuthProvider, AuthVerifier } from '@dxos/echo-db';
|
|
7
9
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -23,27 +25,69 @@ export const createHaloAuthProvider =
|
|
|
23
25
|
return schema.getCodecForType('dxos.halo.credentials.Credential').encode(credential);
|
|
24
26
|
};
|
|
25
27
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
28
|
+
export type HaloAuthVerifierParams = {
|
|
29
|
+
trustedDevicesProvider: () => ComplexSet<PublicKey>;
|
|
30
|
+
update: Event<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Timeout to wait for the device key to be added to the trusted set.
|
|
33
|
+
*/
|
|
34
|
+
authTimeout: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Verifies credentials of another device of the same identity in the HALO space.
|
|
39
|
+
* Will wait up to `authTimeout` for the device key to be added to the trusted set.
|
|
40
|
+
*/
|
|
41
|
+
export class HaloAuthVerifier {
|
|
42
|
+
private _ctx = new Context();
|
|
43
|
+
|
|
44
|
+
constructor(private readonly _params: HaloAuthVerifierParams) {}
|
|
45
|
+
|
|
46
|
+
async close() {
|
|
47
|
+
await this._ctx.dispose();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private _isTrustedDevice(deviceKey: PublicKey) {
|
|
51
|
+
const deviceSet = this._params.trustedDevicesProvider();
|
|
52
|
+
return deviceSet.has(deviceKey);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get verifier(): AuthVerifier {
|
|
56
|
+
return async (nonce, auth) => {
|
|
57
|
+
const credential = schema.getCodecForType('dxos.halo.credentials.Credential').decode(auth);
|
|
58
|
+
|
|
59
|
+
const result = await verifyCredential(credential);
|
|
60
|
+
if (result.kind === 'fail') {
|
|
61
|
+
log('Invalid credential', { result });
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!credential.proof.nonce || !Buffer.from(nonce).equals(credential.proof.nonce)) {
|
|
66
|
+
log('Invalid nonce', { nonce, credential });
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this._isTrustedDevice(credential.issuer)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const trigger = new Trigger<boolean>();
|
|
75
|
+
this._ctx.onDispose(() => {
|
|
76
|
+
trigger.wake(false);
|
|
77
|
+
});
|
|
78
|
+
const clear = this._params.update.on(this._ctx, () => {
|
|
79
|
+
if (this._isTrustedDevice(credential.issuer)) {
|
|
80
|
+
trigger.wake(true);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
await trigger.wait({ timeout: this._params.authTimeout });
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
} finally {
|
|
89
|
+
clear();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
6
|
|
|
7
|
-
import { valueEncoding, MetadataStore } from '@dxos/echo-db';
|
|
7
|
+
import { valueEncoding, MetadataStore, SpaceManager, AuthStatus } from '@dxos/echo-db';
|
|
8
8
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
9
9
|
import { Keyring } from '@dxos/keyring';
|
|
10
10
|
import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
|
|
@@ -13,7 +13,6 @@ import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
|
13
13
|
import { createStorage, Storage, StorageType } from '@dxos/random-access-storage';
|
|
14
14
|
import { describe, test, afterTest } from '@dxos/test';
|
|
15
15
|
|
|
16
|
-
import { createDefaultModelFactory } from '../services';
|
|
17
16
|
import { IdentityManager } from './identity-manager';
|
|
18
17
|
|
|
19
18
|
describe('identity/identity-manager', () => {
|
|
@@ -43,14 +42,11 @@ describe('identity/identity-manager', () => {
|
|
|
43
42
|
signalManager: new MemorySignalManager(signalContext),
|
|
44
43
|
transportFactory: MemoryTransportFactory
|
|
45
44
|
});
|
|
46
|
-
|
|
47
|
-
const identityManager = new IdentityManager(
|
|
48
|
-
metadataStore,
|
|
45
|
+
const spaceManager = new SpaceManager({
|
|
49
46
|
feedStore,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
47
|
+
networkManager
|
|
48
|
+
});
|
|
49
|
+
const identityManager = new IdentityManager(metadataStore, keyring, spaceManager);
|
|
54
50
|
|
|
55
51
|
return {
|
|
56
52
|
identityManager,
|
|
@@ -116,5 +112,11 @@ describe('identity/identity-manager', () => {
|
|
|
116
112
|
// TODO(dmaretskyi): We'd also need to admit device2's feeds otherwise messages from them won't be processed by the pipeline.
|
|
117
113
|
// This would mean that peer2 has replicated it's device credential chain from peer1 and is ready to issue credentials.
|
|
118
114
|
await identity2.ready();
|
|
115
|
+
|
|
116
|
+
// Connection is authenticated.
|
|
117
|
+
expect(identity1.space.protocol.sessions.get(identity2.deviceKey)).to.exist;
|
|
118
|
+
expect(identity1.space.protocol.sessions.get(identity2.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
|
|
119
|
+
expect(identity2.space.protocol.sessions.get(identity1.deviceKey)).to.exist;
|
|
120
|
+
expect(identity2.space.protocol.sessions.get(identity1.deviceKey)?.authStatus).to.equal(AuthStatus.SUCCESS);
|
|
119
121
|
});
|
|
120
122
|
});
|
|
@@ -5,31 +5,22 @@
|
|
|
5
5
|
import assert from 'node:assert';
|
|
6
6
|
|
|
7
7
|
import { Event } from '@dxos/async';
|
|
8
|
-
import { CredentialGenerator } from '@dxos/credentials';
|
|
9
|
-
import {
|
|
10
|
-
MOCK_AUTH_PROVIDER,
|
|
11
|
-
MOCK_AUTH_VERIFIER,
|
|
12
|
-
MetadataStore,
|
|
13
|
-
Space,
|
|
14
|
-
SwarmIdentity,
|
|
15
|
-
Database,
|
|
16
|
-
SpaceProtocol
|
|
17
|
-
} from '@dxos/echo-db';
|
|
18
|
-
import { FeedStore } from '@dxos/feed-store';
|
|
8
|
+
import { createCredentialSignerWithKey, CredentialGenerator } from '@dxos/credentials';
|
|
9
|
+
import { MetadataStore, NoopDataPipelineController, SpaceManager, SwarmIdentity } from '@dxos/echo-db';
|
|
19
10
|
import { Keyring } from '@dxos/keyring';
|
|
20
11
|
import { PublicKey } from '@dxos/keys';
|
|
21
12
|
import { log } from '@dxos/log';
|
|
22
|
-
import { ModelFactory } from '@dxos/model-factory';
|
|
23
|
-
import { NetworkManager, Plugin } from '@dxos/network-manager';
|
|
24
|
-
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
25
13
|
import { AdmittedFeed, IdentityRecord, SpaceRecord } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
14
|
+
import { Presence } from '@dxos/teleport-extension-presence';
|
|
15
|
+
import { deferFunction } from '@dxos/util';
|
|
26
16
|
|
|
27
17
|
import { Identity } from '../identity';
|
|
18
|
+
import { createHaloAuthProvider } from './authenticator';
|
|
28
19
|
|
|
29
20
|
interface ConstructSpaceParams {
|
|
30
21
|
spaceRecord: SpaceRecord;
|
|
31
22
|
swarmIdentity: SwarmIdentity;
|
|
32
|
-
|
|
23
|
+
identityKey: PublicKey;
|
|
33
24
|
}
|
|
34
25
|
|
|
35
26
|
export type JoinIdentityParams = {
|
|
@@ -52,10 +43,8 @@ export class IdentityManager {
|
|
|
52
43
|
// TODO(dmaretskyi): Perhaps this should take/generate the peerKey outside of an initialized identity.
|
|
53
44
|
constructor(
|
|
54
45
|
private readonly _metadataStore: MetadataStore,
|
|
55
|
-
private readonly _feedStore: FeedStore<FeedMessage>,
|
|
56
46
|
private readonly _keyring: Keyring,
|
|
57
|
-
private readonly
|
|
58
|
-
private readonly _modelFactory: ModelFactory
|
|
47
|
+
private readonly _spaceManager: SpaceManager
|
|
59
48
|
) {}
|
|
60
49
|
|
|
61
50
|
get identity() {
|
|
@@ -177,43 +166,40 @@ export class IdentityManager {
|
|
|
177
166
|
spaceRecord: identityRecord.haloSpace,
|
|
178
167
|
swarmIdentity: {
|
|
179
168
|
peerKey: identityRecord.deviceKey,
|
|
180
|
-
credentialProvider:
|
|
181
|
-
|
|
182
|
-
|
|
169
|
+
credentialProvider: createHaloAuthProvider(
|
|
170
|
+
createCredentialSignerWithKey(this._keyring, identityRecord.deviceKey)
|
|
171
|
+
),
|
|
172
|
+
credentialAuthenticator: deferFunction(() => identity.authVerifier.verifier)
|
|
173
|
+
},
|
|
174
|
+
identityKey: identityRecord.identityKey
|
|
183
175
|
});
|
|
184
|
-
|
|
185
|
-
log('done', { identityKey: identityRecord.identityKey });
|
|
186
|
-
return new Identity({
|
|
176
|
+
const identity: Identity = new Identity({
|
|
187
177
|
space,
|
|
188
178
|
signer: this._keyring,
|
|
189
179
|
identityKey: identityRecord.identityKey,
|
|
190
180
|
deviceKey: identityRecord.deviceKey
|
|
191
181
|
});
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
private async _constructSpace({ spaceRecord, swarmIdentity }: ConstructSpaceParams) {
|
|
195
|
-
const controlFeed = await this._feedStore.openFeed(spaceRecord.writeControlFeedKey, { writable: true });
|
|
196
|
-
const dataFeed = await this._feedStore.openFeed(spaceRecord.writeDataFeedKey, { writable: true });
|
|
197
|
-
|
|
198
|
-
// The genesis feed will be the same as the control feed if the space was created by the local agent.
|
|
199
|
-
// NOTE: Must be initialized after writable feeds so that it is in a writable state.
|
|
200
|
-
const genesisFeed = await this._feedStore.openFeed(spaceRecord.genesisFeedKey);
|
|
182
|
+
log('done', { identityKey: identityRecord.identityKey });
|
|
201
183
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
identity: swarmIdentity,
|
|
205
|
-
networkManager: this._networkManager
|
|
206
|
-
});
|
|
184
|
+
return identity;
|
|
185
|
+
}
|
|
207
186
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
187
|
+
private async _constructSpace({ spaceRecord, swarmIdentity, identityKey }: ConstructSpaceParams) {
|
|
188
|
+
return this._spaceManager.constructSpace({
|
|
189
|
+
metadata: {
|
|
190
|
+
key: spaceRecord.spaceKey,
|
|
191
|
+
genesisFeedKey: spaceRecord.genesisFeedKey,
|
|
192
|
+
controlFeedKey: spaceRecord.writeControlFeedKey,
|
|
193
|
+
dataFeedKey: spaceRecord.writeDataFeedKey
|
|
194
|
+
},
|
|
195
|
+
dataPipelineControllerProvider: () => new NoopDataPipelineController(),
|
|
196
|
+
swarmIdentity,
|
|
197
|
+
presence: new Presence({
|
|
198
|
+
localPeerId: swarmIdentity.peerKey,
|
|
199
|
+
announceInterval: 1_000,
|
|
200
|
+
offlineTimeout: 30_000,
|
|
201
|
+
identityKey
|
|
202
|
+
})
|
|
217
203
|
});
|
|
218
204
|
}
|
|
219
205
|
}
|
|
@@ -4,8 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import expect from 'expect';
|
|
6
6
|
|
|
7
|
-
import { CredentialGenerator, verifyCredential
|
|
8
|
-
import {
|
|
7
|
+
import { CredentialGenerator, verifyCredential } from '@dxos/credentials';
|
|
8
|
+
import {
|
|
9
|
+
MOCK_AUTH_PROVIDER,
|
|
10
|
+
MOCK_AUTH_VERIFIER,
|
|
11
|
+
NoopDataPipelineController,
|
|
12
|
+
Space,
|
|
13
|
+
SpaceProtocol,
|
|
14
|
+
valueEncoding
|
|
15
|
+
} from '@dxos/echo-db';
|
|
9
16
|
import { FeedFactory, FeedStore } from '@dxos/feed-store';
|
|
10
17
|
import { Keyring } from '@dxos/keyring';
|
|
11
18
|
import { PublicKey } from '@dxos/keys';
|
|
@@ -14,14 +21,11 @@ import { MemoryTransportFactory, NetworkManager } from '@dxos/network-manager';
|
|
|
14
21
|
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
15
22
|
import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
16
23
|
import { createStorage, StorageType } from '@dxos/random-access-storage';
|
|
17
|
-
import {
|
|
24
|
+
import { Presence } from '@dxos/teleport-extension-presence';
|
|
25
|
+
import { afterTest, describe, test } from '@dxos/test';
|
|
18
26
|
|
|
19
|
-
import { createDefaultModelFactory } from '../services';
|
|
20
|
-
import { createHaloAuthProvider, createHaloAuthVerifier } from './authenticator';
|
|
21
27
|
import { Identity } from './identity';
|
|
22
28
|
|
|
23
|
-
const modelFactory = createDefaultModelFactory();
|
|
24
|
-
|
|
25
29
|
describe('identity/identity', () => {
|
|
26
30
|
test('create', async () => {
|
|
27
31
|
const keyring = new Keyring();
|
|
@@ -49,14 +53,20 @@ describe('identity/identity', () => {
|
|
|
49
53
|
|
|
50
54
|
const protocol = new SpaceProtocol({
|
|
51
55
|
topic: spaceKey,
|
|
52
|
-
|
|
53
|
-
peerKey:
|
|
54
|
-
credentialProvider:
|
|
55
|
-
credentialAuthenticator:
|
|
56
|
+
swarmIdentity: {
|
|
57
|
+
peerKey: deviceKey,
|
|
58
|
+
credentialProvider: MOCK_AUTH_PROVIDER,
|
|
59
|
+
credentialAuthenticator: MOCK_AUTH_VERIFIER
|
|
56
60
|
},
|
|
57
61
|
networkManager: new NetworkManager({
|
|
58
62
|
signalManager: new MemorySignalManager(new MemorySignalManagerContext()),
|
|
59
63
|
transportFactory: MemoryTransportFactory
|
|
64
|
+
}),
|
|
65
|
+
presence: new Presence({
|
|
66
|
+
localPeerId: deviceKey,
|
|
67
|
+
announceInterval: 30,
|
|
68
|
+
offlineTimeout: 200,
|
|
69
|
+
identityKey
|
|
60
70
|
})
|
|
61
71
|
});
|
|
62
72
|
|
|
@@ -67,7 +77,7 @@ describe('identity/identity', () => {
|
|
|
67
77
|
controlFeed,
|
|
68
78
|
dataFeed,
|
|
69
79
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
70
|
-
|
|
80
|
+
dataPipelineControllerProvider: () => new NoopDataPipelineController()
|
|
71
81
|
});
|
|
72
82
|
|
|
73
83
|
const identity = new Identity({
|
|
@@ -120,6 +130,7 @@ describe('identity/identity', () => {
|
|
|
120
130
|
const signalContext = new MemorySignalManagerContext();
|
|
121
131
|
|
|
122
132
|
let spaceKey: PublicKey;
|
|
133
|
+
let identityKey: PublicKey;
|
|
123
134
|
let genesisFeedKey: PublicKey;
|
|
124
135
|
let identity1: Identity;
|
|
125
136
|
let identity2: Identity;
|
|
@@ -129,7 +140,7 @@ describe('identity/identity', () => {
|
|
|
129
140
|
//
|
|
130
141
|
{
|
|
131
142
|
const keyring = new Keyring();
|
|
132
|
-
|
|
143
|
+
identityKey = await keyring.createKey();
|
|
133
144
|
const deviceKey = await keyring.createKey();
|
|
134
145
|
spaceKey = await keyring.createKey();
|
|
135
146
|
|
|
@@ -155,7 +166,7 @@ describe('identity/identity', () => {
|
|
|
155
166
|
|
|
156
167
|
const protocol = new SpaceProtocol({
|
|
157
168
|
topic: spaceKey,
|
|
158
|
-
|
|
169
|
+
swarmIdentity: {
|
|
159
170
|
peerKey: deviceKey,
|
|
160
171
|
credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
|
|
161
172
|
credentialAuthenticator: MOCK_AUTH_VERIFIER // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
|
|
@@ -163,6 +174,12 @@ describe('identity/identity', () => {
|
|
|
163
174
|
networkManager: new NetworkManager({
|
|
164
175
|
signalManager: new MemorySignalManager(signalContext),
|
|
165
176
|
transportFactory: MemoryTransportFactory
|
|
177
|
+
}),
|
|
178
|
+
presence: new Presence({
|
|
179
|
+
localPeerId: deviceKey,
|
|
180
|
+
announceInterval: 30,
|
|
181
|
+
offlineTimeout: 200,
|
|
182
|
+
identityKey
|
|
166
183
|
})
|
|
167
184
|
});
|
|
168
185
|
|
|
@@ -173,7 +190,7 @@ describe('identity/identity', () => {
|
|
|
173
190
|
controlFeed,
|
|
174
191
|
dataFeed,
|
|
175
192
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
176
|
-
|
|
193
|
+
dataPipelineControllerProvider: () => new NoopDataPipelineController()
|
|
177
194
|
});
|
|
178
195
|
|
|
179
196
|
const identity = (identity1 = new Identity({
|
|
@@ -236,7 +253,7 @@ describe('identity/identity', () => {
|
|
|
236
253
|
|
|
237
254
|
const protocol = new SpaceProtocol({
|
|
238
255
|
topic: spaceKey,
|
|
239
|
-
|
|
256
|
+
swarmIdentity: {
|
|
240
257
|
peerKey: deviceKey,
|
|
241
258
|
credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
|
|
242
259
|
credentialAuthenticator: MOCK_AUTH_VERIFIER // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
|
|
@@ -244,6 +261,12 @@ describe('identity/identity', () => {
|
|
|
244
261
|
networkManager: new NetworkManager({
|
|
245
262
|
signalManager: new MemorySignalManager(signalContext),
|
|
246
263
|
transportFactory: MemoryTransportFactory
|
|
264
|
+
}),
|
|
265
|
+
presence: new Presence({
|
|
266
|
+
localPeerId: deviceKey,
|
|
267
|
+
announceInterval: 30,
|
|
268
|
+
offlineTimeout: 200,
|
|
269
|
+
identityKey
|
|
247
270
|
})
|
|
248
271
|
});
|
|
249
272
|
|
|
@@ -254,7 +277,7 @@ describe('identity/identity', () => {
|
|
|
254
277
|
controlFeed,
|
|
255
278
|
dataFeed,
|
|
256
279
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey),
|
|
257
|
-
|
|
280
|
+
dataPipelineControllerProvider: () => new NoopDataPipelineController()
|
|
258
281
|
});
|
|
259
282
|
|
|
260
283
|
const identity = (identity2 = new Identity({
|
|
@@ -13,8 +13,7 @@ import {
|
|
|
13
13
|
ProfileStateMachine
|
|
14
14
|
} from '@dxos/credentials';
|
|
15
15
|
import { Signer } from '@dxos/crypto';
|
|
16
|
-
import {
|
|
17
|
-
import { Database, Space } from '@dxos/echo-db';
|
|
16
|
+
import { Space } from '@dxos/echo-db';
|
|
18
17
|
import { writeMessages } from '@dxos/feed-store';
|
|
19
18
|
import { PublicKey } from '@dxos/keys';
|
|
20
19
|
import { log } from '@dxos/log';
|
|
@@ -23,6 +22,13 @@ import { AdmittedFeed, ProfileDocument } from '@dxos/protocols/proto/dxos/halo/c
|
|
|
23
22
|
import { HaloAdmissionCredentials } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
24
23
|
import { ComplexSet } from '@dxos/util';
|
|
25
24
|
|
|
25
|
+
import { HaloAuthVerifier } from './authenticator';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Timeout for the device to be added to the trusted set during auth.
|
|
29
|
+
*/
|
|
30
|
+
const AUTH_TIMEOUT = 30000;
|
|
31
|
+
|
|
26
32
|
export type IdentityParams = {
|
|
27
33
|
identityKey: PublicKey;
|
|
28
34
|
deviceKey: PublicKey;
|
|
@@ -34,10 +40,11 @@ export type IdentityParams = {
|
|
|
34
40
|
* Agent identity manager, which includes the agent's Halo space.
|
|
35
41
|
*/
|
|
36
42
|
export class Identity {
|
|
37
|
-
|
|
43
|
+
public readonly space: Space;
|
|
38
44
|
private readonly _signer: Signer;
|
|
39
45
|
private readonly _deviceStateMachine: DeviceStateMachine;
|
|
40
46
|
private readonly _profileStateMachine: ProfileStateMachine;
|
|
47
|
+
public readonly authVerifier: HaloAuthVerifier;
|
|
41
48
|
|
|
42
49
|
public readonly identityKey: PublicKey;
|
|
43
50
|
public readonly deviceKey: PublicKey;
|
|
@@ -45,7 +52,7 @@ export class Identity {
|
|
|
45
52
|
public readonly stateUpdate = new Event();
|
|
46
53
|
|
|
47
54
|
constructor({ space, signer, identityKey, deviceKey }: IdentityParams) {
|
|
48
|
-
this.
|
|
55
|
+
this.space = space;
|
|
49
56
|
this._signer = signer;
|
|
50
57
|
|
|
51
58
|
this.identityKey = identityKey;
|
|
@@ -55,12 +62,18 @@ export class Identity {
|
|
|
55
62
|
this._profileStateMachine = new ProfileStateMachine(this.identityKey);
|
|
56
63
|
|
|
57
64
|
// Process halo-specific credentials.
|
|
58
|
-
this.
|
|
65
|
+
this.space.onCredentialProcessed.set(async (credential) => {
|
|
59
66
|
// Save device keychain credential when processed by the space state machine.
|
|
60
67
|
await this._deviceStateMachine.process(credential);
|
|
61
68
|
await this._profileStateMachine.process(credential);
|
|
62
69
|
this.stateUpdate.emit();
|
|
63
70
|
});
|
|
71
|
+
|
|
72
|
+
this.authVerifier = new HaloAuthVerifier({
|
|
73
|
+
trustedDevicesProvider: () => this.authorizedDeviceKeys,
|
|
74
|
+
update: this.stateUpdate,
|
|
75
|
+
authTimeout: AUTH_TIMEOUT
|
|
76
|
+
});
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
// TODO(burdon): Expose state object?
|
|
@@ -69,11 +82,12 @@ export class Identity {
|
|
|
69
82
|
}
|
|
70
83
|
|
|
71
84
|
async open() {
|
|
72
|
-
await this.
|
|
85
|
+
await this.space.open();
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
async close() {
|
|
76
|
-
await this.
|
|
89
|
+
await this.authVerifier.close();
|
|
90
|
+
await this.space.close();
|
|
77
91
|
}
|
|
78
92
|
|
|
79
93
|
async ready() {
|
|
@@ -90,26 +104,22 @@ export class Identity {
|
|
|
90
104
|
* @test-only
|
|
91
105
|
*/
|
|
92
106
|
get controlPipeline() {
|
|
93
|
-
return this.
|
|
107
|
+
return this.space.controlPipeline;
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
get haloSpaceKey() {
|
|
97
|
-
return this.
|
|
111
|
+
return this.space.key;
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
get haloGenesisFeedKey() {
|
|
101
|
-
return this.
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
get haloDatabase(): Database {
|
|
105
|
-
return this._space.database ?? failUndefined();
|
|
115
|
+
return this.space.genesisFeedKey;
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
getAdmissionCredentials(): HaloAdmissionCredentials {
|
|
109
119
|
return {
|
|
110
120
|
deviceKey: this.deviceKey,
|
|
111
|
-
controlFeedKey: this.
|
|
112
|
-
dataFeedKey: this.
|
|
121
|
+
controlFeedKey: this.space.controlFeedKey,
|
|
122
|
+
dataFeedKey: this.space.dataFeedKey
|
|
113
123
|
};
|
|
114
124
|
}
|
|
115
125
|
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
HaloAdmissionOffer,
|
|
21
21
|
HaloHostService
|
|
22
22
|
} from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
23
|
-
import { ExtensionContext } from '@dxos/teleport';
|
|
23
|
+
import { ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
24
24
|
|
|
25
25
|
import { IdentityManager } from '../identity';
|
|
26
26
|
import {
|
|
@@ -32,7 +32,6 @@ import {
|
|
|
32
32
|
ON_CLOSE_DELAY
|
|
33
33
|
} from './invitations';
|
|
34
34
|
import { AbstractInvitationsHandler, InvitationsOptions } from './invitations-handler';
|
|
35
|
-
import { RpcExtension } from './rpc-extension';
|
|
36
35
|
|
|
37
36
|
/**
|
|
38
37
|
* Handles the life-cycle of Halo invitations between peers.
|
|
@@ -24,15 +24,15 @@ describe('services/space-invitations-handler', () => {
|
|
|
24
24
|
test('genesis', async () => {
|
|
25
25
|
const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
|
|
26
26
|
|
|
27
|
-
const space = await peer.
|
|
27
|
+
const space = await peer.dataSpaceManager!.createSpace();
|
|
28
28
|
expect(space.database).not.to.be.undefined;
|
|
29
|
-
expect(peer.
|
|
29
|
+
expect(peer.dataSpaceManager!.spaces.has(space.key)).to.be.true;
|
|
30
30
|
await space.close();
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
test('genesis with database mutations', async () => {
|
|
34
34
|
const [peer] = await asyncChain<ServiceContext>([createIdentity, closeAfterTest])(createPeers(1));
|
|
35
|
-
const space = await peer.
|
|
35
|
+
const space = await peer.dataSpaceManager!.createSpace();
|
|
36
36
|
|
|
37
37
|
{
|
|
38
38
|
const item = await space.database!.createItem<ObjectModel>({ type: 'test' });
|
|
@@ -56,7 +56,7 @@ describe('services/space-invitations-handler', () => {
|
|
|
56
56
|
let attempt = 0;
|
|
57
57
|
const authenticationCode = new Trigger<string>();
|
|
58
58
|
|
|
59
|
-
const space1 = await host.
|
|
59
|
+
const space1 = await host.dataSpaceManager!.createSpace();
|
|
60
60
|
const observable1 = host.spaceInvitations!.createInvitation(space1);
|
|
61
61
|
observable1.subscribe({
|
|
62
62
|
onConnecting: async (invitation1: Invitation) => {
|
|
@@ -98,8 +98,8 @@ describe('services/space-invitations-handler', () => {
|
|
|
98
98
|
expect(spaceKey1).to.deep.eq(spaceKey2);
|
|
99
99
|
|
|
100
100
|
{
|
|
101
|
-
const space1 = host.
|
|
102
|
-
const space2 = guest.
|
|
101
|
+
const space1 = host.dataSpaceManager!.spaces.get(spaceKey1)!;
|
|
102
|
+
const space2 = guest.dataSpaceManager!.spaces.get(spaceKey2)!;
|
|
103
103
|
expect(space1).not.to.be.undefined;
|
|
104
104
|
expect(space2).not.to.be.undefined;
|
|
105
105
|
|
|
@@ -117,7 +117,7 @@ describe('services/space-invitations-handler', () => {
|
|
|
117
117
|
const connecting1 = new Trigger<Invitation>(); // peer 1 connected.
|
|
118
118
|
const connecting2 = new Trigger<Invitation>(); // peer 2 connected.
|
|
119
119
|
|
|
120
|
-
const space1 = await host.
|
|
120
|
+
const space1 = await host.dataSpaceManager!.createSpace();
|
|
121
121
|
const observable1 = await host.spaceInvitations!.createInvitation(space1);
|
|
122
122
|
observable1.subscribe({
|
|
123
123
|
onConnecting: async (invitation1: Invitation) => {
|