@dxos/client-services 0.1.0

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.
Files changed (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/src/errors.d.ts +8 -0
  4. package/dist/src/errors.d.ts.map +1 -0
  5. package/dist/src/errors.js +21 -0
  6. package/dist/src/errors.js.map +1 -0
  7. package/dist/src/index.d.ts +7 -0
  8. package/dist/src/index.d.ts.map +1 -0
  9. package/dist/src/index.js +26 -0
  10. package/dist/src/index.js.map +1 -0
  11. package/dist/src/packlets/devtools/devtools-host-events.d.ts +5 -0
  12. package/dist/src/packlets/devtools/devtools-host-events.d.ts.map +1 -0
  13. package/dist/src/packlets/devtools/devtools-host-events.js +14 -0
  14. package/dist/src/packlets/devtools/devtools-host-events.js.map +1 -0
  15. package/dist/src/packlets/devtools/feeds.d.ts +11 -0
  16. package/dist/src/packlets/devtools/feeds.d.ts.map +1 -0
  17. package/dist/src/packlets/devtools/feeds.js +72 -0
  18. package/dist/src/packlets/devtools/feeds.js.map +1 -0
  19. package/dist/src/packlets/devtools/index.d.ts +5 -0
  20. package/dist/src/packlets/devtools/index.d.ts.map +1 -0
  21. package/dist/src/packlets/devtools/index.js +24 -0
  22. package/dist/src/packlets/devtools/index.js.map +1 -0
  23. package/dist/src/packlets/devtools/network.d.ts +19 -0
  24. package/dist/src/packlets/devtools/network.d.ts.map +1 -0
  25. package/dist/src/packlets/devtools/network.js +79 -0
  26. package/dist/src/packlets/devtools/network.js.map +1 -0
  27. package/dist/src/packlets/devtools/spaces.d.ts +5 -0
  28. package/dist/src/packlets/devtools/spaces.d.ts.map +1 -0
  29. package/dist/src/packlets/devtools/spaces.js +38 -0
  30. package/dist/src/packlets/devtools/spaces.js.map +1 -0
  31. package/dist/src/packlets/identity/authenticator.d.ts +7 -0
  32. package/dist/src/packlets/identity/authenticator.d.ts.map +1 -0
  33. package/dist/src/packlets/identity/authenticator.js +40 -0
  34. package/dist/src/packlets/identity/authenticator.js.map +1 -0
  35. package/dist/src/packlets/identity/authenticator.test.d.ts +2 -0
  36. package/dist/src/packlets/identity/authenticator.test.d.ts.map +1 -0
  37. package/dist/src/packlets/identity/authenticator.test.js +32 -0
  38. package/dist/src/packlets/identity/authenticator.test.js.map +1 -0
  39. package/dist/src/packlets/identity/identity-manager.d.ts +35 -0
  40. package/dist/src/packlets/identity/identity-manager.d.ts.map +1 -0
  41. package/dist/src/packlets/identity/identity-manager.js +151 -0
  42. package/dist/src/packlets/identity/identity-manager.js.map +1 -0
  43. package/dist/src/packlets/identity/identity-manager.test.d.ts +2 -0
  44. package/dist/src/packlets/identity/identity-manager.test.d.ts.map +1 -0
  45. package/dist/src/packlets/identity/identity-manager.test.js +92 -0
  46. package/dist/src/packlets/identity/identity-manager.test.js.map +1 -0
  47. package/dist/src/packlets/identity/identity.d.ts +43 -0
  48. package/dist/src/packlets/identity/identity.d.ts.map +1 -0
  49. package/dist/src/packlets/identity/identity.js +74 -0
  50. package/dist/src/packlets/identity/identity.js.map +1 -0
  51. package/dist/src/packlets/identity/identity.test.d.ts +2 -0
  52. package/dist/src/packlets/identity/identity.test.d.ts.map +1 -0
  53. package/dist/src/packlets/identity/identity.test.js +258 -0
  54. package/dist/src/packlets/identity/identity.test.js.map +1 -0
  55. package/dist/src/packlets/identity/index.d.ts +4 -0
  56. package/dist/src/packlets/identity/index.d.ts.map +1 -0
  57. package/dist/src/packlets/identity/index.js +23 -0
  58. package/dist/src/packlets/identity/index.js.map +1 -0
  59. package/dist/src/packlets/invitations/common.d.ts +35 -0
  60. package/dist/src/packlets/invitations/common.d.ts.map +1 -0
  61. package/dist/src/packlets/invitations/common.js +12 -0
  62. package/dist/src/packlets/invitations/common.js.map +1 -0
  63. package/dist/src/packlets/invitations/halo-invitations.d.ts +23 -0
  64. package/dist/src/packlets/invitations/halo-invitations.d.ts.map +1 -0
  65. package/dist/src/packlets/invitations/halo-invitations.js +150 -0
  66. package/dist/src/packlets/invitations/halo-invitations.js.map +1 -0
  67. package/dist/src/packlets/invitations/index.d.ts +5 -0
  68. package/dist/src/packlets/invitations/index.d.ts.map +1 -0
  69. package/dist/src/packlets/invitations/index.js +24 -0
  70. package/dist/src/packlets/invitations/index.js.map +1 -0
  71. package/dist/src/packlets/invitations/invitation-wrapper.d.ts +36 -0
  72. package/dist/src/packlets/invitations/invitation-wrapper.d.ts.map +1 -0
  73. package/dist/src/packlets/invitations/invitation-wrapper.js +104 -0
  74. package/dist/src/packlets/invitations/invitation-wrapper.js.map +1 -0
  75. package/dist/src/packlets/invitations/observable.test.d.ts +2 -0
  76. package/dist/src/packlets/invitations/observable.test.d.ts.map +1 -0
  77. package/dist/src/packlets/invitations/observable.test.js +43 -0
  78. package/dist/src/packlets/invitations/observable.test.js.map +1 -0
  79. package/dist/src/packlets/invitations/space-invitations.d.ts +25 -0
  80. package/dist/src/packlets/invitations/space-invitations.d.ts.map +1 -0
  81. package/dist/src/packlets/invitations/space-invitations.js +136 -0
  82. package/dist/src/packlets/invitations/space-invitations.js.map +1 -0
  83. package/dist/src/packlets/services/impl/devtools.d.ts +34 -0
  84. package/dist/src/packlets/services/impl/devtools.d.ts.map +1 -0
  85. package/dist/src/packlets/services/impl/devtools.js +77 -0
  86. package/dist/src/packlets/services/impl/devtools.js.map +1 -0
  87. package/dist/src/packlets/services/impl/halo.d.ts +20 -0
  88. package/dist/src/packlets/services/impl/halo.d.ts.map +1 -0
  89. package/dist/src/packlets/services/impl/halo.js +93 -0
  90. package/dist/src/packlets/services/impl/halo.js.map +1 -0
  91. package/dist/src/packlets/services/impl/index.d.ts +6 -0
  92. package/dist/src/packlets/services/impl/index.d.ts.map +1 -0
  93. package/dist/src/packlets/services/impl/index.js +26 -0
  94. package/dist/src/packlets/services/impl/index.js.map +1 -0
  95. package/dist/src/packlets/services/impl/party.d.ts +25 -0
  96. package/dist/src/packlets/services/impl/party.d.ts.map +1 -0
  97. package/dist/src/packlets/services/impl/party.js +265 -0
  98. package/dist/src/packlets/services/impl/party.js.map +1 -0
  99. package/dist/src/packlets/services/impl/profile.d.ts +19 -0
  100. package/dist/src/packlets/services/impl/profile.d.ts.map +1 -0
  101. package/dist/src/packlets/services/impl/profile.js +120 -0
  102. package/dist/src/packlets/services/impl/profile.js.map +1 -0
  103. package/dist/src/packlets/services/impl/system.d.ts +9 -0
  104. package/dist/src/packlets/services/impl/system.d.ts.map +1 -0
  105. package/dist/src/packlets/services/impl/system.js +20 -0
  106. package/dist/src/packlets/services/impl/system.js.map +1 -0
  107. package/dist/src/packlets/services/impl/tracing.d.ts +12 -0
  108. package/dist/src/packlets/services/impl/tracing.d.ts.map +1 -0
  109. package/dist/src/packlets/services/impl/tracing.js +22 -0
  110. package/dist/src/packlets/services/impl/tracing.js.map +1 -0
  111. package/dist/src/packlets/services/index.d.ts +6 -0
  112. package/dist/src/packlets/services/index.d.ts.map +1 -0
  113. package/dist/src/packlets/services/index.js +25 -0
  114. package/dist/src/packlets/services/index.js.map +1 -0
  115. package/dist/src/packlets/services/service-context.d.ts +36 -0
  116. package/dist/src/packlets/services/service-context.d.ts.map +1 -0
  117. package/dist/src/packlets/services/service-context.js +112 -0
  118. package/dist/src/packlets/services/service-context.js.map +1 -0
  119. package/dist/src/packlets/services/service-factory.d.ts +15 -0
  120. package/dist/src/packlets/services/service-factory.d.ts.map +1 -0
  121. package/dist/src/packlets/services/service-factory.js +25 -0
  122. package/dist/src/packlets/services/service-factory.js.map +1 -0
  123. package/dist/src/packlets/services/service-host.d.ts +27 -0
  124. package/dist/src/packlets/services/service-host.d.ts.map +1 -0
  125. package/dist/src/packlets/services/service-host.js +78 -0
  126. package/dist/src/packlets/services/service-host.js.map +1 -0
  127. package/dist/src/packlets/services/service-host.test.d.ts +2 -0
  128. package/dist/src/packlets/services/service-host.test.d.ts.map +1 -0
  129. package/dist/src/packlets/services/service-host.test.js +78 -0
  130. package/dist/src/packlets/services/service-host.test.js.map +1 -0
  131. package/dist/src/packlets/services/services.d.ts +19 -0
  132. package/dist/src/packlets/services/services.d.ts.map +1 -0
  133. package/dist/src/packlets/services/services.js +19 -0
  134. package/dist/src/packlets/services/services.js.map +1 -0
  135. package/dist/src/packlets/services/signer.d.ts +9 -0
  136. package/dist/src/packlets/services/signer.d.ts.map +1 -0
  137. package/dist/src/packlets/services/signer.js +6 -0
  138. package/dist/src/packlets/services/signer.js.map +1 -0
  139. package/dist/src/packlets/services/testing/halo.test.d.ts +2 -0
  140. package/dist/src/packlets/services/testing/halo.test.d.ts.map +1 -0
  141. package/dist/src/packlets/services/testing/halo.test.js +34 -0
  142. package/dist/src/packlets/services/testing/halo.test.js.map +1 -0
  143. package/dist/src/packlets/services/testing/spaces.test.d.ts +2 -0
  144. package/dist/src/packlets/services/testing/spaces.test.d.ts.map +1 -0
  145. package/dist/src/packlets/services/testing/spaces.test.js +68 -0
  146. package/dist/src/packlets/services/testing/spaces.test.js.map +1 -0
  147. package/dist/src/packlets/services/testing/testing.d.ts +8 -0
  148. package/dist/src/packlets/services/testing/testing.d.ts.map +1 -0
  149. package/dist/src/packlets/services/testing/testing.js +23 -0
  150. package/dist/src/packlets/services/testing/testing.js.map +1 -0
  151. package/dist/src/packlets/storage/index.d.ts +2 -0
  152. package/dist/src/packlets/storage/index.d.ts.map +1 -0
  153. package/dist/src/packlets/storage/index.js +21 -0
  154. package/dist/src/packlets/storage/index.js.map +1 -0
  155. package/dist/src/packlets/storage/storage.d.ts +7 -0
  156. package/dist/src/packlets/storage/storage.d.ts.map +1 -0
  157. package/dist/src/packlets/storage/storage.js +89 -0
  158. package/dist/src/packlets/storage/storage.js.map +1 -0
  159. package/package.json +61 -0
  160. package/src/errors.ts +19 -0
  161. package/src/index.ts +11 -0
  162. package/src/packlets/devtools/devtools-host-events.ts +8 -0
  163. package/src/packlets/devtools/feeds.ts +91 -0
  164. package/src/packlets/devtools/index.ts +8 -0
  165. package/src/packlets/devtools/network.ts +89 -0
  166. package/src/packlets/devtools/spaces.ts +50 -0
  167. package/src/packlets/identity/authenticator.test.ts +32 -0
  168. package/src/packlets/identity/authenticator.ts +49 -0
  169. package/src/packlets/identity/identity-manager.test.ts +121 -0
  170. package/src/packlets/identity/identity-manager.ts +207 -0
  171. package/src/packlets/identity/identity.test.ts +295 -0
  172. package/src/packlets/identity/identity.ts +109 -0
  173. package/src/packlets/identity/index.ts +7 -0
  174. package/src/packlets/invitations/common.ts +48 -0
  175. package/src/packlets/invitations/halo-invitations.ts +162 -0
  176. package/src/packlets/invitations/index.ts +8 -0
  177. package/src/packlets/invitations/invitation-wrapper.ts +140 -0
  178. package/src/packlets/invitations/observable.test.ts +67 -0
  179. package/src/packlets/invitations/space-invitations.ts +159 -0
  180. package/src/packlets/services/impl/devtools.ts +141 -0
  181. package/src/packlets/services/impl/halo.ts +107 -0
  182. package/src/packlets/services/impl/index.ts +11 -0
  183. package/src/packlets/services/impl/party.ts +305 -0
  184. package/src/packlets/services/impl/profile.ts +141 -0
  185. package/src/packlets/services/impl/system.ts +19 -0
  186. package/src/packlets/services/impl/tracing.ts +21 -0
  187. package/src/packlets/services/index.ts +9 -0
  188. package/src/packlets/services/service-context.ts +151 -0
  189. package/src/packlets/services/service-factory.ts +35 -0
  190. package/src/packlets/services/service-host.test.ts +88 -0
  191. package/src/packlets/services/service-host.ts +101 -0
  192. package/src/packlets/services/services.ts +39 -0
  193. package/src/packlets/services/signer.ts +13 -0
  194. package/src/packlets/services/testing/halo.test.ts +40 -0
  195. package/src/packlets/services/testing/spaces.test.ts +87 -0
  196. package/src/packlets/services/testing/testing.ts +28 -0
  197. package/src/packlets/storage/index.ts +5 -0
  198. package/src/packlets/storage/storage.ts +97 -0
  199. package/src/typings.d.ts +5 -0
@@ -0,0 +1,121 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import expect from 'expect';
6
+
7
+ import { valueEncoding, MetadataStore } from '@dxos/echo-db';
8
+ import { FeedFactory, FeedStore } from '@dxos/feed-store';
9
+ import { Keyring } from '@dxos/keyring';
10
+ import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
11
+ import { ModelFactory } from '@dxos/model-factory';
12
+ import { MemoryTransportFactory, NetworkManager } from '@dxos/network-manager';
13
+ import { ObjectModel } from '@dxos/object-model';
14
+ import type { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
15
+ import { createStorage, Storage, StorageType } from '@dxos/random-access-storage';
16
+ import { afterTest } from '@dxos/testutils';
17
+
18
+ import { IdentityManager } from './identity-manager';
19
+
20
+ describe('identity/identity-manager', function () {
21
+ const setupPeer = async ({
22
+ signalContext = new MemorySignalManagerContext(),
23
+ storage = createStorage({ type: StorageType.RAM })
24
+ }: {
25
+ signalContext?: MemorySignalManagerContext;
26
+ storage?: Storage;
27
+ } = {}) => {
28
+ const metadataStore = new MetadataStore(storage.createDirectory('metadata'));
29
+
30
+ const keyring = new Keyring(storage.createDirectory('keyring'));
31
+ const feedStore = new FeedStore<FeedMessage>({
32
+ factory: new FeedFactory<FeedMessage>({
33
+ root: storage.createDirectory('feeds'),
34
+ signer: keyring,
35
+ hypercore: {
36
+ valueEncoding
37
+ }
38
+ })
39
+ });
40
+
41
+ afterTest(() => feedStore.close());
42
+
43
+ const networkManager = new NetworkManager({
44
+ signalManager: new MemorySignalManager(signalContext),
45
+ transportFactory: MemoryTransportFactory
46
+ });
47
+
48
+ const identityManager = new IdentityManager(
49
+ metadataStore,
50
+ feedStore,
51
+ keyring,
52
+ networkManager,
53
+ new ModelFactory().registerModel(ObjectModel)
54
+ );
55
+
56
+ return {
57
+ identityManager,
58
+ feedStore
59
+ };
60
+ };
61
+
62
+ it('creates identity', async function () {
63
+ const { identityManager } = await setupPeer();
64
+ await identityManager.open();
65
+ afterTest(() => identityManager.close());
66
+
67
+ const identity = await identityManager.createIdentity();
68
+ expect(identity).toBeTruthy();
69
+ });
70
+
71
+ it('reload from storage', async function () {
72
+ const storage = createStorage({ type: StorageType.RAM });
73
+
74
+ const peer1 = await setupPeer({ storage });
75
+ await peer1.identityManager.open();
76
+ const identity1 = await peer1.identityManager.createIdentity();
77
+ await peer1.identityManager.close();
78
+ await peer1.feedStore.close();
79
+
80
+ const peer2 = await setupPeer({ storage });
81
+ await peer2.identityManager.open();
82
+
83
+ expect(peer2.identityManager.identity).toBeDefined();
84
+ expect(peer2.identityManager.identity!.identityKey).toEqual(identity1.identityKey);
85
+ expect(peer2.identityManager.identity!.deviceKey).toEqual(identity1.deviceKey);
86
+
87
+ // TODO(dmaretskyi): Check that identity is "alive" (space is working and can write mutations).
88
+ });
89
+
90
+ it('admit another device', async function () {
91
+ const signalContext = new MemorySignalManagerContext();
92
+
93
+ const peer1 = await setupPeer({ signalContext });
94
+ const identity1 = await peer1.identityManager.createIdentity();
95
+
96
+ const peer2 = await setupPeer({ signalContext });
97
+
98
+ // Identity2 is not yet ready at this point. Peer1 needs to admit peer2 device key and feed keys.
99
+ const identity2 = await peer2.identityManager.acceptIdentity({
100
+ identityKey: identity1.identityKey,
101
+ haloSpaceKey: identity1.haloSpaceKey,
102
+ haloGenesisFeedKey: identity1.haloGenesisFeedKey
103
+ });
104
+
105
+ await identity1.controlPipeline.writer.write({
106
+ '@type': 'dxos.echo.feed.CredentialsMessage',
107
+ credential: await identity1.getIdentityCredentialSigner().createCredential({
108
+ subject: identity2.deviceKey,
109
+ assertion: {
110
+ '@type': 'dxos.halo.credentials.AuthorizedDevice',
111
+ identityKey: identity2.identityKey,
112
+ deviceKey: identity2.deviceKey
113
+ }
114
+ })
115
+ });
116
+
117
+ // TODO(dmaretskyi): We'd also need to admit device2's feeds otherwise messages from them won't be processed by the pipeline.
118
+ // This would mean that peer2 has replicated it's device credential chain from peer1 and is ready to issue credentials.
119
+ await identity2.ready();
120
+ });
121
+ });
@@ -0,0 +1,207 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import assert from 'assert';
6
+
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';
19
+ import { Keyring } from '@dxos/keyring';
20
+ import { PublicKey } from '@dxos/keys';
21
+ 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
+ import { AdmittedFeed, IdentityRecord, SpaceRecord } from '@dxos/protocols/proto/dxos/halo/credentials';
26
+
27
+ import { Identity } from '../identity';
28
+
29
+ interface ConstructSpaceParams {
30
+ spaceRecord: SpaceRecord;
31
+ swarmIdentity: SwarmIdentity;
32
+ networkPlugins?: Plugin[];
33
+ }
34
+
35
+ export type JoinIdentityParams = {
36
+ identityKey: PublicKey;
37
+ haloSpaceKey: PublicKey;
38
+ haloGenesisFeedKey: PublicKey;
39
+ };
40
+
41
+ // TODO(dmaretskyi): Rename: represents the peer's state machine.
42
+ export class IdentityManager {
43
+ readonly stateUpdate = new Event();
44
+
45
+ private _identity?: Identity;
46
+
47
+ // TODO(burdon): IdentityManagerParams.
48
+ // TODO(dmaretskyi): Perhaps this should take/generate the peerKey outside of an initialized identity.
49
+ constructor(
50
+ private readonly _metadataStore: MetadataStore,
51
+ private readonly _feedStore: FeedStore<FeedMessage>,
52
+ private readonly _keyring: Keyring,
53
+ private readonly _networkManager: NetworkManager,
54
+ private readonly _modelFactory: ModelFactory
55
+ ) {}
56
+
57
+ get identity() {
58
+ return this._identity;
59
+ }
60
+
61
+ async open() {
62
+ await this._metadataStore.load();
63
+
64
+ const identityRecord = this._metadataStore.getIdentityRecord();
65
+ if (identityRecord) {
66
+ this._identity = await this._constructIdentity(identityRecord);
67
+ await this._identity.open();
68
+ await this._identity.ready();
69
+ this.stateUpdate.emit();
70
+ }
71
+ }
72
+
73
+ async close() {
74
+ await this._identity?.close();
75
+ }
76
+
77
+ private async _constructIdentity(identityRecord: IdentityRecord) {
78
+ assert(!this._identity);
79
+ log('constructing identity', { identityRecord });
80
+
81
+ const space = await this._constructSpace({
82
+ spaceRecord: identityRecord.haloSpace,
83
+ swarmIdentity: {
84
+ peerKey: identityRecord.deviceKey,
85
+ credentialProvider: MOCK_AUTH_PROVIDER,
86
+ credentialAuthenticator: MOCK_AUTH_VERIFIER
87
+ }
88
+ });
89
+
90
+ log('done', { identityKey: identityRecord.identityKey });
91
+ return new Identity({
92
+ space,
93
+ signer: this._keyring,
94
+ identityKey: identityRecord.identityKey,
95
+ deviceKey: identityRecord.deviceKey
96
+ });
97
+ }
98
+
99
+ private async _constructSpace({ spaceRecord, swarmIdentity }: ConstructSpaceParams) {
100
+ const controlFeed = await this._feedStore.openFeed(spaceRecord.writeControlFeedKey, { writable: true });
101
+ const dataFeed = await this._feedStore.openFeed(spaceRecord.writeDataFeedKey, { writable: true });
102
+
103
+ // The genesis feed will be the same as the control feed if the space was created by the local agent.
104
+ // NOTE: Must be initialized after writable feeds so that it is in a writable state.
105
+ const genesisFeed = await this._feedStore.openFeed(spaceRecord.genesisFeedKey);
106
+
107
+ const protocol = new SpaceProtocol({
108
+ topic: spaceRecord.spaceKey,
109
+ identity: swarmIdentity,
110
+ networkManager: this._networkManager
111
+ });
112
+
113
+ return new Space({
114
+ spaceKey: spaceRecord.spaceKey,
115
+ protocol,
116
+ genesisFeed,
117
+ controlFeed,
118
+ dataFeed,
119
+ feedProvider: (feedKey) => this._feedStore.openFeed(feedKey),
120
+ databaseFactory: async ({ databaseBackend }) =>
121
+ new Database(this._modelFactory, databaseBackend, swarmIdentity.peerKey)
122
+ });
123
+ }
124
+
125
+ async createIdentity() {
126
+ assert(!this._identity, 'Identity already exists.');
127
+ log('creating identity...');
128
+
129
+ const controlFeedKey = await this._keyring.createKey();
130
+ const identityRecord: IdentityRecord = {
131
+ identityKey: await this._keyring.createKey(),
132
+ deviceKey: await this._keyring.createKey(),
133
+ haloSpace: {
134
+ spaceKey: await this._keyring.createKey(),
135
+ genesisFeedKey: controlFeedKey,
136
+ writeControlFeedKey: controlFeedKey,
137
+ writeDataFeedKey: await this._keyring.createKey()
138
+ }
139
+ };
140
+
141
+ const identity = await this._constructIdentity(identityRecord);
142
+ await identity.open();
143
+
144
+ {
145
+ const generator = new CredentialGenerator(this._keyring, identityRecord.identityKey, identityRecord.deviceKey);
146
+ const credentials = [
147
+ // Space genesis.
148
+ ...(await generator.createSpaceGenesis(
149
+ identityRecord.haloSpace.spaceKey,
150
+ identityRecord.haloSpace.genesisFeedKey
151
+ )),
152
+
153
+ // Feed admission.
154
+ await generator.createFeedAdmission(
155
+ identityRecord.haloSpace.spaceKey,
156
+ identityRecord.haloSpace.writeDataFeedKey,
157
+ AdmittedFeed.Designation.DATA
158
+ ),
159
+
160
+ // Device authorization (writes device chain).
161
+ await generator.createDeviceAuthorization(identityRecord.deviceKey)
162
+ ];
163
+
164
+ for (const credential of credentials) {
165
+ await identity.controlPipeline.writer.write({
166
+ '@type': 'dxos.echo.feed.CredentialsMessage',
167
+ credential
168
+ });
169
+ }
170
+ }
171
+
172
+ // await this._keyring.deleteKey(identityRecord.identity_key);
173
+ // await this._keyring.deleteKey(identityRecord.halo_space.space_key);
174
+
175
+ await this._metadataStore.setIdentityRecord(identityRecord);
176
+ this._identity = identity;
177
+ await this._identity.ready();
178
+ this.stateUpdate.emit();
179
+
180
+ log('created identity', { identityKey: identity.identityKey });
181
+ return identity;
182
+ }
183
+
184
+ /**
185
+ * Accept an existing identity. Expects it's device key to be authorized.
186
+ */
187
+ async acceptIdentity(params: JoinIdentityParams) {
188
+ log('accepting identity', { params });
189
+ assert(!this._identity, 'Identity already exists.');
190
+
191
+ const identity = await this._constructIdentity({
192
+ identityKey: params.identityKey,
193
+ deviceKey: await this._keyring.createKey(),
194
+ haloSpace: {
195
+ spaceKey: params.haloSpaceKey,
196
+ genesisFeedKey: params.haloGenesisFeedKey,
197
+ writeControlFeedKey: await this._keyring.createKey(),
198
+ writeDataFeedKey: await this._keyring.createKey()
199
+ }
200
+ });
201
+
202
+ await identity.open();
203
+ this._identity = identity;
204
+ this.stateUpdate.emit();
205
+ return identity;
206
+ }
207
+ }
@@ -0,0 +1,295 @@
1
+ //
2
+ // Copyright 2022 DXOS.org
3
+ //
4
+
5
+ import expect from 'expect';
6
+
7
+ import { CredentialGenerator, verifyCredential, createCredentialSignerWithKey } from '@dxos/credentials';
8
+ import { valueEncoding, Database, MOCK_AUTH_PROVIDER, MOCK_AUTH_VERIFIER, Space, SpaceProtocol } from '@dxos/echo-db';
9
+ import { FeedFactory, FeedStore } from '@dxos/feed-store';
10
+ import { Keyring } from '@dxos/keyring';
11
+ import { PublicKey } from '@dxos/keys';
12
+ import { MemorySignalManager, MemorySignalManagerContext } from '@dxos/messaging';
13
+ import { ModelFactory } from '@dxos/model-factory';
14
+ import { MemoryTransportFactory, NetworkManager } from '@dxos/network-manager';
15
+ import { ObjectModel } from '@dxos/object-model';
16
+ import { FeedMessage } from '@dxos/protocols/proto/dxos/echo/feed';
17
+ import { AdmittedFeed } from '@dxos/protocols/proto/dxos/halo/credentials';
18
+ import { createStorage, StorageType } from '@dxos/random-access-storage';
19
+ import { afterTest } from '@dxos/testutils';
20
+
21
+ import { createHaloAuthProvider, createHaloAuthVerifier } from './authenticator';
22
+ import { Identity } from './identity';
23
+
24
+ const modelFactory = new ModelFactory().registerModel(ObjectModel);
25
+
26
+ describe('identity/identity', function () {
27
+ it('create', async function () {
28
+ const keyring = new Keyring();
29
+ const identityKey = await keyring.createKey();
30
+ const deviceKey = await keyring.createKey();
31
+ const spaceKey = await keyring.createKey();
32
+
33
+ const feedStore = new FeedStore<FeedMessage>({
34
+ factory: new FeedFactory<FeedMessage>({
35
+ root: createStorage({ type: StorageType.RAM }).createDirectory(),
36
+ signer: keyring,
37
+ hypercore: {
38
+ valueEncoding
39
+ }
40
+ })
41
+ });
42
+
43
+ const createFeed = async () => {
44
+ const feedKey = await keyring.createKey();
45
+ return feedStore.openFeed(feedKey, { writable: true });
46
+ };
47
+
48
+ const controlFeed = await createFeed();
49
+ const dataFeed = await createFeed();
50
+
51
+ const protocol = new SpaceProtocol({
52
+ topic: spaceKey,
53
+ identity: {
54
+ peerKey: identityKey,
55
+ credentialProvider: createHaloAuthProvider(createCredentialSignerWithKey(keyring, deviceKey)),
56
+ credentialAuthenticator: createHaloAuthVerifier(() => identity.authorizedDeviceKeys)
57
+ },
58
+ networkManager: new NetworkManager({
59
+ signalManager: new MemorySignalManager(new MemorySignalManagerContext()),
60
+ transportFactory: MemoryTransportFactory
61
+ })
62
+ });
63
+
64
+ const space: Space = new Space({
65
+ spaceKey,
66
+ protocol,
67
+ genesisFeed: controlFeed,
68
+ controlFeed,
69
+ dataFeed,
70
+ feedProvider: (feedKey) => feedStore.openFeed(feedKey),
71
+ databaseFactory: async ({ databaseBackend }) => new Database(modelFactory, databaseBackend, deviceKey)
72
+ });
73
+
74
+ const identity = new Identity({
75
+ signer: keyring,
76
+ identityKey,
77
+ deviceKey,
78
+ space
79
+ });
80
+
81
+ await identity.open();
82
+ afterTest(() => identity.close());
83
+
84
+ //
85
+ // Identity genesis
86
+ //
87
+ {
88
+ const generator = new CredentialGenerator(keyring, identityKey, deviceKey);
89
+ const credentials = [
90
+ ...(await generator.createSpaceGenesis(spaceKey, controlFeed.key)),
91
+ await generator.createDeviceAuthorization(deviceKey),
92
+ await generator.createFeedAdmission(spaceKey, dataFeed.key, AdmittedFeed.Designation.DATA)
93
+ ];
94
+
95
+ for (const credential of credentials) {
96
+ await identity.controlPipeline.writer.write({
97
+ '@type': 'dxos.echo.feed.CredentialsMessage',
98
+ credential
99
+ });
100
+ }
101
+ }
102
+
103
+ // Wait for identity to be ready.
104
+ await identity.ready();
105
+ const identitySigner = identity.getIdentityCredentialSigner();
106
+ const credential = await identitySigner.createCredential({
107
+ subject: identityKey,
108
+ assertion: {
109
+ '@type': 'dxos.halo.credentials.IdentityProfile',
110
+ profile: {
111
+ displayName: 'Alice'
112
+ }
113
+ }
114
+ });
115
+
116
+ expect(credential.issuer).toEqual(identityKey);
117
+ expect(await verifyCredential(credential)).toEqual({ kind: 'pass' });
118
+ });
119
+
120
+ it('two devices', async function () {
121
+ const signalContext = new MemorySignalManagerContext();
122
+
123
+ let spaceKey: PublicKey;
124
+ let genesisFeedKey: PublicKey;
125
+ let identity1: Identity;
126
+ let identity2: Identity;
127
+
128
+ //
129
+ // First device
130
+ //
131
+ {
132
+ const keyring = new Keyring();
133
+ const identityKey = await keyring.createKey();
134
+ const deviceKey = await keyring.createKey();
135
+ spaceKey = await keyring.createKey();
136
+
137
+ const feedStore = new FeedStore<FeedMessage>({
138
+ factory: new FeedFactory<FeedMessage>({
139
+ root: createStorage({ type: StorageType.RAM }).createDirectory(),
140
+ signer: keyring,
141
+ hypercore: {
142
+ valueEncoding
143
+ }
144
+ })
145
+ });
146
+
147
+ const createFeed = async () => {
148
+ const feedKey = await keyring.createKey();
149
+ return feedStore.openFeed(feedKey, { writable: true });
150
+ };
151
+
152
+ const controlFeed = await createFeed();
153
+ genesisFeedKey = controlFeed.key;
154
+
155
+ const dataFeed = await createFeed();
156
+
157
+ const protocol = new SpaceProtocol({
158
+ topic: spaceKey,
159
+ identity: {
160
+ peerKey: deviceKey,
161
+ credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
162
+ credentialAuthenticator: MOCK_AUTH_VERIFIER // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
163
+ },
164
+ networkManager: new NetworkManager({
165
+ signalManager: new MemorySignalManager(signalContext),
166
+ transportFactory: MemoryTransportFactory
167
+ })
168
+ });
169
+
170
+ const space = new Space({
171
+ spaceKey,
172
+ protocol,
173
+ genesisFeed: controlFeed,
174
+ controlFeed,
175
+ dataFeed,
176
+ feedProvider: (feedKey) => feedStore.openFeed(feedKey),
177
+ databaseFactory: async ({ databaseBackend }) => new Database(modelFactory, databaseBackend, deviceKey)
178
+ });
179
+
180
+ const identity = (identity1 = new Identity({
181
+ signer: keyring,
182
+ identityKey,
183
+ deviceKey,
184
+ space
185
+ }));
186
+
187
+ await identity.open();
188
+ afterTest(() => identity.close());
189
+
190
+ //
191
+ // Identity genesis
192
+ //
193
+ {
194
+ const generator = new CredentialGenerator(keyring, identityKey, deviceKey);
195
+ const credentials = [
196
+ ...(await generator.createSpaceGenesis(spaceKey, controlFeed.key)),
197
+ await generator.createDeviceAuthorization(deviceKey),
198
+ await generator.createFeedAdmission(spaceKey, dataFeed.key, AdmittedFeed.Designation.DATA)
199
+ ];
200
+
201
+ for (const credential of credentials) {
202
+ await identity.controlPipeline.writer.write({
203
+ '@type': 'dxos.echo.feed.CredentialsMessage',
204
+ credential
205
+ });
206
+ }
207
+ }
208
+
209
+ // Wait for identity to be ready.
210
+ await identity.ready();
211
+ }
212
+
213
+ //
214
+ // Second device
215
+ //
216
+ {
217
+ const keyring = new Keyring();
218
+ const deviceKey = await keyring.createKey();
219
+
220
+ const feedStore = new FeedStore<FeedMessage>({
221
+ factory: new FeedFactory<FeedMessage>({
222
+ root: createStorage({ type: StorageType.RAM }).createDirectory(),
223
+ signer: keyring,
224
+ hypercore: {
225
+ valueEncoding
226
+ }
227
+ })
228
+ });
229
+
230
+ const createFeed = async () => {
231
+ const feedKey = await keyring.createKey();
232
+ return feedStore.openFeed(feedKey, { writable: true });
233
+ };
234
+
235
+ const controlFeed = await createFeed();
236
+ const dataFeed = await createFeed();
237
+
238
+ const protocol = new SpaceProtocol({
239
+ topic: spaceKey,
240
+ identity: {
241
+ peerKey: deviceKey,
242
+ credentialProvider: MOCK_AUTH_PROVIDER, // createHaloAuthProvider(createCredentialSignerWithKey(keyring, device_key)),
243
+ credentialAuthenticator: MOCK_AUTH_VERIFIER // createHaloAuthVerifier(() => identity.authorizedDeviceKeys),
244
+ },
245
+ networkManager: new NetworkManager({
246
+ signalManager: new MemorySignalManager(signalContext),
247
+ transportFactory: MemoryTransportFactory
248
+ })
249
+ });
250
+
251
+ const space = new Space({
252
+ spaceKey,
253
+ protocol,
254
+ genesisFeed: await feedStore.openFeed(genesisFeedKey),
255
+ controlFeed,
256
+ dataFeed,
257
+ feedProvider: (feedKey) => feedStore.openFeed(feedKey),
258
+ databaseFactory: async ({ databaseBackend }) => new Database(modelFactory, databaseBackend, deviceKey)
259
+ });
260
+
261
+ const identity = (identity2 = new Identity({
262
+ signer: keyring,
263
+ identityKey: identity1.identityKey,
264
+ deviceKey,
265
+ space
266
+ }));
267
+
268
+ await identity.open();
269
+ afterTest(() => identity.close());
270
+ }
271
+
272
+ //
273
+ // Second device admission
274
+ //
275
+ {
276
+ const signer = identity1.getIdentityCredentialSigner();
277
+ void identity1.controlPipeline.writer.write({
278
+ '@type': 'dxos.echo.feed.CredentialsMessage',
279
+ credential: await signer.createCredential({
280
+ subject: identity2.deviceKey,
281
+ assertion: {
282
+ '@type': 'dxos.halo.credentials.AuthorizedDevice',
283
+ identityKey: identity1.identityKey,
284
+ deviceKey: identity2.deviceKey
285
+ }
286
+ })
287
+ });
288
+
289
+ await identity2.ready();
290
+ }
291
+
292
+ expect(Array.from(identity1.authorizedDeviceKeys.values())).toEqual([identity1.deviceKey, identity2.deviceKey]);
293
+ expect(Array.from(identity2.authorizedDeviceKeys.values())).toEqual([identity1.deviceKey, identity2.deviceKey]);
294
+ });
295
+ });