@convex-dev/better-auth 0.9.10 → 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.
Files changed (102) hide show
  1. package/dist/auth-config.d.ts +43 -0
  2. package/dist/auth-config.d.ts.map +1 -0
  3. package/dist/auth-config.js +45 -0
  4. package/dist/auth-config.js.map +1 -0
  5. package/dist/auth-options.d.ts +3 -0
  6. package/dist/auth-options.d.ts.map +1 -0
  7. package/dist/auth-options.js +41 -0
  8. package/dist/auth-options.js.map +1 -0
  9. package/dist/auth.d.ts +1 -3
  10. package/dist/auth.d.ts.map +1 -1
  11. package/dist/auth.js +2 -42
  12. package/dist/auth.js.map +1 -1
  13. package/dist/client/{adapterUtils.d.ts → adapter-utils.d.ts} +15 -15
  14. package/dist/client/adapter-utils.d.ts.map +1 -0
  15. package/dist/client/{adapterUtils.js → adapter-utils.js} +1 -1
  16. package/dist/client/adapter-utils.js.map +1 -0
  17. package/dist/client/adapter.d.ts +1 -2
  18. package/dist/client/adapter.d.ts.map +1 -1
  19. package/dist/client/adapter.js +4 -4
  20. package/dist/client/adapter.js.map +1 -1
  21. package/dist/client/create-api.d.ts +139 -0
  22. package/dist/client/create-api.d.ts.map +1 -0
  23. package/dist/client/create-api.js +204 -0
  24. package/dist/client/create-api.js.map +1 -0
  25. package/dist/client/create-client.d.ts +183 -0
  26. package/dist/client/create-client.d.ts.map +1 -0
  27. package/dist/client/create-client.js +311 -0
  28. package/dist/client/create-client.js.map +1 -0
  29. package/dist/client/{createSchema.d.ts → create-schema.d.ts} +1 -1
  30. package/dist/client/create-schema.d.ts.map +1 -0
  31. package/dist/client/{createSchema.js → create-schema.js} +11 -5
  32. package/dist/client/create-schema.js.map +1 -0
  33. package/dist/client/index.d.ts +4 -279
  34. package/dist/client/index.d.ts.map +1 -1
  35. package/dist/client/index.js +6 -476
  36. package/dist/client/index.js.map +1 -1
  37. package/dist/component/_generated/component.d.ts +0 -3
  38. package/dist/component/_generated/component.d.ts.map +1 -1
  39. package/dist/component/adapter.d.ts +19 -21
  40. package/dist/component/adapter.d.ts.map +1 -1
  41. package/dist/component/adapter.js +2 -2
  42. package/dist/component/adapter.js.map +1 -1
  43. package/dist/component/schema.d.ts +50 -50
  44. package/dist/nextjs/client.d.ts +4 -0
  45. package/dist/nextjs/client.d.ts.map +1 -0
  46. package/dist/nextjs/client.js +37 -0
  47. package/dist/nextjs/client.js.map +1 -0
  48. package/dist/nextjs/index.d.ts +19 -7
  49. package/dist/nextjs/index.d.ts.map +1 -1
  50. package/dist/nextjs/index.js +90 -36
  51. package/dist/nextjs/index.js.map +1 -1
  52. package/dist/plugins/convex/client.d.ts +1 -1
  53. package/dist/plugins/convex/client.d.ts.map +1 -1
  54. package/dist/plugins/convex/client.js +0 -1
  55. package/dist/plugins/convex/client.js.map +1 -1
  56. package/dist/plugins/convex/index.d.ts +239 -227
  57. package/dist/plugins/convex/index.d.ts.map +1 -1
  58. package/dist/plugins/convex/index.js +191 -37
  59. package/dist/plugins/convex/index.js.map +1 -1
  60. package/dist/plugins/cross-domain/client.d.ts +3 -3
  61. package/dist/plugins/cross-domain/client.d.ts.map +1 -1
  62. package/dist/plugins/cross-domain/index.d.ts +15 -70
  63. package/dist/plugins/cross-domain/index.d.ts.map +1 -1
  64. package/dist/plugins/cross-domain/index.js +8 -0
  65. package/dist/plugins/cross-domain/index.js.map +1 -1
  66. package/dist/react/index.d.ts +52 -2
  67. package/dist/react/index.d.ts.map +1 -1
  68. package/dist/react/index.js +133 -9
  69. package/dist/react/index.js.map +1 -1
  70. package/dist/react-start/index.d.ts +11 -41
  71. package/dist/react-start/index.d.ts.map +1 -1
  72. package/dist/react-start/index.js +82 -106
  73. package/dist/react-start/index.js.map +1 -1
  74. package/dist/utils/index.d.ts +20 -2
  75. package/dist/utils/index.d.ts.map +1 -1
  76. package/dist/utils/index.js +54 -1
  77. package/dist/utils/index.js.map +1 -1
  78. package/package.json +19 -12
  79. package/src/auth-config.ts +82 -0
  80. package/src/auth-options.ts +54 -0
  81. package/src/auth.ts +3 -56
  82. package/src/client/adapter.ts +5 -5
  83. package/src/client/create-api.ts +337 -0
  84. package/src/client/create-client.ts +446 -0
  85. package/src/client/{createSchema.ts → create-schema.ts} +10 -4
  86. package/src/client/index.ts +22 -786
  87. package/src/component/_generated/component.ts +0 -7
  88. package/src/component/adapter.ts +2 -3
  89. package/src/nextjs/client.tsx +52 -0
  90. package/src/nextjs/index.ts +138 -45
  91. package/src/plugins/convex/client.ts +1 -1
  92. package/src/plugins/convex/index.ts +337 -51
  93. package/src/plugins/cross-domain/index.ts +10 -2
  94. package/src/react/index.tsx +195 -9
  95. package/src/react-start/index.ts +126 -171
  96. package/src/test.ts +1 -1
  97. package/src/utils/index.ts +96 -1
  98. package/dist/client/adapterUtils.d.ts.map +0 -1
  99. package/dist/client/adapterUtils.js.map +0 -1
  100. package/dist/client/createSchema.d.ts.map +0 -1
  101. package/dist/client/createSchema.js.map +0 -1
  102. /package/src/client/{adapterUtils.ts → adapter-utils.ts} +0 -0
