jazz-tools 0.10.12 → 0.10.14
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-RL7HVQ5Q.js → chunk-5YDDEUNX.js} +92 -40
- package/dist/chunk-5YDDEUNX.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/testing.js +1 -1
- package/package.json +1 -1
- package/src/auth/AuthSecretStorage.ts +10 -9
- package/src/auth/PassphraseAuth.ts +33 -2
- package/src/implementation/ContextManager.ts +50 -10
- package/src/implementation/createContext.ts +3 -3
- package/src/tests/AuthSecretStorage.test.ts +8 -9
- package/src/tests/ContextManager.test.ts +49 -0
- package/src/tests/PassphraseAuth.test.ts +201 -7
- package/src/types.ts +3 -0
- package/tsconfig.json +1 -1
- package/dist/chunk-RL7HVQ5Q.js.map +0 -1
package/dist/index.js
CHANGED
package/dist/testing.js
CHANGED
package/package.json
CHANGED
@@ -16,7 +16,6 @@ export type AuthSetPayload = {
|
|
16
16
|
export class AuthSecretStorage {
|
17
17
|
private listeners: Set<(isAuthenticated: boolean) => void>;
|
18
18
|
public isAuthenticated: boolean;
|
19
|
-
notify = false;
|
20
19
|
|
21
20
|
constructor() {
|
22
21
|
this.listeners = new Set();
|
@@ -97,7 +96,7 @@ export class AuthSecretStorage {
|
|
97
96
|
};
|
98
97
|
}
|
99
98
|
|
100
|
-
async
|
99
|
+
async setWithoutNotify(payload: AuthSetPayload) {
|
101
100
|
const kvStore = KvStoreContext.getInstance().getStorage();
|
102
101
|
await kvStore.set(
|
103
102
|
STORAGE_KEY,
|
@@ -110,10 +109,11 @@ export class AuthSecretStorage {
|
|
110
109
|
provider: payload.provider,
|
111
110
|
}),
|
112
111
|
);
|
112
|
+
}
|
113
113
|
|
114
|
-
|
115
|
-
|
116
|
-
|
114
|
+
async set(payload: AuthSetPayload) {
|
115
|
+
this.setWithoutNotify(payload);
|
116
|
+
this.emitUpdate(payload);
|
117
117
|
}
|
118
118
|
|
119
119
|
getIsAuthenticated(data: AuthCredentials | null): boolean {
|
@@ -139,12 +139,13 @@ export class AuthSecretStorage {
|
|
139
139
|
}
|
140
140
|
}
|
141
141
|
|
142
|
-
async
|
142
|
+
async clearWithoutNotify() {
|
143
143
|
const kvStore = KvStoreContext.getInstance().getStorage();
|
144
144
|
await kvStore.delete(STORAGE_KEY);
|
145
|
+
}
|
145
146
|
|
146
|
-
|
147
|
-
|
148
|
-
|
147
|
+
async clear() {
|
148
|
+
await this.clearWithoutNotify();
|
149
|
+
this.emitUpdate(null);
|
149
150
|
}
|
150
151
|
}
|
@@ -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;
|
@@ -43,10 +43,6 @@ export class JazzContextManager<
|
|
43
43
|
protected context: PlatformSpecificContext<Acc> | undefined;
|
44
44
|
protected props: P | undefined;
|
45
45
|
protected authSecretStorage = new AuthSecretStorage();
|
46
|
-
protected authSecretStorageWithNotify = Object.assign(
|
47
|
-
Object.create(this.authSecretStorage),
|
48
|
-
{ notify: true },
|
49
|
-
);
|
50
46
|
protected authenticating = false;
|
51
47
|
|
52
48
|
constructor() {
|
@@ -80,6 +76,7 @@ export class JazzContextManager<
|
|
80
76
|
...context,
|
81
77
|
node: context.node,
|
82
78
|
authenticate: this.authenticate,
|
79
|
+
register: this.register,
|
83
80
|
logOut: this.logOut,
|
84
81
|
};
|
85
82
|
|
@@ -102,9 +99,7 @@ export class JazzContextManager<
|
|
102
99
|
}
|
103
100
|
|
104
101
|
getAuthSecretStorage() {
|
105
|
-
|
106
|
-
// We skip internal notify to ensure that the isAuthenticated changes are notified along with the context updates
|
107
|
-
return this.authSecretStorageWithNotify;
|
102
|
+
return this.authSecretStorage;
|
108
103
|
}
|
109
104
|
|
110
105
|
logOut = async () => {
|
@@ -143,14 +138,59 @@ export class JazzContextManager<
|
|
143
138
|
this.authenticating = false;
|
144
139
|
});
|
145
140
|
|
141
|
+
if (wasAnonymous) {
|
142
|
+
await this.handleAnonymousAccountMigration(prevContext);
|
143
|
+
}
|
144
|
+
};
|
145
|
+
|
146
|
+
register = async (
|
147
|
+
accountSecret: AgentSecret,
|
148
|
+
creationProps: { name: string },
|
149
|
+
) => {
|
150
|
+
if (!this.props) {
|
151
|
+
throw new Error("Props required");
|
152
|
+
}
|
153
|
+
|
154
|
+
const prevContext = this.context;
|
155
|
+
const prevCredentials = await this.authSecretStorage.get();
|
156
|
+
const wasAnonymous =
|
157
|
+
this.authSecretStorage.getIsAuthenticated(prevCredentials) === false;
|
158
|
+
|
159
|
+
this.authenticating = true;
|
160
|
+
await this.createContext(this.props, {
|
161
|
+
newAccountProps: {
|
162
|
+
secret: accountSecret,
|
163
|
+
creationProps,
|
164
|
+
},
|
165
|
+
}).finally(() => {
|
166
|
+
this.authenticating = false;
|
167
|
+
});
|
168
|
+
|
169
|
+
if (wasAnonymous) {
|
170
|
+
await this.handleAnonymousAccountMigration(prevContext);
|
171
|
+
}
|
172
|
+
|
173
|
+
if (this.context && "me" in this.context) {
|
174
|
+
return this.context.me.id;
|
175
|
+
}
|
176
|
+
|
177
|
+
throw new Error("The registration hasn't created a new account");
|
178
|
+
};
|
179
|
+
|
180
|
+
private async handleAnonymousAccountMigration(
|
181
|
+
prevContext: PlatformSpecificContext<Acc> | undefined,
|
182
|
+
) {
|
183
|
+
if (!this.props) {
|
184
|
+
throw new Error("Props required");
|
185
|
+
}
|
186
|
+
|
146
187
|
const currentContext = this.context;
|
147
188
|
|
148
189
|
if (
|
149
190
|
prevContext &&
|
150
191
|
currentContext &&
|
151
192
|
"me" in prevContext &&
|
152
|
-
"me" in currentContext
|
153
|
-
wasAnonymous
|
193
|
+
"me" in currentContext
|
154
194
|
) {
|
155
195
|
// Using a direct connection to make coValue transfer almost synchronous
|
156
196
|
const [prevAccountAsPeer, currentAccountAsPeer] =
|
@@ -178,7 +218,7 @@ export class JazzContextManager<
|
|
178
218
|
}
|
179
219
|
|
180
220
|
prevContext?.done();
|
181
|
-
}
|
221
|
+
}
|
182
222
|
|
183
223
|
listeners = new Set<() => void>();
|
184
224
|
subscribe = (callback: () => void) => {
|
@@ -219,7 +219,7 @@ export async function createJazzContext<Acc extends Account>(options: {
|
|
219
219
|
AccountSchema: options.AccountSchema,
|
220
220
|
sessionProvider: options.sessionProvider,
|
221
221
|
onLogOut: () => {
|
222
|
-
authSecretStorage.
|
222
|
+
authSecretStorage.clearWithoutNotify();
|
223
223
|
},
|
224
224
|
});
|
225
225
|
} else {
|
@@ -240,12 +240,12 @@ export async function createJazzContext<Acc extends Account>(options: {
|
|
240
240
|
crypto,
|
241
241
|
AccountSchema: options.AccountSchema,
|
242
242
|
onLogOut: async () => {
|
243
|
-
await authSecretStorage.
|
243
|
+
await authSecretStorage.clearWithoutNotify();
|
244
244
|
},
|
245
245
|
});
|
246
246
|
|
247
247
|
if (!options.newAccountProps) {
|
248
|
-
await authSecretStorage.
|
248
|
+
await authSecretStorage.setWithoutNotify({
|
249
249
|
accountID: context.account.id,
|
250
250
|
secretSeed,
|
251
251
|
accountSecret: context.node.account.agentSecret,
|
@@ -257,11 +257,10 @@ describe("AuthSecretStorage", () => {
|
|
257
257
|
});
|
258
258
|
});
|
259
259
|
|
260
|
-
describe("notify
|
260
|
+
describe("notify", () => {
|
261
261
|
beforeEach(() => {
|
262
262
|
kvStore.clearAll();
|
263
263
|
authSecretStorage = new AuthSecretStorage();
|
264
|
-
authSecretStorage.notify = true;
|
265
264
|
});
|
266
265
|
|
267
266
|
describe("set", () => {
|
@@ -337,7 +336,7 @@ describe("AuthSecretStorage", () => {
|
|
337
336
|
});
|
338
337
|
});
|
339
338
|
|
340
|
-
describe("notify
|
339
|
+
describe("without notify", () => {
|
341
340
|
beforeEach(() => {
|
342
341
|
kvStore.clearAll();
|
343
342
|
authSecretStorage = new AuthSecretStorage();
|
@@ -348,7 +347,7 @@ describe("AuthSecretStorage", () => {
|
|
348
347
|
const handler = vi.fn();
|
349
348
|
authSecretStorage.onUpdate(handler);
|
350
349
|
|
351
|
-
await authSecretStorage.
|
350
|
+
await authSecretStorage.setWithoutNotify({
|
352
351
|
accountID: "test123" as ID<Account>,
|
353
352
|
accountSecret:
|
354
353
|
"secret123" as `sealerSecret_z${string}/signerSecret_z${string}`,
|
@@ -365,7 +364,7 @@ describe("AuthSecretStorage", () => {
|
|
365
364
|
});
|
366
365
|
|
367
366
|
it("should return false for anonymous credentials", async () => {
|
368
|
-
await authSecretStorage.
|
367
|
+
await authSecretStorage.setWithoutNotify({
|
369
368
|
accountID: "test123" as ID<Account>,
|
370
369
|
accountSecret:
|
371
370
|
"secret123" as `sealerSecret_z${string}/signerSecret_z${string}`,
|
@@ -376,7 +375,7 @@ describe("AuthSecretStorage", () => {
|
|
376
375
|
});
|
377
376
|
|
378
377
|
it("should return true for non-anonymous credentials", async () => {
|
379
|
-
await authSecretStorage.
|
378
|
+
await authSecretStorage.setWithoutNotify({
|
380
379
|
accountID: "test123" as ID<Account>,
|
381
380
|
accountSecret:
|
382
381
|
"secret123" as `sealerSecret_z${string}/signerSecret_z${string}`,
|
@@ -395,7 +394,7 @@ describe("AuthSecretStorage", () => {
|
|
395
394
|
});
|
396
395
|
|
397
396
|
it("should return true when the provider is missing", async () => {
|
398
|
-
await authSecretStorage.
|
397
|
+
await authSecretStorage.setWithoutNotify({
|
399
398
|
accountID: "test123" as ID<Account>,
|
400
399
|
accountSecret:
|
401
400
|
"secret123" as `sealerSecret_z${string}/signerSecret_z${string}`,
|
@@ -414,7 +413,7 @@ describe("AuthSecretStorage", () => {
|
|
414
413
|
|
415
414
|
describe("clear", () => {
|
416
415
|
it("should not emit update event when clearing", async () => {
|
417
|
-
await authSecretStorage.
|
416
|
+
await authSecretStorage.setWithoutNotify({
|
418
417
|
accountID: "test123" as ID<Account>,
|
419
418
|
accountSecret:
|
420
419
|
"secret123" as `sealerSecret_z${string}/signerSecret_z${string}`,
|
@@ -424,7 +423,7 @@ describe("AuthSecretStorage", () => {
|
|
424
423
|
const handler = vi.fn();
|
425
424
|
authSecretStorage.onUpdate(handler);
|
426
425
|
|
427
|
-
await authSecretStorage.
|
426
|
+
await authSecretStorage.clearWithoutNotify();
|
428
427
|
|
429
428
|
expect(handler).not.toHaveBeenCalled();
|
430
429
|
});
|
@@ -338,4 +338,53 @@ describe("ContextManager", () => {
|
|
338
338
|
|
339
339
|
expect(me.root.transferredRoot?.value).toBe("Hello");
|
340
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
|
+
});
|
341
390
|
});
|
@@ -1,7 +1,7 @@
|
|
1
1
|
// @vitest-environment happy-dom
|
2
2
|
|
3
|
-
import { mnemonicToEntropy } from "@scure/bip39";
|
4
3
|
import { AgentSecret } from "cojson";
|
4
|
+
import { PureJSCrypto } from "cojson/crypto/PureJSCrypto";
|
5
5
|
import {
|
6
6
|
Account,
|
7
7
|
AuthSecretStorage,
|
@@ -9,31 +9,41 @@ import {
|
|
9
9
|
InMemoryKVStore,
|
10
10
|
KvStoreContext,
|
11
11
|
} from "jazz-tools";
|
12
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
12
|
+
import { assert, beforeEach, describe, expect, it, vi } from "vitest";
|
13
13
|
import { PassphraseAuth } from "../auth/PassphraseAuth";
|
14
|
-
import {
|
15
|
-
|
14
|
+
import {
|
15
|
+
TestJazzContextManager,
|
16
|
+
createJazzTestAccount,
|
17
|
+
setupJazzTestSync,
|
18
|
+
} from "../testing";
|
16
19
|
import { testWordlist } from "./fixtures";
|
17
20
|
|
18
21
|
// Initialize KV store for tests
|
19
22
|
KvStoreContext.getInstance().initialize(new InMemoryKVStore());
|
20
23
|
|
24
|
+
beforeEach(async () => {
|
25
|
+
await setupJazzTestSync();
|
26
|
+
});
|
27
|
+
|
21
28
|
describe("PassphraseAuth", () => {
|
22
|
-
let crypto:
|
29
|
+
let crypto: PureJSCrypto;
|
23
30
|
let mockAuthenticate: any;
|
31
|
+
let mockRegister: any;
|
24
32
|
let authSecretStorage: AuthSecretStorage;
|
25
33
|
let passphraseAuth: PassphraseAuth;
|
34
|
+
let account: Account;
|
26
35
|
|
27
36
|
beforeEach(async () => {
|
28
37
|
// Reset storage
|
29
38
|
KvStoreContext.getInstance().getStorage().clearAll();
|
30
39
|
|
31
40
|
// Set up crypto and mocks
|
32
|
-
crypto = await
|
41
|
+
crypto = await PureJSCrypto.create();
|
33
42
|
mockAuthenticate = vi.fn();
|
43
|
+
mockRegister = vi.fn();
|
34
44
|
authSecretStorage = new AuthSecretStorage();
|
35
45
|
|
36
|
-
await createJazzTestAccount({
|
46
|
+
account = await createJazzTestAccount({
|
37
47
|
isCurrentActiveAccount: true,
|
38
48
|
});
|
39
49
|
|
@@ -41,6 +51,7 @@ describe("PassphraseAuth", () => {
|
|
41
51
|
passphraseAuth = new PassphraseAuth(
|
42
52
|
crypto,
|
43
53
|
mockAuthenticate,
|
54
|
+
mockRegister,
|
44
55
|
authSecretStorage,
|
45
56
|
testWordlist,
|
46
57
|
);
|
@@ -121,6 +132,39 @@ describe("PassphraseAuth", () => {
|
|
121
132
|
"No credentials found",
|
122
133
|
);
|
123
134
|
});
|
135
|
+
|
136
|
+
it("should set account name when provided during signup", async () => {
|
137
|
+
const storageData = {
|
138
|
+
accountID: "test-account-id" as ID<Account>,
|
139
|
+
accountSecret: "test-secret" as AgentSecret,
|
140
|
+
secretSeed: new Uint8Array([
|
141
|
+
173, 58, 235, 40, 67, 188, 236, 11, 107, 237, 97, 23, 182, 49, 188,
|
142
|
+
63, 237, 52, 27, 84, 142, 66, 244, 149, 243, 114, 203, 164, 115, 239,
|
143
|
+
175, 194,
|
144
|
+
]),
|
145
|
+
provider: "anonymous",
|
146
|
+
};
|
147
|
+
|
148
|
+
await authSecretStorage.set(storageData);
|
149
|
+
|
150
|
+
const testName = "Test User";
|
151
|
+
await passphraseAuth.signUp(testName);
|
152
|
+
|
153
|
+
// Verify the account name was set
|
154
|
+
const { profile } = await account.ensureLoaded({
|
155
|
+
profile: {},
|
156
|
+
});
|
157
|
+
expect(profile.name).toBe(testName);
|
158
|
+
|
159
|
+
// Verify storage was updated correctly
|
160
|
+
const storedData = await authSecretStorage.get();
|
161
|
+
expect(storedData).toEqual({
|
162
|
+
accountID: storageData.accountID,
|
163
|
+
accountSecret: storageData.accountSecret,
|
164
|
+
secretSeed: storageData.secretSeed,
|
165
|
+
provider: "passphrase",
|
166
|
+
});
|
167
|
+
});
|
124
168
|
});
|
125
169
|
|
126
170
|
describe("getCurrentAccountPassphrase", () => {
|
@@ -150,3 +194,153 @@ describe("PassphraseAuth", () => {
|
|
150
194
|
});
|
151
195
|
});
|
152
196
|
});
|
197
|
+
|
198
|
+
// Initialize KV store for tests
|
199
|
+
KvStoreContext.getInstance().initialize(new InMemoryKVStore());
|
200
|
+
|
201
|
+
describe("PassphraseAuth with TestJazzContextManager", () => {
|
202
|
+
let crypto: PureJSCrypto;
|
203
|
+
let contextManager: TestJazzContextManager<any>;
|
204
|
+
let authSecretStorage: AuthSecretStorage;
|
205
|
+
let passphraseAuth: PassphraseAuth;
|
206
|
+
|
207
|
+
beforeEach(async () => {
|
208
|
+
// Reset storage
|
209
|
+
KvStoreContext.getInstance().getStorage().clearAll();
|
210
|
+
|
211
|
+
const account = await createJazzTestAccount({
|
212
|
+
isCurrentActiveAccount: true,
|
213
|
+
});
|
214
|
+
|
215
|
+
// Set up crypto and context manager
|
216
|
+
crypto = await PureJSCrypto.create();
|
217
|
+
contextManager = TestJazzContextManager.fromAccountOrGuest(account);
|
218
|
+
authSecretStorage = contextManager.getAuthSecretStorage();
|
219
|
+
|
220
|
+
// Create initial context
|
221
|
+
await contextManager.createContext({});
|
222
|
+
|
223
|
+
// Create PassphraseAuth instance
|
224
|
+
passphraseAuth = new PassphraseAuth(
|
225
|
+
crypto,
|
226
|
+
contextManager.authenticate,
|
227
|
+
contextManager.register,
|
228
|
+
authSecretStorage,
|
229
|
+
testWordlist,
|
230
|
+
);
|
231
|
+
});
|
232
|
+
|
233
|
+
describe("logIn", () => {
|
234
|
+
it("should successfully log in with valid passphrase", async () => {
|
235
|
+
// First sign up to create initial credentials
|
236
|
+
const passphrase = await passphraseAuth.signUp();
|
237
|
+
|
238
|
+
// Log out
|
239
|
+
await contextManager.logOut();
|
240
|
+
|
241
|
+
// Log back in with passphrase
|
242
|
+
await passphraseAuth.logIn(passphrase);
|
243
|
+
|
244
|
+
// Verify we're logged in
|
245
|
+
const context = contextManager.getCurrentValue();
|
246
|
+
|
247
|
+
assert(context && "me" in context);
|
248
|
+
|
249
|
+
// Verify storage was updated
|
250
|
+
const storedData = await authSecretStorage.get();
|
251
|
+
expect(storedData?.provider).toBe("passphrase");
|
252
|
+
});
|
253
|
+
|
254
|
+
it("should throw error with invalid passphrase", async () => {
|
255
|
+
await expect(passphraseAuth.logIn("invalid words here")).rejects.toThrow(
|
256
|
+
"Invalid passphrase",
|
257
|
+
);
|
258
|
+
});
|
259
|
+
});
|
260
|
+
|
261
|
+
describe("signUp", () => {
|
262
|
+
it("should successfully sign up new user", async () => {
|
263
|
+
expect(authSecretStorage.isAuthenticated).toBe(false);
|
264
|
+
|
265
|
+
const passphrase = await passphraseAuth.signUp();
|
266
|
+
|
267
|
+
expect(authSecretStorage.isAuthenticated).toBe(true);
|
268
|
+
|
269
|
+
// Verify passphrase format
|
270
|
+
expect(passphrase.split(" ").length).toBeGreaterThan(0);
|
271
|
+
|
272
|
+
// Verify storage was updated
|
273
|
+
const storedData = await authSecretStorage.get();
|
274
|
+
expect(storedData?.provider).toBe("passphrase");
|
275
|
+
|
276
|
+
// Verify we can log in with the passphrase
|
277
|
+
await contextManager.logOut();
|
278
|
+
await passphraseAuth.logIn(passphrase);
|
279
|
+
const context = contextManager.getCurrentValue();
|
280
|
+
assert(context && "me" in context);
|
281
|
+
expect(context.me).toBeDefined();
|
282
|
+
});
|
283
|
+
|
284
|
+
it("should throw error when no credentials found", async () => {
|
285
|
+
await authSecretStorage.clear();
|
286
|
+
await expect(passphraseAuth.signUp()).rejects.toThrow(
|
287
|
+
"No credentials found",
|
288
|
+
);
|
289
|
+
});
|
290
|
+
});
|
291
|
+
|
292
|
+
describe("registerNewAccount", () => {
|
293
|
+
it("should successfully register new account with passphrase", async () => {
|
294
|
+
expect(authSecretStorage.isAuthenticated).toBe(false);
|
295
|
+
|
296
|
+
const passphrase = passphraseAuth.generateRandomPassphrase();
|
297
|
+
const accountId = await passphraseAuth.registerNewAccount(
|
298
|
+
passphrase,
|
299
|
+
"Test User",
|
300
|
+
);
|
301
|
+
|
302
|
+
// Verify account was created
|
303
|
+
expect(accountId).toBeDefined();
|
304
|
+
|
305
|
+
// Verify we can log in with the passphrase
|
306
|
+
await contextManager.logOut();
|
307
|
+
await passphraseAuth.logIn(passphrase);
|
308
|
+
|
309
|
+
const context = contextManager.getCurrentValue();
|
310
|
+
|
311
|
+
assert(context && "me" in context);
|
312
|
+
expect(context.me.id).toBe(accountId);
|
313
|
+
expect(context.me.profile?.name).toBe("Test User");
|
314
|
+
|
315
|
+
expect(authSecretStorage.isAuthenticated).toBe(true);
|
316
|
+
|
317
|
+
const credentials = await authSecretStorage.get();
|
318
|
+
assert(credentials);
|
319
|
+
expect(credentials.accountID).toBe(accountId);
|
320
|
+
expect(credentials.provider).toBe("passphrase");
|
321
|
+
});
|
322
|
+
|
323
|
+
it("should throw error with invalid passphrase during registration", async () => {
|
324
|
+
await expect(
|
325
|
+
passphraseAuth.registerNewAccount("invalid words", "Test User"),
|
326
|
+
).rejects.toThrow();
|
327
|
+
});
|
328
|
+
});
|
329
|
+
|
330
|
+
describe("getCurrentAccountPassphrase", () => {
|
331
|
+
it("should return current user passphrase when credentials exist", async () => {
|
332
|
+
const originalPassphrase = await passphraseAuth.signUp();
|
333
|
+
const retrievedPassphrase =
|
334
|
+
await passphraseAuth.getCurrentAccountPassphrase();
|
335
|
+
|
336
|
+
expect(retrievedPassphrase).toBe(originalPassphrase);
|
337
|
+
});
|
338
|
+
|
339
|
+
it("should throw error when no credentials found", async () => {
|
340
|
+
await authSecretStorage.clear();
|
341
|
+
await expect(
|
342
|
+
passphraseAuth.getCurrentAccountPassphrase(),
|
343
|
+
).rejects.toThrow("No credentials found");
|
344
|
+
});
|
345
|
+
});
|
346
|
+
});
|
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;
|
package/tsconfig.json
CHANGED