jazz-tools 0.9.23 → 0.10.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.
- package/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +19 -0
- package/dist/{chunk-OJIEP4WE.js → chunk-UBD75Z27.js} +566 -118
- package/dist/chunk-UBD75Z27.js.map +1 -0
- package/dist/index.native.js +17 -5
- package/dist/index.native.js.map +1 -1
- package/dist/index.web.js +17 -5
- package/dist/index.web.js.map +1 -1
- package/dist/testing.js +124 -33
- package/dist/testing.js.map +1 -1
- package/package.json +5 -3
- package/src/auth/AuthSecretStorage.ts +109 -0
- package/src/auth/DemoAuth.ts +188 -0
- package/src/auth/InMemoryKVStore.ts +25 -0
- package/src/auth/KvStoreContext.ts +39 -0
- package/src/auth/PassphraseAuth.ts +113 -0
- package/src/coValues/account.ts +8 -3
- package/src/coValues/coFeed.ts +1 -1
- package/src/coValues/coList.ts +1 -1
- package/src/coValues/coMap.ts +1 -1
- package/src/coValues/group.ts +9 -8
- package/src/coValues/interfaces.ts +14 -5
- package/src/exports.ts +17 -3
- package/src/implementation/ContextManager.ts +178 -0
- package/src/implementation/activeAccountContext.ts +6 -1
- package/src/implementation/createContext.ts +173 -149
- package/src/testing.ts +171 -33
- package/src/tests/AuthSecretStorage.test.ts +275 -0
- package/src/tests/ContextManager.test.ts +256 -0
- package/src/tests/DemoAuth.test.ts +269 -0
- package/src/tests/PassphraseAuth.test.ts +152 -0
- package/src/tests/coFeed.test.ts +44 -39
- package/src/tests/coList.test.ts +21 -20
- package/src/tests/coMap.test.ts +21 -20
- package/src/tests/coPlainText.test.ts +21 -20
- package/src/tests/coRichText.test.ts +21 -20
- package/src/tests/createContext.test.ts +339 -0
- package/src/tests/deepLoading.test.ts +41 -42
- package/src/tests/fixtures.ts +2050 -0
- package/src/tests/groupsAndAccounts.test.ts +2 -2
- package/src/tests/subscribe.test.ts +42 -9
- package/src/tests/testing.test.ts +56 -0
- package/src/tests/utils.ts +11 -11
- package/src/types.ts +54 -0
- package/dist/chunk-OJIEP4WE.js.map +0 -1
@@ -0,0 +1,256 @@
|
|
1
|
+
import { WasmCrypto } from "cojson";
|
2
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
3
|
+
import {
|
4
|
+
Account,
|
5
|
+
AccountClass,
|
6
|
+
AuthSecretStorage,
|
7
|
+
CoMap,
|
8
|
+
Group,
|
9
|
+
InMemoryKVStore,
|
10
|
+
JazzAuthContext,
|
11
|
+
KvStoreContext,
|
12
|
+
co,
|
13
|
+
} from "../exports";
|
14
|
+
import {
|
15
|
+
JazzContextManager,
|
16
|
+
JazzContextManagerAuthProps,
|
17
|
+
JazzContextManagerBaseProps,
|
18
|
+
} from "../implementation/ContextManager";
|
19
|
+
import {
|
20
|
+
createJazzContext,
|
21
|
+
randomSessionProvider,
|
22
|
+
} from "../implementation/createContext";
|
23
|
+
import {
|
24
|
+
createJazzTestAccount,
|
25
|
+
getPeerConnectedToTestSyncServer,
|
26
|
+
setupJazzTestSync,
|
27
|
+
} from "../testing";
|
28
|
+
|
29
|
+
const Crypto = await WasmCrypto.create();
|
30
|
+
|
31
|
+
class TestJazzContextManager<Acc extends Account> extends JazzContextManager<
|
32
|
+
Acc,
|
33
|
+
JazzContextManagerBaseProps<Acc> & {
|
34
|
+
defaultProfileName?: string;
|
35
|
+
AccountSchema?: AccountClass<Acc>;
|
36
|
+
}
|
37
|
+
> {
|
38
|
+
async createContext(
|
39
|
+
props: JazzContextManagerBaseProps<Acc> & {
|
40
|
+
defaultProfileName?: string;
|
41
|
+
AccountSchema?: AccountClass<Acc>;
|
42
|
+
},
|
43
|
+
authProps?: JazzContextManagerAuthProps,
|
44
|
+
) {
|
45
|
+
const context = await createJazzContext<Acc>({
|
46
|
+
credentials: authProps?.credentials,
|
47
|
+
defaultProfileName: props.defaultProfileName,
|
48
|
+
newAccountProps: authProps?.newAccountProps,
|
49
|
+
peersToLoadFrom: [getPeerConnectedToTestSyncServer()],
|
50
|
+
crypto: Crypto,
|
51
|
+
sessionProvider: randomSessionProvider,
|
52
|
+
authSecretStorage: this.getAuthSecretStorage(),
|
53
|
+
AccountSchema: props.AccountSchema,
|
54
|
+
});
|
55
|
+
|
56
|
+
this.updateContext(props, {
|
57
|
+
me: context.account,
|
58
|
+
node: context.node,
|
59
|
+
done: () => {
|
60
|
+
context.done();
|
61
|
+
},
|
62
|
+
logOut: () => {
|
63
|
+
context.logOut();
|
64
|
+
},
|
65
|
+
});
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
describe("ContextManager", () => {
|
70
|
+
let manager: TestJazzContextManager<Account>;
|
71
|
+
let authSecretStorage: AuthSecretStorage;
|
72
|
+
|
73
|
+
function getCurrentValue() {
|
74
|
+
return manager.getCurrentValue() as JazzAuthContext<Account>;
|
75
|
+
}
|
76
|
+
|
77
|
+
beforeEach(async () => {
|
78
|
+
KvStoreContext.getInstance().initialize(new InMemoryKVStore());
|
79
|
+
authSecretStorage = new AuthSecretStorage();
|
80
|
+
await authSecretStorage.clear();
|
81
|
+
await setupJazzTestSync();
|
82
|
+
|
83
|
+
manager = new TestJazzContextManager<Account>();
|
84
|
+
});
|
85
|
+
|
86
|
+
test("creates new context when initialized", async () => {
|
87
|
+
await manager.createContext({});
|
88
|
+
|
89
|
+
const context = getCurrentValue();
|
90
|
+
|
91
|
+
expect(context.me.profile?.name).toBe("Anonymous user");
|
92
|
+
expect(context.node).toBeDefined();
|
93
|
+
expect(manager.getCurrentValue()).toBeDefined();
|
94
|
+
});
|
95
|
+
|
96
|
+
test("creates new context when initialized with default profile name", async () => {
|
97
|
+
await manager.createContext({
|
98
|
+
defaultProfileName: "Test User",
|
99
|
+
});
|
100
|
+
|
101
|
+
const context = getCurrentValue();
|
102
|
+
|
103
|
+
expect(context.me.profile?.name).toBe("Test User");
|
104
|
+
expect(context.node).toBeDefined();
|
105
|
+
expect(manager.getCurrentValue()).toBeDefined();
|
106
|
+
});
|
107
|
+
|
108
|
+
test("handles authentication with credentials", async () => {
|
109
|
+
const account = await createJazzTestAccount();
|
110
|
+
|
111
|
+
// First create an initial context to get credentials
|
112
|
+
await manager.createContext({});
|
113
|
+
|
114
|
+
const credentials = {
|
115
|
+
accountID: account.id,
|
116
|
+
accountSecret: account._raw.core.node.account.agentSecret,
|
117
|
+
provider: "test",
|
118
|
+
};
|
119
|
+
|
120
|
+
// Authenticate with those credentials
|
121
|
+
await manager.authenticate(credentials);
|
122
|
+
|
123
|
+
expect(getCurrentValue().me.id).toBe(credentials.accountID);
|
124
|
+
});
|
125
|
+
|
126
|
+
test("calls onLogOut callback when logging out", async () => {
|
127
|
+
const onLogOut = vi.fn();
|
128
|
+
await manager.createContext({ onLogOut });
|
129
|
+
|
130
|
+
await manager.logOut();
|
131
|
+
|
132
|
+
expect(onLogOut).toHaveBeenCalled();
|
133
|
+
});
|
134
|
+
|
135
|
+
test("notifies listeners of context changes", async () => {
|
136
|
+
const listener = vi.fn();
|
137
|
+
manager.subscribe(listener);
|
138
|
+
|
139
|
+
await manager.createContext({});
|
140
|
+
|
141
|
+
expect(listener).toHaveBeenCalled();
|
142
|
+
});
|
143
|
+
|
144
|
+
test("cleans up context when done", async () => {
|
145
|
+
await manager.createContext({});
|
146
|
+
|
147
|
+
const context = manager.getCurrentValue();
|
148
|
+
expect(context).toBeDefined();
|
149
|
+
|
150
|
+
manager.done();
|
151
|
+
|
152
|
+
// Should still have context, just cleaned up
|
153
|
+
expect(manager.getCurrentValue()).toBe(context);
|
154
|
+
});
|
155
|
+
|
156
|
+
test("calls onAnonymousAccountDiscarded when authenticating from anonymous user", async () => {
|
157
|
+
const onAnonymousAccountDiscarded = vi.fn();
|
158
|
+
const account = await createJazzTestAccount();
|
159
|
+
|
160
|
+
// Create initial anonymous context
|
161
|
+
await manager.createContext({ onAnonymousAccountDiscarded });
|
162
|
+
const anonymousAccount = getCurrentValue().me;
|
163
|
+
|
164
|
+
// Authenticate with credentials
|
165
|
+
await manager.authenticate({
|
166
|
+
accountID: account.id,
|
167
|
+
accountSecret: account._raw.core.node.account.agentSecret,
|
168
|
+
provider: "test",
|
169
|
+
});
|
170
|
+
|
171
|
+
// Verify callback was called with the anonymous account
|
172
|
+
expect(onAnonymousAccountDiscarded).toHaveBeenCalledWith(anonymousAccount);
|
173
|
+
});
|
174
|
+
|
175
|
+
test("does not call onAnonymousAccountDiscarded when authenticating from authenticated user", async () => {
|
176
|
+
const onAnonymousAccountDiscarded = vi.fn();
|
177
|
+
const account = await createJazzTestAccount();
|
178
|
+
|
179
|
+
await manager.getAuthSecretStorage().set({
|
180
|
+
accountID: account.id,
|
181
|
+
accountSecret: account._raw.core.node.account.agentSecret,
|
182
|
+
provider: "test",
|
183
|
+
});
|
184
|
+
|
185
|
+
// Create initial authenticated context
|
186
|
+
await manager.createContext({ onAnonymousAccountDiscarded });
|
187
|
+
|
188
|
+
// Authenticate with same credentials
|
189
|
+
await manager.authenticate({
|
190
|
+
accountID: account.id,
|
191
|
+
accountSecret: account._raw.core.node.account.agentSecret,
|
192
|
+
provider: "test",
|
193
|
+
});
|
194
|
+
|
195
|
+
// Verify callback was not called
|
196
|
+
expect(onAnonymousAccountDiscarded).not.toHaveBeenCalled();
|
197
|
+
});
|
198
|
+
|
199
|
+
test("onAnonymousAccountDiscarded should work on transfering data between accounts", async () => {
|
200
|
+
class AccountRoot extends CoMap {
|
201
|
+
value = co.string;
|
202
|
+
transferredRoot = co.optional.ref(AccountRoot);
|
203
|
+
}
|
204
|
+
|
205
|
+
class CustomAccount extends Account {
|
206
|
+
root = co.ref(AccountRoot);
|
207
|
+
|
208
|
+
migrate() {
|
209
|
+
if (this.root === undefined) {
|
210
|
+
this.root = AccountRoot.create({
|
211
|
+
value: "Hello",
|
212
|
+
});
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
const onAnonymousAccountDiscarded = async (
|
218
|
+
anonymousAccount: CustomAccount,
|
219
|
+
) => {
|
220
|
+
const anonymousAccountWithRoot = await anonymousAccount.ensureLoaded({
|
221
|
+
root: {},
|
222
|
+
});
|
223
|
+
|
224
|
+
const meWithRoot = await CustomAccount.getMe().ensureLoaded({ root: {} });
|
225
|
+
|
226
|
+
const rootToTransfer = anonymousAccountWithRoot.root;
|
227
|
+
|
228
|
+
await rootToTransfer._owner.castAs(Group).addMember(meWithRoot, "admin");
|
229
|
+
|
230
|
+
meWithRoot.root.transferredRoot = rootToTransfer;
|
231
|
+
};
|
232
|
+
|
233
|
+
const customManager = new TestJazzContextManager<CustomAccount>();
|
234
|
+
|
235
|
+
// Create initial anonymous context
|
236
|
+
await customManager.createContext({
|
237
|
+
onAnonymousAccountDiscarded,
|
238
|
+
AccountSchema: CustomAccount,
|
239
|
+
});
|
240
|
+
|
241
|
+
const account = await createJazzTestAccount({
|
242
|
+
isCurrentActiveAccount: true,
|
243
|
+
AccountSchema: CustomAccount,
|
244
|
+
});
|
245
|
+
|
246
|
+
await customManager.authenticate({
|
247
|
+
accountID: account.id,
|
248
|
+
accountSecret: account._raw.core.node.account.agentSecret,
|
249
|
+
provider: "test",
|
250
|
+
});
|
251
|
+
|
252
|
+
const me = await CustomAccount.getMe().ensureLoaded({ root: {} });
|
253
|
+
|
254
|
+
expect(me.root.transferredRoot?.value).toBe("Hello");
|
255
|
+
});
|
256
|
+
});
|
@@ -0,0 +1,269 @@
|
|
1
|
+
import { AgentSecret } from "cojson";
|
2
|
+
import { beforeEach, describe, expect, test, vi } from "vitest";
|
3
|
+
import { AuthSecretStorage } from "../auth/AuthSecretStorage";
|
4
|
+
import { DemoAuth } from "../auth/DemoAuth";
|
5
|
+
import { InMemoryKVStore } from "../auth/InMemoryKVStore";
|
6
|
+
import { KvStoreContext } from "../auth/KvStoreContext";
|
7
|
+
import { Account } from "../coValues/account";
|
8
|
+
import { ID } from "../internal";
|
9
|
+
import { createJazzTestAccount } from "../testing";
|
10
|
+
|
11
|
+
// Initialize KV store for tests
|
12
|
+
const kvStore = new InMemoryKVStore();
|
13
|
+
KvStoreContext.getInstance().initialize(kvStore);
|
14
|
+
|
15
|
+
describe("DemoAuth", () => {
|
16
|
+
let mockAuthenticate: any;
|
17
|
+
let authSecretStorage: AuthSecretStorage;
|
18
|
+
let demoAuth: DemoAuth;
|
19
|
+
|
20
|
+
beforeEach(async () => {
|
21
|
+
// Reset mock authenticate
|
22
|
+
mockAuthenticate = vi.fn();
|
23
|
+
|
24
|
+
// Clear KV store
|
25
|
+
kvStore.clearAll();
|
26
|
+
|
27
|
+
// Create new AuthSecretStorage instance
|
28
|
+
authSecretStorage = new AuthSecretStorage();
|
29
|
+
|
30
|
+
await createJazzTestAccount({
|
31
|
+
isCurrentActiveAccount: true,
|
32
|
+
});
|
33
|
+
|
34
|
+
// Create DemoAuth instance
|
35
|
+
demoAuth = new DemoAuth(mockAuthenticate, authSecretStorage);
|
36
|
+
});
|
37
|
+
|
38
|
+
describe("logIn", () => {
|
39
|
+
test("should successfully log in existing user", async () => {
|
40
|
+
const testUser = "testUser";
|
41
|
+
|
42
|
+
const credentials = {
|
43
|
+
accountID: "new-account-id" as ID<Account>,
|
44
|
+
accountSecret: "new-secret" as AgentSecret,
|
45
|
+
secretSeed: new Uint8Array([1, 2, 3]),
|
46
|
+
provider: "anonymous",
|
47
|
+
};
|
48
|
+
|
49
|
+
await authSecretStorage.set(credentials);
|
50
|
+
|
51
|
+
await demoAuth.signUp(testUser);
|
52
|
+
|
53
|
+
await demoAuth.logIn(testUser);
|
54
|
+
|
55
|
+
expect(mockAuthenticate).toHaveBeenCalledWith({
|
56
|
+
accountID: credentials.accountID,
|
57
|
+
accountSecret: credentials.accountSecret,
|
58
|
+
});
|
59
|
+
|
60
|
+
const storedData = await authSecretStorage.get();
|
61
|
+
expect(storedData).toEqual({
|
62
|
+
accountID: credentials.accountID,
|
63
|
+
accountSecret: credentials.accountSecret,
|
64
|
+
secretSeed: credentials.secretSeed,
|
65
|
+
provider: "demo",
|
66
|
+
});
|
67
|
+
});
|
68
|
+
|
69
|
+
test("should throw error for non-existent user", async () => {
|
70
|
+
await expect(demoAuth.logIn("nonexistentUser")).rejects.toThrow(
|
71
|
+
"User not found",
|
72
|
+
);
|
73
|
+
});
|
74
|
+
});
|
75
|
+
|
76
|
+
describe("signUp", () => {
|
77
|
+
test("should successfully sign up new user", async () => {
|
78
|
+
const testUser = "newUser";
|
79
|
+
const credentials = {
|
80
|
+
accountID: "new-account-id" as ID<Account>,
|
81
|
+
accountSecret: "new-secret" as AgentSecret,
|
82
|
+
secretSeed: new Uint8Array([1, 2, 3]),
|
83
|
+
};
|
84
|
+
|
85
|
+
await authSecretStorage.set({
|
86
|
+
...credentials,
|
87
|
+
provider: "anonymous",
|
88
|
+
});
|
89
|
+
|
90
|
+
await demoAuth.signUp(testUser);
|
91
|
+
|
92
|
+
const storedData = await authSecretStorage.get();
|
93
|
+
expect(storedData).toEqual({
|
94
|
+
...credentials,
|
95
|
+
provider: "demo",
|
96
|
+
});
|
97
|
+
});
|
98
|
+
|
99
|
+
test("should throw error for existing username", async () => {
|
100
|
+
const testUser = "existingUser";
|
101
|
+
await kvStore.set("demo-auth-storage-version", "3");
|
102
|
+
await kvStore.set(
|
103
|
+
"demo-auth-users",
|
104
|
+
JSON.stringify({
|
105
|
+
[testUser]: {
|
106
|
+
accountID: "existing-account-id" as ID<Account>,
|
107
|
+
accountSecret: "existing-secret" as AgentSecret,
|
108
|
+
secretSeed: [1, 2, 3],
|
109
|
+
},
|
110
|
+
}),
|
111
|
+
);
|
112
|
+
|
113
|
+
await expect(demoAuth.signUp(testUser)).rejects.toThrow(
|
114
|
+
"User already registered",
|
115
|
+
);
|
116
|
+
});
|
117
|
+
|
118
|
+
test("should throw error when no credentials found", async () => {
|
119
|
+
await expect(demoAuth.signUp("newUser")).rejects.toThrow(
|
120
|
+
"No credentials found",
|
121
|
+
);
|
122
|
+
});
|
123
|
+
});
|
124
|
+
|
125
|
+
describe("DemoAuth Storage Migration", () => {
|
126
|
+
test("should migrate from version 1 to version 3", async () => {
|
127
|
+
// Set up version 1 data
|
128
|
+
const testUser = "test@example.com";
|
129
|
+
const storageData = {
|
130
|
+
accountID: "test-account-id" as ID<Account>,
|
131
|
+
accountSecret: "test-secret" as AgentSecret,
|
132
|
+
secretSeed: [1, 2, 3],
|
133
|
+
};
|
134
|
+
|
135
|
+
// Store data in old format
|
136
|
+
await kvStore.set("demo-auth-existing-users", testUser);
|
137
|
+
await kvStore.set(
|
138
|
+
`demo-auth-existing-users-${testUser}`,
|
139
|
+
JSON.stringify(storageData),
|
140
|
+
);
|
141
|
+
|
142
|
+
// Trigger migration by getting existing users
|
143
|
+
await demoAuth.getExistingUsers();
|
144
|
+
|
145
|
+
// Verify migration
|
146
|
+
const version = await kvStore.get("demo-auth-storage-version");
|
147
|
+
expect(version).toBe("3");
|
148
|
+
|
149
|
+
// Old key should be deleted
|
150
|
+
const oldData = await kvStore.get(`demo-auth-existing-users-${testUser}`);
|
151
|
+
expect(oldData).toBeNull();
|
152
|
+
|
153
|
+
// New encoded key should exist
|
154
|
+
const newData = await kvStore.get("demo-auth-users");
|
155
|
+
expect(JSON.parse(newData!)).toEqual({
|
156
|
+
[testUser]: storageData,
|
157
|
+
});
|
158
|
+
});
|
159
|
+
|
160
|
+
test("should migrate from version 2 to version 3 (new storage format)", async () => {
|
161
|
+
// Set up version 2 data
|
162
|
+
const testUser = "test@example.com";
|
163
|
+
const encodedUsername = btoa(testUser)
|
164
|
+
.replace(/=/g, "-")
|
165
|
+
.replace(/\+/g, "_")
|
166
|
+
.replace(/\//g, ".");
|
167
|
+
|
168
|
+
const storageData = {
|
169
|
+
accountID: "test-account-id" as ID<Account>,
|
170
|
+
accountSecret: "test-secret" as AgentSecret,
|
171
|
+
secretSeed: [1, 2, 3],
|
172
|
+
};
|
173
|
+
|
174
|
+
// Store data in version 2 format
|
175
|
+
await kvStore.set("demo-auth-storage-version", "2");
|
176
|
+
await kvStore.set("demo-auth-existing-users", testUser);
|
177
|
+
await kvStore.set(
|
178
|
+
`demo-auth-existing-users-${encodedUsername}`,
|
179
|
+
JSON.stringify(storageData),
|
180
|
+
);
|
181
|
+
|
182
|
+
// Trigger migration by getting existing users
|
183
|
+
await demoAuth.getExistingUsers();
|
184
|
+
|
185
|
+
// Verify migration
|
186
|
+
const version = await kvStore.get("demo-auth-storage-version");
|
187
|
+
expect(version).toBe("3");
|
188
|
+
|
189
|
+
// Old keys should be deleted
|
190
|
+
const oldListData = await kvStore.get("demo-auth-existing-users");
|
191
|
+
expect(oldListData).toBeNull();
|
192
|
+
|
193
|
+
const oldUserData = await kvStore.get(
|
194
|
+
`demo-auth-existing-users-${encodedUsername}`,
|
195
|
+
);
|
196
|
+
expect(oldUserData).toBeNull();
|
197
|
+
|
198
|
+
// New storage format should be used
|
199
|
+
const newData = await kvStore.get("demo-auth-users");
|
200
|
+
expect(JSON.parse(newData!)).toEqual({
|
201
|
+
[testUser]: storageData,
|
202
|
+
});
|
203
|
+
});
|
204
|
+
|
205
|
+
test("should handle new users management after migration", async () => {
|
206
|
+
// Add a test user
|
207
|
+
const testUser = "newuser";
|
208
|
+
await authSecretStorage.set({
|
209
|
+
accountID: "test-id" as ID<Account>,
|
210
|
+
accountSecret: "test-secret" as AgentSecret,
|
211
|
+
secretSeed: new Uint8Array([1, 2, 3]),
|
212
|
+
provider: "anonymous",
|
213
|
+
});
|
214
|
+
|
215
|
+
// Sign up new user
|
216
|
+
await demoAuth.signUp(testUser);
|
217
|
+
|
218
|
+
// Verify user is stored in new format
|
219
|
+
const usersData = await kvStore.get("demo-auth-users");
|
220
|
+
const users = JSON.parse(usersData!);
|
221
|
+
|
222
|
+
expect(Object.keys(users)).toContain(testUser);
|
223
|
+
expect(users[testUser]).toEqual({
|
224
|
+
accountID: "test-id",
|
225
|
+
accountSecret: "test-secret",
|
226
|
+
secretSeed: [1, 2, 3],
|
227
|
+
});
|
228
|
+
|
229
|
+
// Verify we can get existing users
|
230
|
+
const existingUsers = await demoAuth.getExistingUsers();
|
231
|
+
expect(existingUsers).toEqual([testUser]);
|
232
|
+
|
233
|
+
// Verify we can log in with the stored user
|
234
|
+
await demoAuth.logIn(testUser);
|
235
|
+
const storedAuth = await authSecretStorage.get();
|
236
|
+
expect(storedAuth).toEqual({
|
237
|
+
accountID: "test-id",
|
238
|
+
accountSecret: "test-secret",
|
239
|
+
secretSeed: new Uint8Array([1, 2, 3]),
|
240
|
+
provider: "demo",
|
241
|
+
});
|
242
|
+
});
|
243
|
+
|
244
|
+
test("should handle multiple users in new storage format", async () => {
|
245
|
+
// Set up initial auth data
|
246
|
+
await authSecretStorage.set({
|
247
|
+
accountID: "test-id" as ID<Account>,
|
248
|
+
accountSecret: "test-secret" as AgentSecret,
|
249
|
+
secretSeed: new Uint8Array([1, 2, 3]),
|
250
|
+
provider: "anonymous",
|
251
|
+
});
|
252
|
+
|
253
|
+
// Add multiple users
|
254
|
+
const users = ["user1", "user2", "user3"];
|
255
|
+
for (const user of users) {
|
256
|
+
await demoAuth.signUp(user);
|
257
|
+
}
|
258
|
+
|
259
|
+
// Verify all users are stored
|
260
|
+
const existingUsers = await demoAuth.getExistingUsers();
|
261
|
+
expect(existingUsers.sort()).toEqual(users.sort());
|
262
|
+
|
263
|
+
// Verify we can't add duplicate users
|
264
|
+
await expect(demoAuth.signUp("user1")).rejects.toThrow(
|
265
|
+
"User already registered",
|
266
|
+
);
|
267
|
+
});
|
268
|
+
});
|
269
|
+
});
|
@@ -0,0 +1,152 @@
|
|
1
|
+
// @vitest-environment happy-dom
|
2
|
+
|
3
|
+
import { mnemonicToEntropy } from "@scure/bip39";
|
4
|
+
import { AgentSecret } from "cojson";
|
5
|
+
import {
|
6
|
+
Account,
|
7
|
+
AuthSecretStorage,
|
8
|
+
ID,
|
9
|
+
InMemoryKVStore,
|
10
|
+
KvStoreContext,
|
11
|
+
} from "jazz-tools";
|
12
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
13
|
+
import { PassphraseAuth } from "../auth/PassphraseAuth";
|
14
|
+
import { createJazzTestAccount } from "../testing";
|
15
|
+
import { TestJSCrypto } from "../testing";
|
16
|
+
import { testWordlist } from "./fixtures";
|
17
|
+
|
18
|
+
// Initialize KV store for tests
|
19
|
+
KvStoreContext.getInstance().initialize(new InMemoryKVStore());
|
20
|
+
|
21
|
+
describe("PassphraseAuth", () => {
|
22
|
+
let crypto: TestJSCrypto;
|
23
|
+
let mockAuthenticate: any;
|
24
|
+
let authSecretStorage: AuthSecretStorage;
|
25
|
+
let passphraseAuth: PassphraseAuth;
|
26
|
+
|
27
|
+
beforeEach(async () => {
|
28
|
+
// Reset storage
|
29
|
+
KvStoreContext.getInstance().getStorage().clearAll();
|
30
|
+
|
31
|
+
// Set up crypto and mocks
|
32
|
+
crypto = await TestJSCrypto.create();
|
33
|
+
mockAuthenticate = vi.fn();
|
34
|
+
authSecretStorage = new AuthSecretStorage();
|
35
|
+
|
36
|
+
await createJazzTestAccount({
|
37
|
+
isCurrentActiveAccount: true,
|
38
|
+
});
|
39
|
+
|
40
|
+
// Create PassphraseAuth instance
|
41
|
+
passphraseAuth = new PassphraseAuth(
|
42
|
+
crypto,
|
43
|
+
mockAuthenticate,
|
44
|
+
authSecretStorage,
|
45
|
+
testWordlist,
|
46
|
+
);
|
47
|
+
});
|
48
|
+
|
49
|
+
describe("logIn", () => {
|
50
|
+
it("should successfully log in with valid passphrase", async () => {
|
51
|
+
const storageData = {
|
52
|
+
accountID: "test-account-id" as ID<Account>,
|
53
|
+
accountSecret: "test-secret" as AgentSecret,
|
54
|
+
secretSeed: new Uint8Array([
|
55
|
+
173, 58, 235, 40, 67, 188, 236, 11, 107, 237, 97, 23, 182, 49, 188,
|
56
|
+
63, 237, 52, 27, 84, 142, 66, 244, 149, 243, 114, 203, 164, 115, 239,
|
57
|
+
175, 194,
|
58
|
+
]),
|
59
|
+
provider: "anonymous",
|
60
|
+
};
|
61
|
+
|
62
|
+
await authSecretStorage.set(storageData);
|
63
|
+
|
64
|
+
// Generate a valid passphrase
|
65
|
+
const passphrase = await passphraseAuth.getCurrentAccountPassphrase();
|
66
|
+
|
67
|
+
await passphraseAuth.logIn(passphrase);
|
68
|
+
|
69
|
+
expect(mockAuthenticate).toHaveBeenCalledWith({
|
70
|
+
accountID: expect.any(String),
|
71
|
+
accountSecret: expect.any(String),
|
72
|
+
});
|
73
|
+
|
74
|
+
const storedData = await authSecretStorage.get();
|
75
|
+
expect(storedData).toEqual({
|
76
|
+
accountID: expect.any(String),
|
77
|
+
accountSecret: expect.any(String),
|
78
|
+
secretSeed: storageData.secretSeed,
|
79
|
+
provider: "passphrase",
|
80
|
+
});
|
81
|
+
});
|
82
|
+
|
83
|
+
it("should throw error with invalid passphrase", async () => {
|
84
|
+
await expect(passphraseAuth.logIn("invalid words here")).rejects.toThrow(
|
85
|
+
"Invalid passphrase",
|
86
|
+
);
|
87
|
+
});
|
88
|
+
});
|
89
|
+
|
90
|
+
describe("signUp", () => {
|
91
|
+
it("should successfully sign up new user", async () => {
|
92
|
+
const storageData = {
|
93
|
+
accountID: "test-account-id" as ID<Account>,
|
94
|
+
accountSecret: "test-secret" as AgentSecret,
|
95
|
+
secretSeed: new Uint8Array([
|
96
|
+
173, 58, 235, 40, 67, 188, 236, 11, 107, 237, 97, 23, 182, 49, 188,
|
97
|
+
63, 237, 52, 27, 84, 142, 66, 244, 149, 243, 114, 203, 164, 115, 239,
|
98
|
+
175, 194,
|
99
|
+
]),
|
100
|
+
provider: "anonymous",
|
101
|
+
};
|
102
|
+
|
103
|
+
await authSecretStorage.set(storageData);
|
104
|
+
|
105
|
+
const passphrase = await passphraseAuth.signUp();
|
106
|
+
|
107
|
+
const storedData = await authSecretStorage.get();
|
108
|
+
expect(storedData).toEqual({
|
109
|
+
accountID: storageData.accountID,
|
110
|
+
accountSecret: storageData.accountSecret,
|
111
|
+
secretSeed: storageData.secretSeed,
|
112
|
+
provider: "passphrase",
|
113
|
+
});
|
114
|
+
expect(passphrase).toMatchInlineSnapshot(
|
115
|
+
`"pudding struggle skate manual solution aisle quick promote bless ranch humor lemon spy asset fall sign virus question syrup nuclear elbow water sample garden"`,
|
116
|
+
);
|
117
|
+
});
|
118
|
+
|
119
|
+
it("should throw error when no credentials found", async () => {
|
120
|
+
await expect(passphraseAuth.signUp()).rejects.toThrow(
|
121
|
+
"No credentials found",
|
122
|
+
);
|
123
|
+
});
|
124
|
+
});
|
125
|
+
|
126
|
+
describe("getCurrentAccountPassphrase", () => {
|
127
|
+
it("should return current user passphrase when credentials exist", async () => {
|
128
|
+
const storageData = {
|
129
|
+
accountID: "test-account-id" as ID<Account>,
|
130
|
+
accountSecret: "test-secret" as AgentSecret,
|
131
|
+
secretSeed: crypto.newRandomSecretSeed(),
|
132
|
+
provider: "anonymous",
|
133
|
+
};
|
134
|
+
|
135
|
+
await authSecretStorage.set(storageData);
|
136
|
+
|
137
|
+
// First sign up to create valid credentials
|
138
|
+
const originalPassphrase = await passphraseAuth.signUp();
|
139
|
+
|
140
|
+
// Then get the current passphrase
|
141
|
+
const retrievedPassphrase =
|
142
|
+
await passphraseAuth.getCurrentAccountPassphrase();
|
143
|
+
expect(retrievedPassphrase).toBe(originalPassphrase);
|
144
|
+
});
|
145
|
+
|
146
|
+
it("should throw error when no credentials found", async () => {
|
147
|
+
await expect(
|
148
|
+
passphraseAuth.getCurrentAccountPassphrase(),
|
149
|
+
).rejects.toThrow("No credentials found");
|
150
|
+
});
|
151
|
+
});
|
152
|
+
});
|