jazz-tools 0.18.4 → 0.18.6

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 (80) hide show
  1. package/.turbo/turbo-build.log +54 -54
  2. package/CHANGELOG.md +28 -0
  3. package/dist/better-auth/auth/client.d.ts.map +1 -1
  4. package/dist/better-auth/auth/client.js +7 -1
  5. package/dist/better-auth/auth/client.js.map +1 -1
  6. package/dist/better-auth/auth/react.d.ts +0 -2142
  7. package/dist/better-auth/auth/react.d.ts.map +1 -1
  8. package/dist/better-auth/auth/react.js +2 -14
  9. package/dist/better-auth/auth/react.js.map +1 -1
  10. package/dist/better-auth/auth/server.d.ts +21 -1
  11. package/dist/better-auth/auth/server.d.ts.map +1 -1
  12. package/dist/better-auth/auth/server.js +83 -27
  13. package/dist/better-auth/auth/server.js.map +1 -1
  14. package/dist/better-auth/auth/tests/react.test.d.ts +2 -0
  15. package/dist/better-auth/auth/tests/react.test.d.ts.map +1 -0
  16. package/dist/browser/createBrowserContext.d.ts.map +1 -1
  17. package/dist/browser/index.js +7 -0
  18. package/dist/browser/index.js.map +1 -1
  19. package/dist/{chunk-LHQQZH7I.js → chunk-45VKEOXG.js} +123 -81
  20. package/dist/chunk-45VKEOXG.js.map +1 -0
  21. package/dist/index.js +1 -1
  22. package/dist/inspector/{custom-element-WCY6D3QJ.js → custom-element-IBHKHN27.js} +19 -69
  23. package/dist/inspector/custom-element-IBHKHN27.js.map +1 -0
  24. package/dist/inspector/index.d.ts +5 -1
  25. package/dist/inspector/index.d.ts.map +1 -1
  26. package/dist/inspector/index.js +18 -17
  27. package/dist/inspector/index.js.map +1 -1
  28. package/dist/inspector/register-custom-element.js +1 -1
  29. package/dist/inspector/viewer/new-app.d.ts +0 -3
  30. package/dist/inspector/viewer/new-app.d.ts.map +1 -1
  31. package/dist/react-core/index.js +3 -1
  32. package/dist/react-core/index.js.map +1 -1
  33. package/dist/testing.js +2 -2
  34. package/dist/testing.js.map +1 -1
  35. package/dist/tools/coValues/inbox.d.ts +5 -5
  36. package/dist/tools/coValues/inbox.d.ts.map +1 -1
  37. package/dist/tools/implementation/anonymousJazzAgent.d.ts +1 -1
  38. package/dist/tools/implementation/anonymousJazzAgent.d.ts.map +1 -1
  39. package/dist/tools/implementation/zodSchema/coExport.d.ts +2 -0
  40. package/dist/tools/implementation/zodSchema/coExport.d.ts.map +1 -1
  41. package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts +4 -0
  42. package/dist/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.d.ts.map +1 -1
  43. package/dist/tools/implementation/zodSchema/typeConverters/TypeOfZodSchema.d.ts.map +1 -1
  44. package/dist/tools/implementation/zodSchema/unionUtils.d.ts.map +1 -1
  45. package/dist/worker/index.d.ts +8 -2
  46. package/dist/worker/index.d.ts.map +1 -1
  47. package/dist/worker/index.js +7 -3
  48. package/dist/worker/index.js.map +1 -1
  49. package/package.json +5 -4
  50. package/src/better-auth/auth/client.ts +8 -2
  51. package/src/better-auth/auth/react.tsx +2 -51
  52. package/src/better-auth/auth/server.ts +132 -31
  53. package/src/better-auth/auth/tests/client.test.ts +92 -4
  54. package/src/better-auth/auth/tests/react.test.tsx +43 -0
  55. package/src/better-auth/auth/tests/server.test.ts +317 -51
  56. package/src/browser/createBrowserContext.ts +8 -0
  57. package/src/inspector/custom-element.tsx +1 -1
  58. package/src/inspector/index.tsx +44 -0
  59. package/src/inspector/viewer/new-app.tsx +0 -18
  60. package/src/tools/coValues/inbox.ts +190 -108
  61. package/src/tools/implementation/anonymousJazzAgent.ts +1 -1
  62. package/src/tools/implementation/zodSchema/coExport.ts +2 -0
  63. package/src/tools/implementation/zodSchema/typeConverters/CoFieldSchemaInit.ts +8 -1
  64. package/src/tools/implementation/zodSchema/typeConverters/TypeOfZodSchema.ts +0 -1
  65. package/src/tools/implementation/zodSchema/unionUtils.ts +0 -1
  66. package/src/tools/testing.ts +1 -1
  67. package/src/tools/tests/coFeed.test.ts +33 -22
  68. package/src/tools/tests/coList.test-d.ts +17 -0
  69. package/src/tools/tests/coList.test.ts +6 -4
  70. package/src/tools/tests/coMap.record.test-d.ts +18 -0
  71. package/src/tools/tests/coMap.test-d.ts +15 -0
  72. package/src/tools/tests/coMap.test.ts +13 -5
  73. package/src/tools/tests/exportImport.test.ts +3 -1
  74. package/src/tools/tests/groupsAndAccounts.test.ts +56 -44
  75. package/src/tools/tests/inbox.test.ts +293 -31
  76. package/src/worker/index.ts +15 -5
  77. package/tsup.config.ts +1 -1
  78. package/dist/chunk-LHQQZH7I.js.map +0 -1
  79. package/dist/inspector/custom-element-WCY6D3QJ.js.map +0 -1
  80. package/src/inspector/index.ts +0 -23
