@dxos/client-services 0.1.23 → 0.1.24
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-R2FEBB66.mjs +3865 -0
- package/dist/lib/browser/chunk-R2FEBB66.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +56 -3066
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/packlets/testing/index.mjs +198 -0
- package/dist/lib/browser/packlets/testing/index.mjs.map +7 -0
- package/dist/lib/node/index.cjs +1401 -645
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/packlets/testing/index.cjs +3688 -0
- package/dist/lib/node/packlets/testing/index.cjs.map +7 -0
- package/dist/types/src/packlets/deprecated/profile.d.ts +3 -1
- package/dist/types/src/packlets/deprecated/profile.d.ts.map +1 -1
- package/dist/types/src/packlets/deprecated/space.d.ts.map +1 -1
- package/dist/types/src/packlets/deprecated/system.d.ts +15 -9
- package/dist/types/src/packlets/deprecated/system.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/devtools.d.ts +2 -2
- package/dist/types/src/packlets/devtools/devtools.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/feeds.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/network.d.ts +3 -3
- package/dist/types/src/packlets/devtools/network.d.ts.map +1 -1
- package/dist/types/src/packlets/devtools/spaces.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts +1 -1
- package/dist/types/src/packlets/identity/authenticator.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts +7 -1
- package/dist/types/src/packlets/identity/identity-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/identity/identity.d.ts +1 -0
- package/dist/types/src/packlets/identity/identity.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts +11 -3
- package/dist/types/src/packlets/invitations/halo-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/invitations.d.ts +1 -1
- package/dist/types/src/packlets/invitations/invitations.d.ts.map +1 -1
- package/dist/types/src/packlets/invitations/space-invitations-handler.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.d.ts +2 -0
- package/dist/types/src/packlets/services/service-context.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-context.test.d.ts +2 -0
- package/dist/types/src/packlets/services/service-context.test.d.ts.map +1 -0
- package/dist/types/src/packlets/services/service-definitions.d.ts +1 -1
- package/dist/types/src/packlets/services/service-definitions.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-host.d.ts +9 -6
- package/dist/types/src/packlets/services/service-host.d.ts.map +1 -1
- package/dist/types/src/packlets/services/service-registry.d.ts +4 -3
- package/dist/types/src/packlets/services/service-registry.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts +9 -2
- package/dist/types/src/packlets/spaces/data-space-manager.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/data-space.d.ts +18 -4
- package/dist/types/src/packlets/spaces/data-space.d.ts.map +1 -1
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts +50 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts +2 -0
- package/dist/types/src/packlets/spaces/notarization-plugin.test.d.ts.map +1 -0
- package/dist/types/src/packlets/spaces/spaces-service.d.ts +2 -1
- package/dist/types/src/packlets/spaces/spaces-service.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/credential-utils.d.ts +8 -0
- package/dist/types/src/packlets/testing/credential-utils.d.ts.map +1 -0
- package/dist/types/src/packlets/testing/index.d.ts +2 -0
- package/dist/types/src/packlets/testing/index.d.ts.map +1 -1
- package/dist/types/src/packlets/testing/invitation-utils.d.ts +3 -0
- package/dist/types/src/packlets/testing/invitation-utils.d.ts.map +1 -0
- package/dist/types/src/packlets/testing/test-builder.d.ts +2 -2
- package/dist/types/src/packlets/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts +28 -0
- package/dist/types/src/packlets/vault/iframe-host-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/{iframe-runtime.d.ts → iframe-proxy-runtime.d.ts} +4 -4
- package/dist/types/src/packlets/vault/iframe-proxy-runtime.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/index.d.ts +4 -1
- package/dist/types/src/packlets/vault/index.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/vault-resource-lock.d.ts +19 -0
- package/dist/types/src/packlets/vault/vault-resource-lock.d.ts.map +1 -0
- package/dist/types/src/packlets/vault/worker-runtime.d.ts.map +1 -1
- package/dist/types/src/packlets/vault/worker-session.d.ts +4 -4
- package/dist/types/src/packlets/vault/worker-session.d.ts.map +1 -1
- package/package.json +29 -29
- package/src/packlets/deprecated/profile.ts +20 -2
- package/src/packlets/deprecated/space.ts +41 -37
- package/src/packlets/deprecated/system.ts +26 -13
- package/src/packlets/devtools/devtools.ts +4 -4
- package/src/packlets/devtools/feeds.ts +11 -6
- package/src/packlets/devtools/network.ts +20 -9
- package/src/packlets/devtools/spaces.ts +7 -6
- package/src/packlets/identity/authenticator.ts +11 -6
- package/src/packlets/identity/identity-manager.test.ts +24 -15
- package/src/packlets/identity/identity-manager.ts +22 -8
- package/src/packlets/identity/identity.test.ts +21 -22
- package/src/packlets/identity/identity.ts +10 -5
- package/src/packlets/invitations/halo-invitations-handler.test.ts +4 -45
- package/src/packlets/invitations/halo-invitations-handler.ts +33 -17
- package/src/packlets/invitations/invitations.ts +2 -1
- package/src/packlets/invitations/space-invitations-handler.test.ts +43 -26
- package/src/packlets/invitations/space-invitations-handler.ts +8 -11
- package/src/packlets/services/service-context.test.ts +56 -0
- package/src/packlets/services/service-context.ts +60 -4
- package/src/packlets/services/service-definitions.ts +1 -1
- package/src/packlets/services/service-host.test.ts +74 -5
- package/src/packlets/services/service-host.ts +85 -23
- package/src/packlets/services/service-registry.test.ts +2 -1
- package/src/packlets/services/service-registry.ts +5 -1
- package/src/packlets/spaces/data-space-manager.test.ts +71 -14
- package/src/packlets/spaces/data-space-manager.ts +49 -8
- package/src/packlets/spaces/data-space.ts +113 -14
- package/src/packlets/spaces/notarization-plugin.test.ts +81 -0
- package/src/packlets/spaces/notarization-plugin.ts +211 -0
- package/src/packlets/spaces/spaces-service.ts +9 -1
- package/src/packlets/testing/credential-utils.ts +28 -0
- package/src/packlets/testing/index.ts +2 -0
- package/src/packlets/testing/invitation-utils.ts +44 -0
- package/src/packlets/testing/test-builder.ts +14 -18
- package/src/packlets/vault/iframe-host-runtime.ts +112 -0
- package/src/packlets/vault/{iframe-runtime.ts → iframe-proxy-runtime.ts} +3 -3
- package/src/packlets/vault/index.ts +4 -1
- package/src/packlets/vault/vault-resource-lock.ts +76 -0
- package/src/packlets/vault/worker-runtime.ts +13 -3
- package/src/packlets/vault/worker-session.ts +17 -17
- package/dist/types/src/packlets/vault/iframe-runtime.d.ts.map +0 -1
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { Stream } from '@dxos/codec-protobuf';
|
|
6
|
+
import { Context } from '@dxos/context';
|
|
6
7
|
import { PublicKey } from '@dxos/keys';
|
|
7
|
-
import { CommandTrace } from '@dxos/messaging';
|
|
8
8
|
import { NetworkManager } from '@dxos/network-manager';
|
|
9
9
|
import {
|
|
10
10
|
GetNetworkPeersRequest,
|
|
11
11
|
GetNetworkPeersResponse,
|
|
12
12
|
SubscribeToNetworkTopicsResponse,
|
|
13
13
|
SubscribeToSignalStatusResponse,
|
|
14
|
-
|
|
14
|
+
SignalResponse,
|
|
15
15
|
SubscribeToSwarmInfoResponse
|
|
16
16
|
} from '@dxos/protocols/proto/dxos/devtools/host';
|
|
17
17
|
|
|
@@ -30,14 +30,25 @@ export const subscribeToNetworkStatus = ({ networkManager }: { networkManager: N
|
|
|
30
30
|
update();
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
export const
|
|
34
|
-
new Stream<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
export const subscribeToSignal = ({ networkManager }: { networkManager: NetworkManager }) =>
|
|
34
|
+
new Stream<SignalResponse>(({ next }) => {
|
|
35
|
+
const ctx = new Context();
|
|
36
|
+
networkManager.signalManager.onMessage.on(ctx, (message) => {
|
|
37
|
+
next({
|
|
38
|
+
message: {
|
|
39
|
+
author: message.author.asUint8Array(),
|
|
40
|
+
recipient: message.recipient.asUint8Array(),
|
|
41
|
+
payload: message.payload
|
|
42
|
+
},
|
|
43
|
+
receivedAt: new Date()
|
|
44
|
+
});
|
|
40
45
|
});
|
|
46
|
+
networkManager.signalManager.swarmEvent.on(ctx, (swarmEvent) => {
|
|
47
|
+
next({ swarmEvent: swarmEvent.swarmEvent, receivedAt: new Date() });
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
return ctx.dispose();
|
|
51
|
+
};
|
|
41
52
|
});
|
|
42
53
|
|
|
43
54
|
export const subscribeToNetworkTopics = ({ networkManager }: { networkManager: NetworkManager }) =>
|
|
@@ -9,8 +9,8 @@ import { SpaceMetadata } from '@dxos/protocols/proto/dxos/echo/metadata';
|
|
|
9
9
|
|
|
10
10
|
import { ServiceContext } from '../services';
|
|
11
11
|
|
|
12
|
-
export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: SubscribeToSpacesRequest) =>
|
|
13
|
-
new Stream<SubscribeToSpacesResponse>(({ next }) => {
|
|
12
|
+
export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: SubscribeToSpacesRequest) => {
|
|
13
|
+
return new Stream<SubscribeToSpacesResponse>(({ next }) => {
|
|
14
14
|
let unsubscribe: () => void;
|
|
15
15
|
|
|
16
16
|
const update = async () => {
|
|
@@ -20,7 +20,7 @@ export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: S
|
|
|
20
20
|
);
|
|
21
21
|
|
|
22
22
|
next({
|
|
23
|
-
spaces: filteredSpaces.map((space) => {
|
|
23
|
+
spaces: filteredSpaces.map((space): SubscribeToSpacesResponse.SpaceInfo => {
|
|
24
24
|
const spaceMetadata = context.metadataStore.spaces.find((spaceMetadata: SpaceMetadata) =>
|
|
25
25
|
spaceMetadata.key.equals(space.key)
|
|
26
26
|
);
|
|
@@ -28,10 +28,10 @@ export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: S
|
|
|
28
28
|
return {
|
|
29
29
|
key: space.key,
|
|
30
30
|
isOpen: space.isOpen,
|
|
31
|
-
timeframe: spaceMetadata?.
|
|
31
|
+
timeframe: spaceMetadata?.dataTimeframe,
|
|
32
32
|
genesisFeed: space.genesisFeedKey,
|
|
33
|
-
controlFeed: space.controlFeedKey
|
|
34
|
-
dataFeed: space.dataFeedKey
|
|
33
|
+
controlFeed: space.controlFeedKey!, // TODO(dmaretskyi): Those keys may be missing.
|
|
34
|
+
dataFeed: space.dataFeedKey!
|
|
35
35
|
};
|
|
36
36
|
})
|
|
37
37
|
});
|
|
@@ -50,3 +50,4 @@ export const subscribeToSpaces = (context: ServiceContext, { spaceKeys = [] }: S
|
|
|
50
50
|
clearTimeout(timeout);
|
|
51
51
|
};
|
|
52
52
|
});
|
|
53
|
+
};
|
|
@@ -42,17 +42,15 @@ export type TrustedKeySetAuthVerifierParams = {
|
|
|
42
42
|
export class TrustedKeySetAuthVerifier {
|
|
43
43
|
private _ctx = new Context();
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
// prettier-ignore
|
|
46
|
+
constructor(
|
|
47
|
+
private readonly _params: TrustedKeySetAuthVerifierParams
|
|
48
|
+
) {}
|
|
46
49
|
|
|
47
50
|
async close() {
|
|
48
51
|
await this._ctx.dispose();
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
private _isTrustedKey(deviceKey: PublicKey) {
|
|
52
|
-
const deviceSet = this._params.trustedKeysProvider();
|
|
53
|
-
return deviceSet.has(deviceKey);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
54
|
get verifier(): AuthVerifier {
|
|
57
55
|
return async (nonce, auth) => {
|
|
58
56
|
const credential = schema.getCodecForType('dxos.halo.credentials.Credential').decode(auth);
|
|
@@ -78,6 +76,7 @@ export class TrustedKeySetAuthVerifier {
|
|
|
78
76
|
this._ctx.onDispose(() => {
|
|
79
77
|
trigger.wake(false);
|
|
80
78
|
});
|
|
79
|
+
|
|
81
80
|
const clear = this._params.update.on(this._ctx, () => {
|
|
82
81
|
if (this._isTrustedKey(credential.issuer)) {
|
|
83
82
|
log('auth success', { key: credential.issuer });
|
|
@@ -86,6 +85,7 @@ export class TrustedKeySetAuthVerifier {
|
|
|
86
85
|
log('key is not currently in trusted set, waiting...', { key: credential.issuer });
|
|
87
86
|
}
|
|
88
87
|
});
|
|
88
|
+
|
|
89
89
|
try {
|
|
90
90
|
return await trigger.wait({ timeout: this._params.authTimeout });
|
|
91
91
|
} catch {
|
|
@@ -95,4 +95,9 @@ export class TrustedKeySetAuthVerifier {
|
|
|
95
95
|
}
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
|
+
|
|
99
|
+
private _isTrustedKey(deviceKey: PublicKey) {
|
|
100
|
+
const deviceSet = this._params.trustedKeysProvider();
|
|
101
|
+
return deviceSet.has(deviceKey);
|
|
102
|
+
}
|
|
98
103
|
}
|
|
@@ -46,11 +46,12 @@ describe('identity/identity-manager', () => {
|
|
|
46
46
|
feedStore,
|
|
47
47
|
networkManager
|
|
48
48
|
});
|
|
49
|
-
const identityManager = new IdentityManager(metadataStore, keyring, spaceManager);
|
|
49
|
+
const identityManager = new IdentityManager(metadataStore, keyring, feedStore, spaceManager);
|
|
50
50
|
|
|
51
51
|
return {
|
|
52
52
|
identityManager,
|
|
53
|
-
feedStore
|
|
53
|
+
feedStore,
|
|
54
|
+
keyring
|
|
54
55
|
};
|
|
55
56
|
};
|
|
56
57
|
|
|
@@ -90,23 +91,31 @@ describe('identity/identity-manager', () => {
|
|
|
90
91
|
|
|
91
92
|
const peer2 = await setupPeer({ signalContext });
|
|
92
93
|
|
|
94
|
+
const deviceKey = await peer2.keyring.createKey();
|
|
95
|
+
const controlFeedKey = await peer2.keyring.createKey();
|
|
96
|
+
const dataFeedKey = await peer2.keyring.createKey();
|
|
97
|
+
|
|
98
|
+
await identity1.controlPipeline.writer.write({
|
|
99
|
+
credential: {
|
|
100
|
+
credential: await identity1.getIdentityCredentialSigner().createCredential({
|
|
101
|
+
subject: deviceKey,
|
|
102
|
+
assertion: {
|
|
103
|
+
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
104
|
+
identityKey: identity1.identityKey,
|
|
105
|
+
deviceKey
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
93
111
|
// Identity2 is not yet ready at this point. Peer1 needs to admit peer2 device key and feed keys.
|
|
94
112
|
const identity2 = await peer2.identityManager.acceptIdentity({
|
|
95
113
|
identityKey: identity1.identityKey,
|
|
114
|
+
deviceKey,
|
|
96
115
|
haloSpaceKey: identity1.haloSpaceKey,
|
|
97
|
-
haloGenesisFeedKey: identity1.haloGenesisFeedKey
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
await identity1.controlPipeline.writer.write({
|
|
101
|
-
'@type': 'dxos.echo.feed.CredentialsMessage',
|
|
102
|
-
credential: await identity1.getIdentityCredentialSigner().createCredential({
|
|
103
|
-
subject: identity2.deviceKey,
|
|
104
|
-
assertion: {
|
|
105
|
-
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
106
|
-
identityKey: identity2.identityKey,
|
|
107
|
-
deviceKey: identity2.deviceKey
|
|
108
|
-
}
|
|
109
|
-
})
|
|
116
|
+
haloGenesisFeedKey: identity1.haloGenesisFeedKey,
|
|
117
|
+
controlFeedKey,
|
|
118
|
+
dataFeedKey
|
|
110
119
|
});
|
|
111
120
|
|
|
112
121
|
// TODO(dmaretskyi): We'd also need to admit device2's feeds otherwise messages from them won't be processed by the pipeline.
|
|
@@ -7,9 +7,11 @@ import assert from 'node:assert';
|
|
|
7
7
|
import { Event } from '@dxos/async';
|
|
8
8
|
import { createCredentialSignerWithKey, CredentialGenerator } from '@dxos/credentials';
|
|
9
9
|
import { MetadataStore, SpaceManager, SwarmIdentity } from '@dxos/echo-db';
|
|
10
|
+
import { FeedStore } from '@dxos/feed-store';
|
|
10
11
|
import { Keyring } from '@dxos/keyring';
|
|
11
12
|
import { PublicKey } from '@dxos/keys';
|
|
12
13
|
import { log } from '@dxos/log';
|
|
14
|
+
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
13
15
|
import { AdmittedFeed, IdentityRecord, SpaceRecord } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
14
16
|
import { deferFunction } from '@dxos/util';
|
|
15
17
|
|
|
@@ -24,8 +26,11 @@ interface ConstructSpaceParams {
|
|
|
24
26
|
|
|
25
27
|
export type JoinIdentityParams = {
|
|
26
28
|
identityKey: PublicKey;
|
|
29
|
+
deviceKey: PublicKey;
|
|
27
30
|
haloSpaceKey: PublicKey;
|
|
28
31
|
haloGenesisFeedKey: PublicKey;
|
|
32
|
+
controlFeedKey: PublicKey;
|
|
33
|
+
dataFeedKey: PublicKey;
|
|
29
34
|
};
|
|
30
35
|
|
|
31
36
|
export type CreateIdentityOptions = {
|
|
@@ -43,6 +48,7 @@ export class IdentityManager {
|
|
|
43
48
|
constructor(
|
|
44
49
|
private readonly _metadataStore: MetadataStore,
|
|
45
50
|
private readonly _keyring: Keyring,
|
|
51
|
+
private readonly _feedStore: FeedStore<FeedMessage>,
|
|
46
52
|
private readonly _spaceManager: SpaceManager
|
|
47
53
|
) {}
|
|
48
54
|
|
|
@@ -113,8 +119,7 @@ export class IdentityManager {
|
|
|
113
119
|
|
|
114
120
|
for (const credential of credentials) {
|
|
115
121
|
await identity.controlPipeline.writer.write({
|
|
116
|
-
|
|
117
|
-
credential
|
|
122
|
+
credential: { credential }
|
|
118
123
|
});
|
|
119
124
|
}
|
|
120
125
|
}
|
|
@@ -141,12 +146,12 @@ export class IdentityManager {
|
|
|
141
146
|
|
|
142
147
|
const identityRecord: IdentityRecord = {
|
|
143
148
|
identityKey: params.identityKey,
|
|
144
|
-
deviceKey:
|
|
149
|
+
deviceKey: params.deviceKey,
|
|
145
150
|
haloSpace: {
|
|
146
151
|
spaceKey: params.haloSpaceKey,
|
|
147
152
|
genesisFeedKey: params.haloGenesisFeedKey,
|
|
148
|
-
writeControlFeedKey:
|
|
149
|
-
writeDataFeedKey:
|
|
153
|
+
writeControlFeedKey: params.controlFeedKey,
|
|
154
|
+
writeDataFeedKey: params.dataFeedKey
|
|
150
155
|
}
|
|
151
156
|
};
|
|
152
157
|
const identity = await this._constructIdentity(identityRecord);
|
|
@@ -154,7 +159,9 @@ export class IdentityManager {
|
|
|
154
159
|
await identity.open();
|
|
155
160
|
this._identity = identity;
|
|
156
161
|
await this._metadataStore.setIdentityRecord(identityRecord);
|
|
162
|
+
await this._identity.ready();
|
|
157
163
|
this.stateUpdate.emit();
|
|
164
|
+
log('accepted identity', { identityKey: identity.identityKey, deviceKey: identity.deviceKey });
|
|
158
165
|
return identity;
|
|
159
166
|
}
|
|
160
167
|
|
|
@@ -162,6 +169,12 @@ export class IdentityManager {
|
|
|
162
169
|
assert(!this._identity);
|
|
163
170
|
log('constructing identity', { identityRecord });
|
|
164
171
|
|
|
172
|
+
// Must be created before the space so the feeds are writable.
|
|
173
|
+
const controlFeed = await this._feedStore.openFeed(identityRecord.haloSpace.writeControlFeedKey, {
|
|
174
|
+
writable: true
|
|
175
|
+
});
|
|
176
|
+
const dataFeed = await this._feedStore.openFeed(identityRecord.haloSpace.writeDataFeedKey, { writable: true });
|
|
177
|
+
|
|
165
178
|
const space = await this._constructSpace({
|
|
166
179
|
spaceRecord: identityRecord.haloSpace,
|
|
167
180
|
swarmIdentity: {
|
|
@@ -171,6 +184,9 @@ export class IdentityManager {
|
|
|
171
184
|
},
|
|
172
185
|
identityKey: identityRecord.identityKey
|
|
173
186
|
});
|
|
187
|
+
space.setControlFeed(controlFeed);
|
|
188
|
+
space.setDataFeed(dataFeed);
|
|
189
|
+
|
|
174
190
|
const identity: Identity = new Identity({
|
|
175
191
|
space,
|
|
176
192
|
signer: this._keyring,
|
|
@@ -186,9 +202,7 @@ export class IdentityManager {
|
|
|
186
202
|
return this._spaceManager.constructSpace({
|
|
187
203
|
metadata: {
|
|
188
204
|
key: spaceRecord.spaceKey,
|
|
189
|
-
genesisFeedKey: spaceRecord.genesisFeedKey
|
|
190
|
-
controlFeedKey: spaceRecord.writeControlFeedKey,
|
|
191
|
-
dataFeedKey: spaceRecord.writeDataFeedKey
|
|
205
|
+
genesisFeedKey: spaceRecord.genesisFeedKey
|
|
192
206
|
},
|
|
193
207
|
swarmIdentity,
|
|
194
208
|
onNetworkConnection: () => {}
|
|
@@ -60,10 +60,10 @@ describe('identity/identity', () => {
|
|
|
60
60
|
spaceKey,
|
|
61
61
|
protocol,
|
|
62
62
|
genesisFeed: controlFeed,
|
|
63
|
-
controlFeed,
|
|
64
|
-
dataFeed,
|
|
65
63
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
66
|
-
})
|
|
64
|
+
})
|
|
65
|
+
.setControlFeed(controlFeed)
|
|
66
|
+
.setDataFeed(dataFeed);
|
|
67
67
|
|
|
68
68
|
const identity = new Identity({
|
|
69
69
|
signer: keyring,
|
|
@@ -88,8 +88,7 @@ describe('identity/identity', () => {
|
|
|
88
88
|
|
|
89
89
|
for (const credential of credentials) {
|
|
90
90
|
await identity.controlPipeline.writer.write({
|
|
91
|
-
|
|
92
|
-
credential
|
|
91
|
+
credential: { credential }
|
|
93
92
|
});
|
|
94
93
|
}
|
|
95
94
|
}
|
|
@@ -166,10 +165,10 @@ describe('identity/identity', () => {
|
|
|
166
165
|
spaceKey,
|
|
167
166
|
protocol,
|
|
168
167
|
genesisFeed: controlFeed,
|
|
169
|
-
controlFeed,
|
|
170
|
-
dataFeed,
|
|
171
168
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
172
|
-
})
|
|
169
|
+
})
|
|
170
|
+
.setControlFeed(controlFeed)
|
|
171
|
+
.setDataFeed(dataFeed);
|
|
173
172
|
|
|
174
173
|
const identity = (identity1 = new Identity({
|
|
175
174
|
signer: keyring,
|
|
@@ -194,8 +193,7 @@ describe('identity/identity', () => {
|
|
|
194
193
|
|
|
195
194
|
for (const credential of credentials) {
|
|
196
195
|
await identity.controlPipeline.writer.write({
|
|
197
|
-
|
|
198
|
-
credential
|
|
196
|
+
credential: { credential }
|
|
199
197
|
});
|
|
200
198
|
}
|
|
201
199
|
}
|
|
@@ -246,10 +244,10 @@ describe('identity/identity', () => {
|
|
|
246
244
|
spaceKey,
|
|
247
245
|
protocol,
|
|
248
246
|
genesisFeed: await feedStore.openFeed(genesisFeedKey),
|
|
249
|
-
controlFeed,
|
|
250
|
-
dataFeed,
|
|
251
247
|
feedProvider: (feedKey) => feedStore.openFeed(feedKey)
|
|
252
|
-
})
|
|
248
|
+
})
|
|
249
|
+
.setControlFeed(controlFeed)
|
|
250
|
+
.setDataFeed(dataFeed);
|
|
253
251
|
|
|
254
252
|
const identity = (identity2 = new Identity({
|
|
255
253
|
signer: keyring,
|
|
@@ -268,15 +266,16 @@ describe('identity/identity', () => {
|
|
|
268
266
|
{
|
|
269
267
|
const signer = identity1.getIdentityCredentialSigner();
|
|
270
268
|
void identity1.controlPipeline.writer.write({
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
269
|
+
credential: {
|
|
270
|
+
credential: await signer.createCredential({
|
|
271
|
+
subject: identity2.deviceKey,
|
|
272
|
+
assertion: {
|
|
273
|
+
'@type': 'dxos.halo.credentials.AuthorizedDevice',
|
|
274
|
+
identityKey: identity1.identityKey,
|
|
275
|
+
deviceKey: identity2.deviceKey
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}
|
|
280
279
|
});
|
|
281
280
|
|
|
282
281
|
await identity2.ready();
|
|
@@ -14,11 +14,12 @@ import {
|
|
|
14
14
|
CredentialConsumer
|
|
15
15
|
} from '@dxos/credentials';
|
|
16
16
|
import { Signer } from '@dxos/crypto';
|
|
17
|
+
import { failUndefined } from '@dxos/debug';
|
|
17
18
|
import { Space } from '@dxos/echo-db';
|
|
18
19
|
import { writeMessages } from '@dxos/feed-store';
|
|
19
20
|
import { PublicKey } from '@dxos/keys';
|
|
20
21
|
import { log } from '@dxos/log';
|
|
21
|
-
import {
|
|
22
|
+
import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
|
|
22
23
|
import { AdmittedFeed, ProfileDocument } from '@dxos/protocols/proto/dxos/halo/credentials';
|
|
23
24
|
import { HaloAdmissionCredentials } from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
24
25
|
import { ComplexSet } from '@dxos/util';
|
|
@@ -101,7 +102,7 @@ export class Identity {
|
|
|
101
102
|
async ready() {
|
|
102
103
|
await this._deviceStateMachine.processor.deviceChainReady.wait();
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
await this.controlPipeline.state.waitUntilReachedTargetTimeframe({ timeout: 3_000 });
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
get profileDocument(): ProfileDocument | undefined {
|
|
@@ -123,11 +124,15 @@ export class Identity {
|
|
|
123
124
|
return this.space.genesisFeedKey;
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
get deviceCredentialChain() {
|
|
128
|
+
return this._deviceStateMachine.processor.deviceCredentialChain;
|
|
129
|
+
}
|
|
130
|
+
|
|
126
131
|
getAdmissionCredentials(): HaloAdmissionCredentials {
|
|
127
132
|
return {
|
|
128
133
|
deviceKey: this.deviceKey,
|
|
129
|
-
controlFeedKey: this.space.controlFeedKey,
|
|
130
|
-
dataFeedKey: this.space.dataFeedKey
|
|
134
|
+
controlFeedKey: this.space.controlFeedKey ?? failUndefined(),
|
|
135
|
+
dataFeedKey: this.space.dataFeedKey ?? failUndefined()
|
|
131
136
|
};
|
|
132
137
|
}
|
|
133
138
|
|
|
@@ -191,7 +196,7 @@ export class Identity {
|
|
|
191
196
|
designation: AdmittedFeed.Designation.DATA
|
|
192
197
|
}
|
|
193
198
|
})
|
|
194
|
-
].map((credential):
|
|
199
|
+
].map((credential): FeedMessage.Payload => ({ credential: { credential } }))
|
|
195
200
|
);
|
|
196
201
|
}
|
|
197
202
|
}
|
|
@@ -3,16 +3,13 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { expect } from 'chai';
|
|
6
|
-
import assert from 'node:assert';
|
|
7
6
|
|
|
8
|
-
import { asyncChain
|
|
9
|
-
import { raise } from '@dxos/debug';
|
|
10
|
-
import { PublicKey } from '@dxos/keys';
|
|
11
|
-
import { Invitation } from '@dxos/protocols/proto/dxos/client/services';
|
|
7
|
+
import { asyncChain } from '@dxos/async';
|
|
12
8
|
import { describe, test, afterTest } from '@dxos/test';
|
|
13
9
|
|
|
14
10
|
import { ServiceContext } from '../services';
|
|
15
11
|
import { createPeers, createServiceContext } from '../testing';
|
|
12
|
+
import { performInvitation } from '../testing/invitation-utils';
|
|
16
13
|
|
|
17
14
|
const closeAfterTest = async (peer: ServiceContext) => {
|
|
18
15
|
afterTest(() => peer.close());
|
|
@@ -35,45 +32,7 @@ describe('services/halo', () => {
|
|
|
35
32
|
const identity1 = await host.createIdentity();
|
|
36
33
|
expect(host.identityManager.identity).to.eq(identity1);
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const complete1 = new Trigger<PublicKey>();
|
|
41
|
-
const complete2 = new Trigger<PublicKey>();
|
|
42
|
-
|
|
43
|
-
const authenticationCode = new Trigger<string>();
|
|
44
|
-
|
|
45
|
-
observable1.subscribe({
|
|
46
|
-
onConnecting: async (invitation1: Invitation) => {
|
|
47
|
-
const observable2 = guest.haloInvitations!.acceptInvitation(invitation1);
|
|
48
|
-
observable2.subscribe({
|
|
49
|
-
onConnecting: async () => {},
|
|
50
|
-
onConnected: async (invitation2: Invitation) => {
|
|
51
|
-
expect(invitation1.swarmKey).to.eq(invitation2.swarmKey);
|
|
52
|
-
},
|
|
53
|
-
onAuthenticating: async () => {
|
|
54
|
-
await observable2.authenticate(await authenticationCode.wait());
|
|
55
|
-
},
|
|
56
|
-
onSuccess: (invitation: Invitation) => {
|
|
57
|
-
complete2.wake(invitation.identityKey!);
|
|
58
|
-
},
|
|
59
|
-
onCancelled: () => raise(new Error()),
|
|
60
|
-
onTimeout: (err: Error) => raise(new Error(err.message)),
|
|
61
|
-
onError: (err: Error) => raise(new Error(err.message))
|
|
62
|
-
});
|
|
63
|
-
},
|
|
64
|
-
onConnected: (invitation: Invitation) => {
|
|
65
|
-
assert(invitation.authenticationCode);
|
|
66
|
-
authenticationCode.wake(invitation.authenticationCode);
|
|
67
|
-
},
|
|
68
|
-
onSuccess: (invitation: Invitation) => {
|
|
69
|
-
complete1.wake(host.identityManager.identity!.identityKey);
|
|
70
|
-
},
|
|
71
|
-
onCancelled: () => raise(new Error()),
|
|
72
|
-
onTimeout: (err: Error) => raise(new Error(err.message)),
|
|
73
|
-
onError: (err: Error) => raise(new Error(err.message))
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const [identityKey1, identityKey2] = await Promise.all([complete1.wait(), complete2.wait()]);
|
|
77
|
-
expect(identityKey1).to.deep.eq(identityKey2);
|
|
35
|
+
await performInvitation(host.haloInvitations, guest.haloInvitations, undefined);
|
|
36
|
+
expect(guest.identityManager.identity?.identityKey).to.deep.eq(identity1.identityKey);
|
|
78
37
|
});
|
|
79
38
|
});
|
|
@@ -7,7 +7,7 @@ import assert from 'node:assert';
|
|
|
7
7
|
import { scheduleTask, sleep, Trigger } from '@dxos/async';
|
|
8
8
|
import { Context } from '@dxos/context';
|
|
9
9
|
import { generatePasscode } from '@dxos/credentials';
|
|
10
|
-
import {
|
|
10
|
+
import { Keyring } from '@dxos/keyring';
|
|
11
11
|
import { PublicKey } from '@dxos/keys';
|
|
12
12
|
import { log } from '@dxos/log';
|
|
13
13
|
import { createTeleportProtocolFactory, NetworkManager, StarTopology } from '@dxos/network-manager';
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from '@dxos/protocols/proto/dxos/halo/invitations';
|
|
23
23
|
import { ExtensionContext, RpcExtension } from '@dxos/teleport';
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { Identity, JoinIdentityParams } from '../identity';
|
|
26
26
|
import {
|
|
27
27
|
AuthenticatingInvitationProvider,
|
|
28
28
|
AUTHENTICATION_CODE_LENGTH,
|
|
@@ -33,6 +33,14 @@ import {
|
|
|
33
33
|
} from './invitations';
|
|
34
34
|
import { AbstractInvitationsHandler, InvitationsOptions } from './invitations-handler';
|
|
35
35
|
|
|
36
|
+
type HaloInvitationsHandlerParams = {
|
|
37
|
+
networkManager: NetworkManager;
|
|
38
|
+
keyring: Keyring;
|
|
39
|
+
|
|
40
|
+
getIdentity: () => Identity;
|
|
41
|
+
acceptIdentity: (identity: JoinIdentityParams) => Promise<Identity>;
|
|
42
|
+
};
|
|
43
|
+
|
|
36
44
|
/**
|
|
37
45
|
* Handles the life-cycle of Halo invitations between peers.
|
|
38
46
|
*/
|
|
@@ -40,10 +48,9 @@ import { AbstractInvitationsHandler, InvitationsOptions } from './invitations-ha
|
|
|
40
48
|
export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
41
49
|
// prettier-ignore
|
|
42
50
|
constructor(
|
|
43
|
-
|
|
44
|
-
private readonly _identityManager: IdentityManager
|
|
51
|
+
private readonly _params: HaloInvitationsHandlerParams
|
|
45
52
|
) {
|
|
46
|
-
super(networkManager);
|
|
53
|
+
super(_params.networkManager);
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
/**
|
|
@@ -52,7 +59,7 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
|
52
59
|
createInvitation(context: void, options?: InvitationsOptions): CancellableInvitationObservable {
|
|
53
60
|
const { type, timeout = INVITATION_TIMEOUT, swarmKey } = options ?? {};
|
|
54
61
|
assert(type !== Invitation.Type.OFFLINE);
|
|
55
|
-
const identity = this.
|
|
62
|
+
const identity = this._params.getIdentity();
|
|
56
63
|
|
|
57
64
|
// TODO(dmaretskyi): Add invitation kind: halo/space.
|
|
58
65
|
const invitation: Invitation = {
|
|
@@ -78,8 +85,8 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
|
78
85
|
const complete = new Trigger<PublicKey>();
|
|
79
86
|
|
|
80
87
|
// Called for every connecting peer.
|
|
81
|
-
const createExtension = ()
|
|
82
|
-
|
|
88
|
+
const createExtension = () =>
|
|
89
|
+
new HostHaloInvitationExtension({
|
|
83
90
|
requestAdmission: async () => {
|
|
84
91
|
log('responding with admission offer', {
|
|
85
92
|
host: identity.deviceKey
|
|
@@ -145,8 +152,6 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
|
145
152
|
});
|
|
146
153
|
}
|
|
147
154
|
});
|
|
148
|
-
return hostInvitationExtension;
|
|
149
|
-
};
|
|
150
155
|
|
|
151
156
|
scheduleTask(ctx, async () => {
|
|
152
157
|
const topic = invitation.swarmKey!;
|
|
@@ -224,18 +229,29 @@ export class HaloInvitationsHandler extends AbstractInvitationsHandler {
|
|
|
224
229
|
await extension.rpc.HaloHostService.authenticate({ authenticationCode });
|
|
225
230
|
}
|
|
226
231
|
|
|
227
|
-
|
|
232
|
+
const deviceKey = await this._params.keyring.createKey();
|
|
233
|
+
const controlFeedKey = await this._params.keyring.createKey();
|
|
234
|
+
const dataFeedKey = await this._params.keyring.createKey();
|
|
235
|
+
|
|
236
|
+
// 3. Send admission credentials to host (with local identity keys).
|
|
237
|
+
log('presenting admission credentials', { identityKey, deviceKey, controlFeedKey, dataFeedKey });
|
|
238
|
+
await extension.rpc.HaloHostService.presentAdmissionCredentials({
|
|
239
|
+
deviceKey,
|
|
240
|
+
controlFeedKey,
|
|
241
|
+
dataFeedKey
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// 4. Create local identity.
|
|
228
245
|
// TODO(burdon): Abandon if does not complete (otherwise retry will fail).
|
|
229
|
-
const identity = await this.
|
|
246
|
+
const identity = await this._params.acceptIdentity({
|
|
230
247
|
identityKey,
|
|
248
|
+
deviceKey,
|
|
231
249
|
haloSpaceKey,
|
|
232
|
-
haloGenesisFeedKey: genesisFeedKey
|
|
250
|
+
haloGenesisFeedKey: genesisFeedKey,
|
|
251
|
+
controlFeedKey,
|
|
252
|
+
dataFeedKey
|
|
233
253
|
});
|
|
234
254
|
|
|
235
|
-
// 4. Send admission credentials to host (with local identity keys).
|
|
236
|
-
log('presenting admission credentials', { guest: identity.deviceKey, identityKey });
|
|
237
|
-
await extension.rpc.HaloHostService.presentAdmissionCredentials(identity.getAdmissionCredentials());
|
|
238
|
-
|
|
239
255
|
// 5. Success.
|
|
240
256
|
log('admitted by host', { guest: identity.deviceKey, identityKey });
|
|
241
257
|
complete.wake(identityKey);
|
|
@@ -18,7 +18,7 @@ export const AUTHENTICATION_CODE_LENGTH = 6;
|
|
|
18
18
|
export const INVITATION_TIMEOUT = 3 * 60_000; // 3 mins.
|
|
19
19
|
|
|
20
20
|
// TODO(burdon): Don't close until RPC has complete (bug).
|
|
21
|
-
export const ON_CLOSE_DELAY =
|
|
21
|
+
export const ON_CLOSE_DELAY = 1000;
|
|
22
22
|
|
|
23
23
|
export interface InvitationsService {
|
|
24
24
|
createInvitation(invitation: Invitation): Stream<Invitation>;
|
|
@@ -46,6 +46,7 @@ export interface CancellableInvitationObservable extends CancellableObservable<I
|
|
|
46
46
|
get invitation(): Invitation | undefined;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// TODO(wittjosiah): Update with Observable.value.
|
|
49
50
|
export class InvitationObservableProvider
|
|
50
51
|
extends CancellableObservableProvider<InvitationEvents>
|
|
51
52
|
implements CancellableInvitationObservable
|