@@ -0,0 +1,446 @@
1
+ import {
2
+ type DataModelFromSchemaDefinition,
3
+ type FunctionReference,
4
+ type GenericDataModel,
5
+ type GenericMutationCtx,
6
+ type GenericSchema,
7
+ type HttpRouter,
8
+ type SchemaDefinition,
9
+ httpActionGeneric,
10
+ internalMutationGeneric,
11
+ queryGeneric,
12
+ } from "convex/server";
13
+ import { ConvexError, type Infer, v } from "convex/values";
14
+ import { convexAdapter } from "./adapter.js";
15
+ import { corsRouter } from "convex-helpers/server/cors";
16
+ import defaultSchema from "../component/schema.js";
17
+ import { type ComponentApi } from "../component/_generated/component.js";
18
+ import { type CreateAuth, type GenericCtx } from "./index.js";
19
+
20
+ export type AuthFunctions = {
21
+ onCreate?: FunctionReference<"mutation", "internal", { [key: string]: any }>;
22
+ onUpdate?: FunctionReference<"mutation", "internal", { [key: string]: any }>;
23
+ onDelete?: FunctionReference<"mutation", "internal", { [key: string]: any }>;
24
+ };
25
+
26
+ export type Triggers<
27
+ DataModel extends GenericDataModel,
28
+ Schema extends SchemaDefinition<any, any>,
29
+ > = {
30
+ [K in keyof Schema["tables"]]?: {
31
+ onCreate?: <Ctx extends GenericMutationCtx<DataModel>>(
32
+ ctx: Ctx,
33
+ doc: Infer<Schema["tables"][K]["validator"]> & {
34
+ _id: string;
35
+ _creationTime: number;
36
+ }
37
+ ) => Promise<void>;
38
+ onUpdate?: <Ctx extends GenericMutationCtx<DataModel>>(
39
+ ctx: Ctx,
40
+ newDoc: Infer<Schema["tables"][K]["validator"]> & {
41
+ _id: string;
42
+ _creationTime: number;
43
+ },
44
+ oldDoc: Infer<Schema["tables"][K]["validator"]> & {
45
+ _id: string;
46
+ _creationTime: number;
47
+ }
48
+ ) => Promise<void>;
49
+ onDelete?: <Ctx extends GenericMutationCtx<DataModel>>(
50
+ ctx: Ctx,
51
+ doc: Infer<Schema["tables"][K]["validator"]> & {
52
+ _id: string;
53
+ _creationTime: number;
54
+ }
55
+ ) => Promise<void>;
56
+ };
57
+ };
58
+
59
+ type SlimComponentApi = {
60
+ adapter: {
61
+ create: FunctionReference<"mutation", "internal">;
62
+ findOne: FunctionReference<"query", "internal">;
63
+ findMany: FunctionReference<"query", "internal">;
64
+ updateOne: FunctionReference<"mutation", "internal">;
65
+ updateMany: FunctionReference<"mutation", "internal">;
66
+ deleteOne: FunctionReference<"mutation", "internal">;
67
+ deleteMany: FunctionReference<"mutation", "internal">;
68
+ };
69
+ adapterTest?: ComponentApi["adapterTest"];
70
+ };
71
+
72
+ /**
73
+ * Backend API for the Better Auth component.
74
+ * Responsible for exposing the `client` and `triggers` APIs to the client, http
75
+ * route registration, and having convenience methods for interacting with the
76
+ * component from the backend.
77
+ *
78
+ * @param component - Generally `components.betterAuth` from
79
+ * `./_generated/api` once you've configured it in `convex.config.ts`.
80
+ * @param config - Configuration options for the component.
81
+ * @param config.local - Local schema configuration.
82
+ * @param config.verbose - Whether to enable verbose logging.
83
+ * @param config.triggers - Triggers configuration.
84
+ * @param config.authFunctions - Authentication functions configuration.
85
+ */
86
+ export const createClient = <
87
+ DataModel extends GenericDataModel,
88
+ Schema extends SchemaDefinition<GenericSchema, true> = typeof defaultSchema,
89
+ Api extends SlimComponentApi = SlimComponentApi,
90
+ >(
91
+ component: Api,
92
+ config?: {
93
+ local?: {
94
+ schema?: Schema;
95
+ };
96
+ verbose?: boolean;
97
+ } & (
98
+ | {
99
+ triggers: Triggers<DataModel, Schema>;
100
+ authFunctions: AuthFunctions;
101
+ }
102
+ | { triggers?: undefined }
103
+ )
104
+ ) => {
105
+ type BetterAuthDataModel = DataModelFromSchemaDefinition<Schema>;
106
+
107
+ const safeGetAuthUser = async (ctx: GenericCtx<DataModel>) => {
108
+ const identity = await ctx.auth.getUserIdentity();
109
+ if (!identity) {
110
+ return;
111
+ }
112
+ const session = (await ctx.runQuery(component.adapter.findOne, {
113
+ model: "session",
114
+ where: [
115
+ {
116
+ field: "_id",
117
+ value: identity.sessionId as string,
118
+ },
119
+ {
120
+ field: "expiresAt",
121
+ operator: "gt",
122
+ value: new Date().getTime(),
123
+ },
124
+ ],
125
+ })) as BetterAuthDataModel["session"]["document"] | null;
126
+
127
+ if (!session) {
128
+ return;
129
+ }
130
+
131
+ const doc = (await ctx.runQuery(component.adapter.findOne, {
132
+ model: "user",
133
+ where: [
134
+ {
135
+ field: "_id",
136
+ value: identity.subject,
137
+ },
138
+ ],
139
+ })) as BetterAuthDataModel["user"]["document"] | null;
140
+ if (!doc) {
141
+ return;
142
+ }
143
+ return doc;
144
+ };
145
+
146
+ const getAuthUser = async (ctx: GenericCtx<DataModel>) => {
147
+ const user = await safeGetAuthUser(ctx);
148
+ if (!user) {
149
+ throw new ConvexError("Unauthenticated");
150
+ }
151
+ return user;
152
+ };
153
+
154
+ const getHeaders = async (ctx: GenericCtx<DataModel>) => {
155
+ const identity = await ctx.auth.getUserIdentity();
156
+ if (!identity) {
157
+ return new Headers();
158
+ }
159
+ // Don't validate the session here, let Better Auth handle that
160
+ const session = await ctx.runQuery(component.adapter.findOne, {
161
+ model: "session",
162
+ where: [
163
+ {
164
+ field: "_id",
165
+ value: identity.sessionId as string,
166
+ },
167
+ ],
168
+ });
169
+ return new Headers({
170
+ ...(session?.token ? { authorization: `Bearer ${session.token}` } : {}),
171
+ ...(session?.ipAddress
172
+ ? { "x-forwarded-for": session.ipAddress as string }
173
+ : {}),
174
+ });
175
+ };
176
+
177
+ return {
178
+ /**
179
+ * Returns the Convex database adapter for use in Better Auth options.
180
+ * @param ctx - The Convex context
181
+ * @returns The Convex database adapter
182
+ */
183
+ adapter: (ctx: GenericCtx<DataModel>) =>
184
+ convexAdapter<DataModel, typeof ctx, Schema>(ctx, component, {
185
+ ...config,
186
+ debugLogs: config?.verbose,
187
+ }),
188
+
189
+ /**
190
+ * Returns the Better Auth auth object and headers for using Better Auth API
191
+ * methods directly in a Convex mutation or query. Convex functions don't
192
+ * have access to request headers, so the headers object is created at
193
+ * runtime with the token for the current session as a Bearer token.
194
+ *
195
+ * @param createAuth - The createAuth function
196
+ * @param ctx - The Convex context
197
+ * @returns A promise that resolves to the Better Auth `auth` API object and
198
+ * headers.
199
+ */
200
+ getAuth: async <T extends CreateAuth<DataModel>>(
201
+ createAuth: T,
202
+ ctx: GenericCtx<DataModel>
203
+ ) => ({
204
+ auth: createAuth(ctx) as ReturnType<T>,
205
+ headers: await getHeaders(ctx),
206
+ }),
207
+
208
+ /**
209
+ * Returns a Headers object for the current session using the session id
210
+ * from the current user identity via `ctx.auth.getUserIdentity()`. This is
211
+ * used to pass the headers to the Better Auth API methods when using the
212
+ * `getAuth` method.
213
+ *
214
+ * @param ctx - The Convex context
215
+ * @returns The headers
216
+ */
217
+ getHeaders,
218
+
219
+ /**
220
+ * Returns the current user or null if the user is not found
221
+ * @param ctx - The Convex context
222
+ * @returns The user or null if the user is not found
223
+ */
224
+ safeGetAuthUser,
225
+
226
+ /**
227
+ * Returns the current user or throws an error if the user is not found
228
+ *
229
+ * @param ctx - The Convex context
230
+ * @returns The user or throws an error if the user is not found
231
+ */
232
+ getAuthUser,
233
+
234
+ /**
235
+ * Returns a user by their Better Auth user id.
236
+ * @param ctx - The Convex context
237
+ * @param id - The Better Auth user id
238
+ * @returns The user or null if the user is not found
239
+ */
240
+ getAnyUserById: async (ctx: GenericCtx<DataModel>, id: string) => {
241
+ return (await ctx.runQuery(component.adapter.findOne, {
242
+ model: "user",
243
+ where: [{ field: "_id", value: id }],
244
+ })) as BetterAuthDataModel["user"]["document"] | null;
245
+ },
246
+
247
+ /**
248
+ * Replaces 0.7 behavior of returning a new user id from
249
+ * onCreateUser
250
+ * @param ctx - The Convex context
251
+ * @param authId - The Better Auth user id
252
+ * @param userId - The app user id
253
+ * @deprecated in 0.9
254
+ */
255
+ setUserId: async (
256
+ ctx: GenericMutationCtx<DataModel>,
257
+ authId: string,
258
+ userId: string
259
+ ) => {
260
+ await ctx.runMutation(component.adapter.updateOne, {
261
+ input: {
262
+ model: "user",
263
+ where: [{ field: "_id", value: authId }],
264
+ update: { userId },
265
+ },
266
+ });
267
+ },
268
+
269
+ /**
270
+ * Exposes functions for use with the ClientAuthBoundary component. Currently
271
+ * only contains getAuthUser.
272
+ * @returns Functions to pass to the ClientAuthBoundary component.
273
+ */
274
+ clientApi: () => ({
275
+ /**
276
+ * Convex query to get the current user. For use with the ClientAuthBoundary component.
277
+ *
278
+ * ```ts title="convex/auth.ts"
279
+ * export const { getAuthUser } = authComponent.clientApi();
280
+ * ```
281
+ *
282
+ * @returns The user or throws an error if the user is not found
283
+ */
284
+ getAuthUser: queryGeneric({
285
+ args: {},
286
+ handler: async (ctx: GenericCtx<DataModel>) => {
287
+ return await getAuthUser(ctx);
288
+ },
289
+ }),
290
+ }),
291
+
292
+ /**
293
+ * Exposes functions for executing trigger callbacks in the app context.
294
+ *
295
+ * Callbacks are defined in the `triggers` option to the component client config.
296
+ *
297
+ * See {@link createClient} for more information.
298
+ *
299
+ * @returns Functions to execute trigger callbacks in the app context.
300
+ */
301
+ triggersApi: () => ({
302
+ onCreate: internalMutationGeneric({
303
+ args: {
304
+ doc: v.any(),
305
+ model: v.string(),
306
+ },
307
+ handler: async (ctx, args) => {
308
+ await config?.triggers?.[args.model]?.onCreate?.(ctx, args.doc);
309
+ },
310
+ }),
311
+ onUpdate: internalMutationGeneric({
312
+ args: {
313
+ oldDoc: v.any(),
314
+ newDoc: v.any(),
315
+ model: v.string(),
316
+ },
317
+ handler: async (ctx, args) => {
318
+ await config?.triggers?.[args.model]?.onUpdate?.(
319
+ ctx,
320
+ args.newDoc,
321
+ args.oldDoc
322
+ );
323
+ },
324
+ }),
325
+ onDelete: internalMutationGeneric({
326
+ args: {
327
+ doc: v.any(),
328
+ model: v.string(),
329
+ },
330
+ handler: async (ctx, args) => {
331
+ await config?.triggers?.[args.model]?.onDelete?.(ctx, args.doc);
332
+ },
333
+ }),
334
+ }),
335
+
336
+ registerRoutes: (
337
+ http: HttpRouter,
338
+ createAuth: CreateAuth<DataModel>,
339
+ opts: {
340
+ cors?:
341
+ | boolean
342
+ | {
343
+ // These values are appended to the default values
344
+ allowedOrigins?: string[];
345
+ allowedHeaders?: string[];
346
+ exposedHeaders?: string[];
347
+ };
348
+ } = {}
349
+ ) => {
350
+ const staticAuth = createAuth({} as any);
351
+ const path = staticAuth.options.basePath ?? "/api/auth";
352
+ const authRequestHandler = httpActionGeneric(async (ctx, request) => {
353
+ if (config?.verbose) {
354
+ console.log("options.baseURL", staticAuth.options.baseURL);
355
+ console.log("request headers", request.headers);
356
+ }
357
+ const auth = createAuth(ctx as any);
358
+ const response = await auth.handler(request);
359
+ if (config?.verbose) {
360
+ console.log("response headers", response.headers);
361
+ }
362
+ return response;
363
+ });
364
+ const wellKnown = http.lookup("/.well-known/openid-configuration", "GET");
365
+
366
+ // If registerRoutes is used multiple times, this may already be defined
367
+ if (!wellKnown) {
368
+ // Redirect root well-known to api well-known
369
+ http.route({
370
+ path: "/.well-known/openid-configuration",
371
+ method: "GET",
372
+ handler: httpActionGeneric(async () => {
373
+ const url = `${process.env.CONVEX_SITE_URL}${path}/convex/.well-known/openid-configuration`;
374
+ return Response.redirect(url);
375
+ }),
376
+ });
377
+ }
378
+
379
+ if (!opts.cors) {
380
+ http.route({
381
+ pathPrefix: `${path}/`,
382
+ method: "GET",
383
+ handler: authRequestHandler,
384
+ });
385
+
386
+ http.route({
387
+ pathPrefix: `${path}/`,
388
+ method: "POST",
389
+ handler: authRequestHandler,
390
+ });
391
+
392
+ return;
393
+ }
394
+ const corsOpts =
395
+ typeof opts.cors === "boolean"
396
+ ? { allowedOrigins: [], allowedHeaders: [], exposedHeaders: [] }
397
+ : opts.cors;
398
+ let trustedOriginsOption:
399
+ | string[]
400
+ | ((request: Request) => string[] | Promise<string[]>)
401
+ | undefined;
402
+ const cors = corsRouter(http, {
403
+ allowedOrigins: async (request) => {
404
+ trustedOriginsOption =
405
+ trustedOriginsOption ??
406
+ (await staticAuth.$context).options.trustedOrigins ??
407
+ [];
408
+ const trustedOrigins = Array.isArray(trustedOriginsOption)
409
+ ? trustedOriginsOption
410
+ : await trustedOriginsOption(request);
411
+ return trustedOrigins
412
+ .map((origin) =>
413
+ // Strip trailing wildcards, unsupported for allowedOrigins
414
+ origin.endsWith("*") && origin.length > 1
415
+ ? origin.slice(0, -1)
416
+ : origin
417
+ )
418
+ .concat(corsOpts.allowedOrigins ?? []);
419
+ },
420
+ allowCredentials: true,
421
+ allowedHeaders: [
422
+ "Content-Type",
423
+ "Better-Auth-Cookie",
424
+ "Authorization",
425
+ ].concat(corsOpts.allowedHeaders ?? []),
426
+ exposedHeaders: ["Set-Better-Auth-Cookie"].concat(
427
+ corsOpts.exposedHeaders ?? []
428
+ ),
429
+ debug: config?.verbose,
430
+ enforceAllowOrigins: false,
431
+ });
432
+
433
+ cors.route({
434
+ pathPrefix: `${path}/`,
435
+ method: "GET",
436
+ handler: authRequestHandler,
437
+ });
438
+
439
+ cors.route({
440
+ pathPrefix: `${path}/`,
441
+ method: "POST",
442
+ handler: authRequestHandler,
443
+ });
444
+ },
445
+ };
446
+ };
@@ -44,7 +44,7 @@ const mergedIndexFields = (tables: BetterAuthDBSchema) =>
44
44
  const manualIndexes =