@@ -4,6 +4,29 @@ import { symmetricDecrypt, symmetricEncrypt } from "better-auth/crypto";
4
4
  import { BetterAuthPlugin, createAuthMiddleware } from "better-auth/plugins";
5
5
  import type { Account, AuthCredentials, ID } from "jazz-tools";
6
6
 
7
+ // Define a type to have user fields mapped in the better-auth instance
8
+ // It should be automatic, but it needs an hard reference to BetterAuthPlugin type
9
+ // in order to be exported as library.
10
+ type JazzPlugin = BetterAuthPlugin & {
11
+ schema: {
12
+ user: {
13
+ fields: {
14
+ accountID: {
15
+ type: "string";
16
+ required: false;
17
+ input: false;
18
+ };
19
+ encryptedCredentials: {
20
+ type: "string";
21
+ required: false;
22
+ input: false;
23
+ returned: false;
24
+ };
25
+ };
26
+ };
27
+ };
28
+ };
29
+
7
30
  /**
8
31
  * @returns The BetterAuth server plugin.
9
32
  *
@@ -15,7 +38,7 @@ import type { Account, AuthCredentials, ID } from "jazz-tools";
15
38
  * });
16
39
  * ```
17
40
  */
18
- export const jazzPlugin = (): BetterAuthPlugin => {
41
+ export const jazzPlugin: () => JazzPlugin = () => {
19
42
  return {
20
43
  id: "jazz-plugin",
21
44
  schema: {
@@ -46,15 +69,15 @@ export const jazzPlugin = (): BetterAuthPlugin => {
46
69
  // If the user is created without a jazzAuth, it will throw an error.
47
70
  if (!contextContainsJazzAuth(context)) {
48
71
  throw new APIError(422, {
49
- message: "JazzAuth is required",
72
+ message: "JazzAuth is required on user creation",
50
73
  });
51
74
  }
52
75
  // Decorate the user with the jazz's credentials.
53
76
  return {
54
77
  data: {
55
- accountID: context?.jazzAuth?.accountID,
78
+ accountID: context.jazzAuth.accountID,
56
79
  encryptedCredentials:
57
- context?.jazzAuth?.encryptedCredentials,
80
+ context.jazzAuth.encryptedCredentials,
58
81
  },
59
82
  };
60
83
  },
@@ -62,20 +85,23 @@ export const jazzPlugin = (): BetterAuthPlugin => {
62
85
  },
63
86
  verification: {
64
87
  create: {
65
- before: async (verification, context) => {
66
- // If a jazzAuth is provided, save it for later usage.
67
- if (contextContainsJazzAuth(context)) {
68
- const parsed = JSON.parse(verification.value);
69
- const newValue = JSON.stringify({
70
- ...parsed,
71
- jazzAuth: context.jazzAuth,
72
- });
73
-
74
- return {
75
- data: {
76
- value: newValue,
88
+ after: async (verification, context) => {
89
+ /**
90
+ * For: Email OTP plugin
91
+ * After a verification is created, if it is from the EmailOTP plugin,
92
+ * create a new verification value with the jazzAuth with the same expiration.
93
+ */
94
+ if (
95
+ contextContainsJazzAuth(context) &&
96
+ verification.identifier.startsWith("sign-in-otp-")
97
+ ) {
98
+ await context.context.internalAdapter.createVerificationValue(
99
+ {
100
+ value: JSON.stringify({ jazzAuth: context.jazzAuth }),
101
+ identifier: `${verification.identifier}-jazz-auth`,
102
+ expiresAt: verification.expiresAt,
77
103
  },
78
- };
104
+ );
79
105
  }
80
106
  },
81
107
  },
@@ -124,6 +150,7 @@ export const jazzPlugin = (): BetterAuthPlugin => {
124
150
  },
125
151
 
126
152
  /**
153
+ * For: Social / OAuth2 plugin
127
154
  * /callback is the endpoint that BetterAuth uses to authenticate the user coming from a social provider.
128
155
  * 1. Catch the state
129
156
  * 2. Find the verification value
@@ -131,22 +158,20 @@ export const jazzPlugin = (): BetterAuthPlugin => {
131
158
  */
132
159
  {
133
160
  matcher: (context) => {
134
- return context.path.startsWith("/callback");
161
+ return (
162
+ context.path.startsWith("/callback") ||
163
+ context.path.startsWith("/oauth2/callback")
164
+ );
135
165
  },
136
166
  handler: createAuthMiddleware(async (ctx) => {
137
167
  const state = ctx.query?.state || ctx.body?.state;
138
168
 
139
- const data = await ctx.context.adapter.findOne<{ value: string }>({
140
- model: ctx.context.tables.verification!.modelName,
141
- where: [
142
- {
143
- field: "identifier",
144
- operator: "eq",
145
- value: state,
146
- },
147
- ],
148
- select: ["value"],
149
- });
169
+ const identifier = `${state}-jazz-auth`;
170
+
171
+ const data =
172
+ await ctx.context.internalAdapter.findVerificationValue(
173
+ identifier,
174
+ );
150
175
 
151
176
  // if not found, the social plugin will throw later anyway
152
177
  if (!data) {
@@ -158,7 +183,12 @@ export const jazzPlugin = (): BetterAuthPlugin => {
158
183
  const parsed = JSON.parse(data.value);
159
184
 
160
185
  if (parsed && "jazzAuth" in parsed) {
161
- ctx.context.jazzAuth = parsed.jazzAuth;
186
+ return {
187
+ context: {
188
+ ...ctx,
189
+ jazzAuth: parsed.jazzAuth,
190
+ },
191
+ };
162
192
  } else {
163
193
  throw new APIError(404, {
164
194
  message: "JazzAuth not found in verification value",
@@ -166,6 +196,45 @@ export const jazzPlugin = (): BetterAuthPlugin => {
166
196
  }
167
197
  }),
168
198
  },
199
+ /**
200
+ * For: Email OTP plugin
201
+ * When the user sends an OTP, we try to find the jazzAuth.
202
+ * If it isn't a sign-up, we expect to not find a verification value.
203
+ */
204
+ {
205
+ matcher: (context) => {
206
+ return context.path.startsWith("/sign-in/email-otp");
207
+ },
208
+ handler: createAuthMiddleware(async (ctx) => {
209
+ const email = ctx.body.email;
210
+ const identifier = `sign-in-otp-${email}-jazz-auth`;
211
+
212
+ const data =
213
+ await ctx.context.internalAdapter.findVerificationValue(
214
+ identifier,
215
+ );
216
+
217
+ // if not found, it isn't a sign-up
218
+ if (!data || data.expiresAt < new Date()) {
219
+ return;
220
+ }
221
+
222
+ const parsed = JSON.parse(data.value);
223
+
224
+ if (parsed && "jazzAuth" in parsed) {
225
+ return {
226
+ context: {
227
+ ...ctx,
228
+ jazzAuth: parsed.jazzAuth,
229
+ },
230
+ };
231
+ } else {
232
+ throw new APIError(500, {
233
+ message: "JazzAuth not found in verification value",
234
+ });
235
+ }
236
+ }),
237
+ },
169
238
  ],
170
239
  after: [
171
240
  /**
@@ -196,9 +265,41 @@ export const jazzPlugin = (): BetterAuthPlugin => {
196
265
  });
197
266
  }),
198
267
  },
268
+
269
+ /**
270
+ * For: Social / OAuth2 plugin
271
+ * When the user sign-in via social, we create a verification value with the jazzAuth.
272
+ */
273
+ {
274
+ matcher: (context) => {
275
+ return context.path.startsWith("/sign-in/social");
276
+ },
277
+ handler: createAuthMiddleware(async (ctx) => {
278
+ if (!contextContainsJazzAuth(ctx)) {
279
+ throw new APIError(500, {
280
+ message: "JazzAuth not found in context",
281
+ });
282
+ }
283
+
284
+ const returned = ctx.context.returned as { url: string };
285
+
286
+ const url = new URL(returned.url);
287
+ const state = url.searchParams.get("state");
288
+
289
+ const value = JSON.stringify({ jazzAuth: ctx.jazzAuth });
290
+ const expiresAt = new Date();
291
+ expiresAt.setMinutes(expiresAt.getMinutes() + 10);
292
+
293
+ await ctx.context.internalAdapter.createVerificationValue({
294
+ value,
295
+ identifier: `${state}-jazz-auth`,
296
+ expiresAt,
297
+ });
298
+ }),
299
+ },
199
300
  ],
200
301
  },
201
- };
302
+ } satisfies JazzPlugin;
202
303
  };
203
304
 
204
305
  function contextContainsJazzAuth(ctx: unknown): ctx is {
@@ -7,14 +7,19 @@ import {
7
7
  } from "jazz-tools/testing";
8
8
  import { assert, beforeEach, describe, expect, it, vi } from "vitest";
9
9
  import { jazzPluginClient } from "../client.js";
10
+ import { emailOTPClient, genericOAuthClient } from "better-auth/client/plugins";
10
11
 
11
- describe("auth client", () => {
12
+ describe("Better-Auth client plugin", () => {
12
13
  let account: Account;
13
14
  let jazzContextManager: TestJazzContextManager<Account>;
14
15
  let authSecretStorage: AuthSecretStorage;
15
16
  let authClient: ReturnType<
16
17
  typeof createAuthClient<{
17
- plugins: ReturnType<typeof jazzPluginClient>[];
18
+ plugins: ReturnType<
19
+ | typeof jazzPluginClient
20
+ | typeof emailOTPClient
21
+ | typeof genericOAuthClient
22
+ >[];
18
23
  }>
19
24
  >;
20
25
  let customFetchImpl = vi.fn();
@@ -31,7 +36,7 @@ describe("auth client", () => {
31
36
 
32
37
  authClient = createAuthClient({
33
38
  baseURL: "http://localhost:3000",
34
- plugins: [jazzPluginClient()],
39
+ plugins: [jazzPluginClient(), emailOTPClient(), genericOAuthClient()],
35
40
  fetchOptions: {
36
41
  customFetchImpl,
37
42
  },
@@ -245,5 +250,88 @@ describe("auth client", () => {
245
250
  expect(anonymousCredentials).not.toMatchObject(credentials!);
246
251
  });
247
252
 
248
- it.todo("should logout from Better Auth after Jazz's log-out");
253
+ it("should send Jazz credentials using social login", async () => {
254
+ const credentials = await authSecretStorage.get();
255
+ assert(credentials, "Jazz credentials are not available");
256
+
257
+ customFetchImpl.mockResolvedValue(new Response(JSON.stringify({})));
258
+
259
+ // Sign up
260
+ await authClient.signIn.social({
261
+ provider: "github",
262
+ });
263
+
264
+ expect(customFetchImpl).toHaveBeenCalledTimes(1);
265
+ expect(customFetchImpl.mock.calls[0]![0].toString()).toBe(
266
+ "http://localhost:3000/api/auth/sign-in/social",
267
+ );
268
+
269
+ // Verify the credentials have been injected in the request body
270
+ expect(
271
+ customFetchImpl.mock.calls[0]![1].headers.get("x-jazz-auth")!,
272
+ ).toEqual(
273
+ JSON.stringify({
274
+ accountID: credentials!.accountID,
275
+ secretSeed: credentials!.secretSeed,
276
+ accountSecret: credentials!.accountSecret,
277
+ }),
278
+ );
279
+ });
280
+
281
+ it("should send Jazz credentials using oauth generic plugin", async () => {
282
+ const credentials = await authSecretStorage.get();
283
+ assert(credentials, "Jazz credentials are not available");
284
+
285
+ customFetchImpl.mockResolvedValue(new Response(JSON.stringify({})));
286
+
287
+ // Sign up
288
+ await authClient.signIn.oauth2({
289
+ providerId: "github",
290
+ });
291
+
292
+ expect(customFetchImpl).toHaveBeenCalledTimes(1);
293
+ expect(customFetchImpl.mock.calls[0]![0].toString()).toBe(
294
+ "http://localhost:3000/api/auth/sign-in/oauth2",
295
+ );
296
+
297
+ // Verify the credentials have been injected in the request body
298
+ expect(
299
+ customFetchImpl.mock.calls[0]![1].headers.get("x-jazz-auth")!,
300
+ ).toEqual(
301
+ JSON.stringify({
302
+ accountID: credentials!.accountID,
303
+ secretSeed: credentials!.secretSeed,
304
+ accountSecret: credentials!.accountSecret,
305
+ }),
306
+ );
307
+ });
308
+
309
+ it("should send Jazz credentials using email OTP", async () => {
310
+ const credentials = await authSecretStorage.get();
311
+ assert(credentials, "Jazz credentials are not available");
312
+
313
+ customFetchImpl.mockResolvedValue(new Response(JSON.stringify({})));
314
+
315
+ // Sign up
316
+ await authClient.emailOtp.sendVerificationOtp({
317
+ email: "test@jazz.dev",
318
+ type: "sign-in",
319
+ });
320
+
321
+ expect(customFetchImpl).toHaveBeenCalledTimes(1);
322
+ expect(customFetchImpl.mock.calls[0]![0].toString()).toBe(
323
+ "http://localhost:3000/api/auth/email-otp/send-verification-otp",
324
+ );
325
+
326
+ // Verify the credentials have been injected in the request body
327
+ expect(
328
+ customFetchImpl.mock.calls[0]![1].headers.get("x-jazz-auth")!,
329
+ ).toEqual(
330
+ JSON.stringify({
331
+ accountID: credentials!.accountID,
332
+ secretSeed: credentials!.secretSeed,
333
+ accountSecret: credentials!.accountSecret,
334
+ }),
335
+ );
336
+ });
249
337
  });
@@ -0,0 +1,43 @@
1
+ // @vitest-environment jsdom
2
+ import { render } from "@testing-library/react";
3
+ import { describe, expect, it } from "vitest";
4
+ import { AuthProvider } from "../react";
5
+ import { createAuthClient } from "better-auth/client";
6
+ import { jazzPluginClient } from "../client";
7
+ import { JazzReactProvider } from "jazz-tools/react";
8
+
9
+ describe("AuthProvider", () => {
10
+ it("should throw if no JazzContext is set", () => {
11
+ const betterAuthClient = createAuthClient({
12
+ plugins: [jazzPluginClient()],
13
+ });
14
+
15
+ expect(() => {
16
+ render(
17
+ <AuthProvider betterAuthClient={betterAuthClient}>
18
+ <div />
19
+ </AuthProvider>,
20
+ );
21
+ }).toThrow(
22
+ "You need to set up a JazzProvider on top of your app to use this hook.",
23
+ );
24
+ });
25
+
26
+ it("should render with JazzReactProvider", () => {
27
+ const betterAuthClient = createAuthClient({
28
+ plugins: [jazzPluginClient()],
29
+ });
30
+
31
+ render(
32
+ <JazzReactProvider
33
+ // @ts-expect-error - no memory storage
34
+ storage={["memory"]}
35
+ sync={{ peer: "ws://", when: "never" }}
36
+ >
37
+ <AuthProvider betterAuthClient={betterAuthClient}>
38
+ <div />
39
+ </AuthProvider>
40
+ </JazzReactProvider>,
41
+ );
42
+ });
43
+ });