jazz-tools 0.10.8 → 0.10.13
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-6OJCOJJ6.js → chunk-GSIV52ZH.js} +78 -23
- package/dist/chunk-GSIV52ZH.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/testing.js +1 -1
- package/package.json +1 -1
- package/src/auth/PassphraseAuth.ts +33 -2
- package/src/coValues/deepLoading.ts +0 -1
- package/src/coValues/interfaces.ts +6 -6
- package/src/implementation/ContextManager.ts +49 -3
- package/src/tests/ContextManager.test.ts +49 -1
- package/src/tests/PassphraseAuth.test.ts +201 -6
- package/src/tests/deepLoading.test.ts +44 -0
- package/src/types.ts +3 -0
- package/dist/chunk-6OJCOJJ6.js.map +0 -1
package/dist/index.js
CHANGED
package/dist/testing.js
CHANGED
package/package.json
CHANGED
@@ -3,7 +3,10 @@ import { entropyToMnemonic } from "@scure/bip39";
|
|
3
3
|
import { CryptoProvider, cojsonInternals } from "cojson";
|
4
4
|
import { Account } from "../coValues/account.js";
|
5
5
|
import type { ID } from "../internal.js";
|
6
|
-
import type {
|
6
|
+
import type {
|
7
|
+
AuthenticateAccountFunction,
|
8
|
+
RegisterAccountFunction,
|
9
|
+
} from "../types.js";
|
7
10
|
import { AuthSecretStorage } from "./AuthSecretStorage.js";
|
8
11
|
|
9
12
|
/**
|
@@ -23,6 +26,7 @@ export class PassphraseAuth {
|
|
23
26
|
constructor(
|
24
27
|
private crypto: CryptoProvider,
|
25
28
|
private authenticate: AuthenticateAccountFunction,
|
29
|
+
private register: RegisterAccountFunction,
|
26
30
|
private authSecretStorage: AuthSecretStorage,
|
27
31
|
public wordlist: string[],
|
28
32
|
) {}
|
@@ -61,7 +65,7 @@ export class PassphraseAuth {
|
|
61
65
|
this.notify();
|
62
66
|
};
|
63
67
|
|
64
|
-
signUp = async () => {
|
68
|
+
signUp = async (name?: string) => {
|
65
69
|
const credentials = await this.authSecretStorage.get();
|
66
70
|
|
67
71
|
if (!credentials || !credentials.secretSeed) {
|
@@ -77,9 +81,32 @@ export class PassphraseAuth {
|
|
77
81
|
provider: "passphrase",
|
78
82
|
});
|
79
83
|
|
84
|
+
if (name?.trim()) {
|
85
|
+
const currentAccount = await Account.getMe().ensureLoaded({
|
86
|
+
profile: {},
|
87
|
+
});
|
88
|
+
|
89
|
+
currentAccount.profile.name = name;
|
90
|
+
}
|
91
|
+
|
80
92
|
return passphrase;
|
81
93
|
};
|
82
94
|
|
95
|
+
registerNewAccount = async (passphrase: string, name: string) => {
|
96
|
+
const secretSeed = bip39.mnemonicToEntropy(passphrase, this.wordlist);
|
97
|
+
const accountSecret = this.crypto.agentSecretFromSecretSeed(secretSeed);
|
98
|
+
const accountID = await this.register(accountSecret, { name });
|
99
|
+
|
100
|
+
await this.authSecretStorage.set({
|
101
|
+
accountID,
|
102
|
+
secretSeed,
|
103
|
+
accountSecret,
|
104
|
+
provider: "passphrase",
|
105
|
+
});
|
106
|
+
|
107
|
+
return accountID;
|
108
|
+
};
|
109
|
+
|
83
110
|
getCurrentAccountPassphrase = async () => {
|
84
111
|
const credentials = await this.authSecretStorage.get();
|
85
112
|
|
@@ -90,6 +117,10 @@ export class PassphraseAuth {
|
|
90
117
|
return entropyToMnemonic(credentials.secretSeed, this.wordlist);
|
91
118
|
};
|
92
119
|
|
120
|
+
generateRandomPassphrase = () => {
|
121
|
+
return entropyToMnemonic(this.crypto.newRandomSecretSeed(), this.wordlist);
|
122
|
+
};
|
123
|
+
|
93
124
|
loadCurrentAccountPassphrase = async () => {
|
94
125
|
const passphrase = await this.getCurrentAccountPassphrase();
|
95
126
|
this.passphrase = passphrase;
|
@@ -29,7 +29,6 @@ export function fulfillsDepth(depth: any, value: CoValue): boolean {
|
|
29
29
|
const map = value as unknown as {
|
30
30
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
31
31
|
[key: string]: any;
|
32
|
-
_refs: { [key: string]: Ref<CoValue> | undefined };
|
33
32
|
};
|
34
33
|
|
35
34
|
if (map._raw.get(key) === undefined) {
|
@@ -157,7 +157,7 @@ export class CoValueBase implements CoValue {
|
|
157
157
|
|
158
158
|
export function loadCoValueWithoutMe<V extends CoValue, Depth>(
|
159
159
|
cls: CoValueClass<V>,
|
160
|
-
id: ID<
|
160
|
+
id: ID<CoValue>,
|
161
161
|
asOrDepth: Account | AnonymousJazzAgent | (Depth & DepthsIn<V>),
|
162
162
|
depth?: Depth & DepthsIn<V>,
|
163
163
|
) {
|
@@ -175,7 +175,7 @@ export function loadCoValueWithoutMe<V extends CoValue, Depth>(
|
|
175
175
|
|
176
176
|
export function loadCoValue<V extends CoValue, Depth>(
|
177
177
|
cls: CoValueClass<V>,
|
178
|
-
id: ID<
|
178
|
+
id: ID<CoValue>,
|
179
179
|
as: Account | AnonymousJazzAgent,
|
180
180
|
depth: Depth & DepthsIn<V>,
|
181
181
|
): Promise<DeeplyLoaded<V, Depth> | undefined> {
|
@@ -216,7 +216,7 @@ export async function ensureCoValueLoaded<V extends CoValue, Depth>(
|
|
216
216
|
|
217
217
|
export function subscribeToCoValueWithoutMe<V extends CoValue, Depth>(
|
218
218
|
cls: CoValueClass<V>,
|
219
|
-
id: ID<
|
219
|
+
id: ID<CoValue>,
|
220
220
|
asOrDepth: Account | AnonymousJazzAgent | (Depth & DepthsIn<V>),
|
221
221
|
depthOrListener:
|
222
222
|
| (Depth & DepthsIn<V>)
|
@@ -251,7 +251,7 @@ export function subscribeToCoValueWithoutMe<V extends CoValue, Depth>(
|
|
251
251
|
|
252
252
|
export function subscribeToCoValue<V extends CoValue, Depth>(
|
253
253
|
cls: CoValueClass<V>,
|
254
|
-
id: ID<
|
254
|
+
id: ID<CoValue>,
|
255
255
|
as: Account | AnonymousJazzAgent,
|
256
256
|
depth: Depth & DepthsIn<V>,
|
257
257
|
listener: (value: DeeplyLoaded<V, Depth>, unsubscribe: () => void) => void,
|
@@ -263,7 +263,7 @@ export function subscribeToCoValue<V extends CoValue, Depth>(
|
|
263
263
|
let unsubscribed = false;
|
264
264
|
let unsubscribe: (() => void) | undefined;
|
265
265
|
|
266
|
-
function subscribe(value:
|
266
|
+
function subscribe(value: CoValue | undefined) {
|
267
267
|
if (!value) {
|
268
268
|
onUnavailable && onUnavailable();
|
269
269
|
return;
|
@@ -313,7 +313,7 @@ export function createCoValueObservable<V extends CoValue, Depth>(options?: {
|
|
313
313
|
|
314
314
|
function subscribe(
|
315
315
|
cls: CoValueClass<V>,
|
316
|
-
id: ID<
|
316
|
+
id: ID<CoValue>,
|
317
317
|
as: Account | AnonymousJazzAgent,
|
318
318
|
depth: Depth & DepthsIn<V>,
|
319
319
|
listener: () => void,
|
@@ -80,6 +80,7 @@ export class JazzContextManager<
|
|
80
80
|
...context,
|
81
81
|
node: context.node,
|
82
82
|
authenticate: this.authenticate,
|
83
|
+
register: this.register,
|
83
84
|
logOut: this.logOut,
|
84
85
|
};
|
85
86
|
|
@@ -143,14 +144,59 @@ export class JazzContextManager<
|
|
143
144
|
this.authenticating = false;
|
144
145
|
});
|
145
146
|
|
147
|
+
if (wasAnonymous) {
|
148
|
+
await this.handleAnonymousAccountMigration(prevContext);
|
149
|
+
}
|
150
|
+
};
|
151
|
+
|
152
|
+
register = async (
|
153
|
+
accountSecret: AgentSecret,
|
154
|
+
creationProps: { name: string },
|
155
|
+
) => {
|
156
|
+
if (!this.props) {
|
157
|
+
throw new Error("Props required");
|
158
|
+
}
|
159
|
+
|
160
|
+
const prevContext = this.context;
|
161
|
+
const prevCredentials = await this.authSecretStorage.get();
|
162
|
+
const wasAnonymous =
|
163
|
+
this.authSecretStorage.getIsAuthenticated(prevCredentials) === false;
|
164
|
+
|
165
|
+
this.authenticating = true;
|
166
|
+
await this.createContext(this.props, {
|
167
|
+
newAccountProps: {
|
168
|
+
secret: accountSecret,
|
169
|
+
creationProps,
|
170
|
+
},
|
171
|
+
}).finally(() => {
|
172
|
+
this.authenticating = false;
|
173
|
+
});
|
174
|
+
|
175
|
+
if (wasAnonymous) {
|
176
|
+
await this.handleAnonymousAccountMigration(prevContext);
|
177
|
+
}
|
178
|
+
|
179
|
+
if (this.context && "me" in this.context) {
|
180
|
+
return this.context.me.id;
|
181
|
+
}
|
182
|
+
|
183
|
+
throw new Error("The registration hasn't created a new account");
|
184
|
+
};
|
185
|
+
|
186
|
+
private async handleAnonymousAccountMigration(
|
187
|
+
prevContext: PlatformSpecificContext<Acc> | undefined,
|
188
|
+
) {
|
189
|
+
if (!this.props) {
|
190
|
+
throw new Error("Props required");
|
191
|
+
}
|
192
|
+
|
146
193
|
const currentContext = this.context;
|
147
194
|
|
148
195
|
if (
|
149
196
|
prevContext &&
|
150
197
|
currentContext &&
|
151
198
|
"me" in prevContext &&
|
152
|
-
"me" in currentContext
|
153
|
-
wasAnonymous
|
199
|
+
"me" in currentContext
|
154
200
|
) {
|
155
201
|
// Using a direct connection to make coValue transfer almost synchronous
|
156
202
|
const [prevAccountAsPeer, currentAccountAsPeer] =
|
@@ -178,7 +224,7 @@ export class JazzContextManager<
|
|
178
224
|
}
|
179
225
|
|
180
226
|
prevContext?.done();
|
181
|
-
}
|
227
|
+
}
|
182
228
|
|
183
229
|
listeners = new Set<() => void>();
|
184
230
|
subscribe = (callback: () => void) => {
|
@@ -26,7 +26,6 @@ import {
|
|
26
26
|
setupJazzTestSync,
|
27
27
|
} from "../testing";
|
28
28
|
|
29
|
-
// @ts-ignore Typescript in VSCode doesn't like top level await
|
30
29
|
const Crypto = await WasmCrypto.create();
|
31
30
|
|
32
31
|
class TestJazzContextManager<Acc extends Account> extends JazzContextManager<
|
@@ -339,4 +338,53 @@ describe("ContextManager", () => {
|
|
339
338
|
|
340
339
|
expect(me.root.transferredRoot?.value).toBe("Hello");
|
341
340
|
});
|
341
|
+
|
342
|
+
test("handles registration of new account", async () => {
|
343
|
+
const onAnonymousAccountDiscarded = vi.fn();
|
344
|
+
await manager.createContext({ onAnonymousAccountDiscarded });
|
345
|
+
|
346
|
+
const secret = Crypto.newRandomAgentSecret();
|
347
|
+
const accountId = await manager.register(secret, { name: "Test User" });
|
348
|
+
|
349
|
+
expect(accountId).toBeDefined();
|
350
|
+
const context = getCurrentValue();
|
351
|
+
expect(context.me.profile?.name).toBe("Test User");
|
352
|
+
expect(context.me.id).toBe(accountId);
|
353
|
+
});
|
354
|
+
|
355
|
+
test("calls onAnonymousAccountDiscarded when registering from anonymous user", async () => {
|
356
|
+
const onAnonymousAccountDiscarded = vi.fn();
|
357
|
+
await manager.createContext({ onAnonymousAccountDiscarded });
|
358
|
+
const anonymousAccount = getCurrentValue().me;
|
359
|
+
|
360
|
+
const secret = Crypto.newRandomAgentSecret();
|
361
|
+
await manager.register(secret, { name: "Test User" });
|
362
|
+
|
363
|
+
expect(onAnonymousAccountDiscarded).toHaveBeenCalledWith(anonymousAccount);
|
364
|
+
});
|
365
|
+
|
366
|
+
test("does not call onAnonymousAccountDiscarded when registering from authenticated user", async () => {
|
367
|
+
const onAnonymousAccountDiscarded = vi.fn();
|
368
|
+
const account = await createJazzTestAccount();
|
369
|
+
|
370
|
+
await manager.getAuthSecretStorage().set({
|
371
|
+
accountID: account.id,
|
372
|
+
accountSecret: account._raw.core.node.account.agentSecret,
|
373
|
+
provider: "test",
|
374
|
+
});
|
375
|
+
|
376
|
+
await manager.createContext({ onAnonymousAccountDiscarded });
|
377
|
+
|
378
|
+
const secret = Crypto.newRandomAgentSecret();
|
379
|
+
await manager.register(secret, { name: "New User" });
|
380
|
+
|
381
|
+
expect(onAnonymousAccountDiscarded).not.toHaveBeenCalled();
|
382
|
+
});
|
383
|
+
|
384
|
+
test("throws error when registering without props", async () => {
|
385
|
+
const secret = Crypto.newRandomAgentSecret();
|
386
|
+
await expect(
|
387
|
+
manager.register(secret, { name: "Test User" }),
|
388
|
+
).rejects.toThrow("Props required");
|
389
|
+
});
|
342
390
|
});
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
import { mnemonicToEntropy } from "@scure/bip39";
|
4
4
|
import { AgentSecret } from "cojson";
|
5
|
+
import { PureJSCrypto } from "cojson/src/crypto/PureJSCrypto";
|
5
6
|
import {
|
6
7
|
Account,
|
7
8
|
AuthSecretStorage,
|
@@ -9,31 +10,41 @@ import {
|
|
9
10
|
InMemoryKVStore,
|
10
11
|
KvStoreContext,
|
11
12
|
} from "jazz-tools";
|
12
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
13
|
+
import { assert, beforeEach, describe, expect, it, vi } from "vitest";
|
13
14
|
import { PassphraseAuth } from "../auth/PassphraseAuth";
|
14
|
-
import {
|
15
|
-
|
15
|
+
import {
|
16
|
+
TestJazzContextManager,
|
17
|
+
createJazzTestAccount,
|
18
|
+
setupJazzTestSync,
|
19
|
+
} from "../testing";
|
16
20
|
import { testWordlist } from "./fixtures";
|
17
21
|
|
18
22
|
// Initialize KV store for tests
|
19
23
|
KvStoreContext.getInstance().initialize(new InMemoryKVStore());
|
20
24
|
|
25
|
+
beforeEach(async () => {
|
26
|
+
await setupJazzTestSync();
|
27
|
+
});
|
28
|
+
|
21
29
|
describe("PassphraseAuth", () => {
|
22
|
-
let crypto:
|
30
|
+
let crypto: PureJSCrypto;
|
23
31
|
let mockAuthenticate: any;
|
32
|
+
let mockRegister: any;
|
24
33
|
let authSecretStorage: AuthSecretStorage;
|
25
34
|
let passphraseAuth: PassphraseAuth;
|
35
|
+
let account: Account;
|
26
36
|
|
27
37
|
beforeEach(async () => {
|
28
38
|
// Reset storage
|
29
39
|
KvStoreContext.getInstance().getStorage().clearAll();
|
30
40
|
|
31
41
|
// Set up crypto and mocks
|
32
|
-
crypto = await
|
42
|
+
crypto = await PureJSCrypto.create();
|
33
43
|
mockAuthenticate = vi.fn();
|
44
|
+
mockRegister = vi.fn();
|
34
45
|
authSecretStorage = new AuthSecretStorage();
|
35
46
|
|
36
|
-
await createJazzTestAccount({
|
47
|
+
account = await createJazzTestAccount({
|
37
48
|
isCurrentActiveAccount: true,
|
38
49
|
});
|
39
50
|
|
@@ -41,6 +52,7 @@ describe("PassphraseAuth", () => {
|
|
41
52
|
passphraseAuth = new PassphraseAuth(
|
42
53
|
crypto,
|
43
54
|
mockAuthenticate,
|
55
|
+
mockRegister,
|
44
56
|
authSecretStorage,
|
45
57
|
testWordlist,
|
46
58
|
);
|
@@ -121,6 +133,39 @@ describe("PassphraseAuth", () => {
|
|
121
133
|
"No credentials found",
|
122
134
|
);
|
123
135
|
});
|
136
|
+
|
137
|
+
it("should set account name when provided during signup", async () => {
|
138
|
+
const storageData = {
|
139
|
+
accountID: "test-account-id" as ID<Account>,
|
140
|
+
accountSecret: "test-secret" as AgentSecret,
|
141
|
+
secretSeed: new Uint8Array([
|
142
|
+
173, 58, 235, 40, 67, 188, 236, 11, 107, 237, 97, 23, 182, 49, 188,
|
143
|
+
63, 237, 52, 27, 84, 142, 66, 244, 149, 243, 114, 203, 164, 115, 239,
|
144
|
+
175, 194,
|
145
|
+
]),
|
146
|
+
provider: "anonymous",
|
147
|
+
};
|
148
|
+
|
149
|
+
await authSecretStorage.set(storageData);
|
150
|
+
|
151
|
+
const testName = "Test User";
|
152
|
+
await passphraseAuth.signUp(testName);
|
153
|
+
|
154
|
+
// Verify the account name was set
|
155
|
+
const { profile } = await account.ensureLoaded({
|
156
|
+
profile: {},
|
157
|
+
});
|
158
|
+
expect(profile.name).toBe(testName);
|
159
|
+
|
160
|
+
// Verify storage was updated correctly
|
161
|
+
const storedData = await authSecretStorage.get();
|
162
|
+
expect(storedData).toEqual({
|
163
|
+
accountID: storageData.accountID,
|
164
|
+
accountSecret: storageData.accountSecret,
|
165
|
+
secretSeed: storageData.secretSeed,
|
166
|
+
provider: "passphrase",
|
167
|
+
});
|
168
|
+
});
|
124
169
|
});
|
125
170
|
|
126
171
|
describe("getCurrentAccountPassphrase", () => {
|
@@ -150,3 +195,153 @@ describe("PassphraseAuth", () => {
|
|
150
195
|
});
|
151
196
|
});
|
152
197
|
});
|
198
|
+
|
199
|
+
// Initialize KV store for tests
|
200
|
+
KvStoreContext.getInstance().initialize(new InMemoryKVStore());
|
201
|
+
|
202
|
+
describe("PassphraseAuth with TestJazzContextManager", () => {
|
203
|
+
let crypto: PureJSCrypto;
|
204
|
+
let contextManager: TestJazzContextManager<any>;
|
205
|
+
let authSecretStorage: AuthSecretStorage;
|
206
|
+
let passphraseAuth: PassphraseAuth;
|
207
|
+
|
208
|
+
beforeEach(async () => {
|
209
|
+
// Reset storage
|
210
|
+
KvStoreContext.getInstance().getStorage().clearAll();
|
211
|
+
|
212
|
+
const account = await createJazzTestAccount({
|
213
|
+
isCurrentActiveAccount: true,
|
214
|
+
});
|
215
|
+
|
216
|
+
// Set up crypto and context manager
|
217
|
+
crypto = await PureJSCrypto.create();
|
218
|
+
contextManager = TestJazzContextManager.fromAccountOrGuest(account);
|
219
|
+
authSecretStorage = contextManager.getAuthSecretStorage();
|
220
|
+
|
221
|
+
// Create initial context
|
222
|
+
await contextManager.createContext({});
|
223
|
+
|
224
|
+
// Create PassphraseAuth instance
|
225
|
+
passphraseAuth = new PassphraseAuth(
|
226
|
+
crypto,
|
227
|
+
contextManager.authenticate,
|
228
|
+
contextManager.register,
|
229
|
+
authSecretStorage,
|
230
|
+
testWordlist,
|
231
|
+
);
|
232
|
+
});
|
233
|
+
|
234
|
+
describe("logIn", () => {
|
235
|
+
it("should successfully log in with valid passphrase", async () => {
|
236
|
+
// First sign up to create initial credentials
|
237
|
+
const passphrase = await passphraseAuth.signUp();
|
238
|
+
|
239
|
+
// Log out
|
240
|
+
await contextManager.logOut();
|
241
|
+
|
242
|
+
// Log back in with passphrase
|
243
|
+
await passphraseAuth.logIn(passphrase);
|
244
|
+
|
245
|
+
// Verify we're logged in
|
246
|
+
const context = contextManager.getCurrentValue();
|
247
|
+
|
248
|
+
assert(context && "me" in context);
|
249
|
+
|
250
|
+
// Verify storage was updated
|
251
|
+
const storedData = await authSecretStorage.get();
|
252
|
+
expect(storedData?.provider).toBe("passphrase");
|
253
|
+
});
|
254
|
+
|
255
|
+
it("should throw error with invalid passphrase", async () => {
|
256
|
+
await expect(passphraseAuth.logIn("invalid words here")).rejects.toThrow(
|
257
|
+
"Invalid passphrase",
|
258
|
+
);
|
259
|
+
});
|
260
|
+
});
|
261
|
+
|
262
|
+
describe("signUp", () => {
|
263
|
+
it("should successfully sign up new user", async () => {
|
264
|
+
expect(authSecretStorage.isAuthenticated).toBe(false);
|
265
|
+
|
266
|
+
const passphrase = await passphraseAuth.signUp();
|
267
|
+
|
268
|
+
expect(authSecretStorage.isAuthenticated).toBe(true);
|
269
|
+
|
270
|
+
// Verify passphrase format
|
271
|
+
expect(passphrase.split(" ").length).toBeGreaterThan(0);
|
272
|
+
|
273
|
+
// Verify storage was updated
|
274
|
+
const storedData = await authSecretStorage.get();
|
275
|
+
expect(storedData?.provider).toBe("passphrase");
|
276
|
+
|
277
|
+
// Verify we can log in with the passphrase
|
278
|
+
await contextManager.logOut();
|
279
|
+
await passphraseAuth.logIn(passphrase);
|
280
|
+
const context = contextManager.getCurrentValue();
|
281
|
+
assert(context && "me" in context);
|
282
|
+
expect(context.me).toBeDefined();
|
283
|
+
});
|
284
|
+
|
285
|
+
it("should throw error when no credentials found", async () => {
|
286
|
+
await authSecretStorage.clear();
|
287
|
+
await expect(passphraseAuth.signUp()).rejects.toThrow(
|
288
|
+
"No credentials found",
|
289
|
+
);
|
290
|
+
});
|
291
|
+
});
|
292
|
+
|
293
|
+
describe("registerNewAccount", () => {
|
294
|
+
it("should successfully register new account with passphrase", async () => {
|
295
|
+
expect(authSecretStorage.isAuthenticated).toBe(false);
|
296
|
+
|
297
|
+
const passphrase = passphraseAuth.generateRandomPassphrase();
|
298
|
+
const accountId = await passphraseAuth.registerNewAccount(
|
299
|
+
passphrase,
|
300
|
+
"Test User",
|
301
|
+
);
|
302
|
+
|
303
|
+
// Verify account was created
|
304
|
+
expect(accountId).toBeDefined();
|
305
|
+
|
306
|
+
// Verify we can log in with the passphrase
|
307
|
+
await contextManager.logOut();
|
308
|
+
await passphraseAuth.logIn(passphrase);
|
309
|
+
|
310
|
+
const context = contextManager.getCurrentValue();
|
311
|
+
|
312
|
+
assert(context && "me" in context);
|
313
|
+
expect(context.me.id).toBe(accountId);
|
314
|
+
expect(context.me.profile?.name).toBe("Test User");
|
315
|
+
|
316
|
+
expect(authSecretStorage.isAuthenticated).toBe(true);
|
317
|
+
|
318
|
+
const credentials = await authSecretStorage.get();
|
319
|
+
assert(credentials);
|
320
|
+
expect(credentials.accountID).toBe(accountId);
|
321
|
+
expect(credentials.provider).toBe("passphrase");
|
322
|
+
});
|
323
|
+
|
324
|
+
it("should throw error with invalid passphrase during registration", async () => {
|
325
|
+
await expect(
|
326
|
+
passphraseAuth.registerNewAccount("invalid words", "Test User"),
|
327
|
+
).rejects.toThrow();
|
328
|
+
});
|
329
|
+
});
|
330
|
+
|
331
|
+
describe("getCurrentAccountPassphrase", () => {
|
332
|
+
it("should return current user passphrase when credentials exist", async () => {
|
333
|
+
const originalPassphrase = await passphraseAuth.signUp();
|
334
|
+
const retrievedPassphrase =
|
335
|
+
await passphraseAuth.getCurrentAccountPassphrase();
|
336
|
+
|
337
|
+
expect(retrievedPassphrase).toBe(originalPassphrase);
|
338
|
+
});
|
339
|
+
|
340
|
+
it("should throw error when no credentials found", async () => {
|
341
|
+
await authSecretStorage.clear();
|
342
|
+
await expect(
|
343
|
+
passphraseAuth.getCurrentAccountPassphrase(),
|
344
|
+
).rejects.toThrow("No credentials found");
|
345
|
+
});
|
346
|
+
});
|
347
|
+
});
|
@@ -387,3 +387,47 @@ test("throw when calling ensureLoaded on a ref that is not defined in the schema
|
|
387
387
|
}),
|
388
388
|
).rejects.toThrow("Failed to deeply load CoValue " + root.id);
|
389
389
|
});
|
390
|
+
|
391
|
+
test("should not throw when calling ensureLoaded a record with a deleted ref", async () => {
|
392
|
+
class JazzProfile extends CoMap {
|
393
|
+
firstName = co.string;
|
394
|
+
}
|
395
|
+
|
396
|
+
class JazzySnapStore extends CoMap.Record(co.ref(JazzProfile)) {}
|
397
|
+
|
398
|
+
const me = await Account.create({
|
399
|
+
creationProps: { name: "Tester McTesterson" },
|
400
|
+
crypto: Crypto,
|
401
|
+
});
|
402
|
+
|
403
|
+
const root = JazzySnapStore.create(
|
404
|
+
{
|
405
|
+
profile: JazzProfile.create({ firstName: "John" }, me),
|
406
|
+
},
|
407
|
+
me,
|
408
|
+
);
|
409
|
+
|
410
|
+
let value: any;
|
411
|
+
let unsub = root.subscribe([{}], (v) => {
|
412
|
+
value = v;
|
413
|
+
});
|
414
|
+
|
415
|
+
await waitFor(() => expect(value.profile).toBeDefined());
|
416
|
+
|
417
|
+
delete root.profile;
|
418
|
+
|
419
|
+
await waitFor(() => expect(value.profile).toBeUndefined());
|
420
|
+
|
421
|
+
unsub();
|
422
|
+
|
423
|
+
value = undefined;
|
424
|
+
unsub = root.subscribe([{}], (v) => {
|
425
|
+
value = v;
|
426
|
+
});
|
427
|
+
|
428
|
+
await waitFor(() => expect(value).toBeDefined());
|
429
|
+
|
430
|
+
expect(value.profile).toBeUndefined();
|
431
|
+
|
432
|
+
unsub();
|
433
|
+
});
|
package/src/types.ts
CHANGED
@@ -12,6 +12,7 @@ export type AuthCredentials = {
|
|
12
12
|
export type AuthenticateAccountFunction = (
|
13
13
|
credentials: AuthCredentials,
|
14
14
|
) => Promise<void>;
|
15
|
+
|
15
16
|
export type RegisterAccountFunction = (
|
16
17
|
accountSecret: AgentSecret,
|
17
18
|
creationProps: { name: string },
|
@@ -22,6 +23,7 @@ export type JazzAuthContext<Acc extends Account> = {
|
|
22
23
|
me: Acc;
|
23
24
|
node: LocalNode;
|
24
25
|
authenticate: AuthenticateAccountFunction;
|
26
|
+
register: RegisterAccountFunction;
|
25
27
|
logOut: () => Promise<void>;
|
26
28
|
done: () => void;
|
27
29
|
isAuthenticated?: boolean;
|
@@ -31,6 +33,7 @@ export type JazzGuestContext = {
|
|
31
33
|
guest: AnonymousJazzAgent;
|
32
34
|
node: LocalNode;
|
33
35
|
authenticate: AuthenticateAccountFunction;
|
36
|
+
register: RegisterAccountFunction;
|
34
37
|
logOut: () => void;
|
35
38
|
done: () => void;
|
36
39
|
isAuthenticated?: boolean;
|