45
45
  indexFields[key as keyof typeof indexFields]?.map((index) => {
46
46
  return typeof index === "string"
47
- ? table.fields[index]?.fieldName ?? index
47
+ ? (table.fields[index]?.fieldName ?? index)
48
48
  : index.map((i) => table.fields[i]?.fieldName ?? i);
49
49
  }) || [];
50
50
  const specialFieldIndexes = Object.keys(
@@ -80,9 +80,15 @@ export const createSchema = async ({
80
80
  "Better Auth schema must be generated in the Better Auth component directory."
81
81
  );
82
82
  }
83
- let code: string = `// This file is auto-generated. Do not edit this file manually.
84
- // To regenerate the schema, run:
85
- // \`npx @better-auth/cli generate --output ${file} -y\`
83
+ let code: string = `/**
84
+ * This file is auto-generated. Do not edit this file manually.
85
+ * To regenerate the schema, run:
86
+ * \`npx @better-auth/cli generate --output ${file} -y\`
87
+ *
88
+ * To customize the schema, generate to an alternate file and import
89
+ * the table definitions to your schema file. See
90
+ * https://convex-better-auth.netlify.app/features/local-install#adding-custom-indexes.
91
+ */
86
92
 
87
93
  import { defineSchema, defineTable } from "convex/server";
88
94
  import { v } from "convex/values";