@convex-dev/better-auth 0.7.0-alpha.12 → 0.7.0-alpha.2

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 (108) hide show
  1. package/dist/commonjs/client/adapter.d.ts +1 -10
  2. package/dist/commonjs/client/adapter.d.ts.map +1 -1
  3. package/dist/commonjs/client/adapter.js +192 -183
  4. package/dist/commonjs/client/adapter.js.map +1 -1
  5. package/dist/commonjs/client/index.d.ts +179 -238
  6. package/dist/commonjs/client/index.d.ts.map +1 -1
  7. package/dist/commonjs/client/index.js +67 -60
  8. package/dist/commonjs/client/index.js.map +1 -1
  9. package/dist/commonjs/component/lib.d.ts +548 -218
  10. package/dist/commonjs/component/lib.d.ts.map +1 -1
  11. package/dist/commonjs/component/lib.js +286 -315
  12. package/dist/commonjs/component/lib.js.map +1 -1
  13. package/dist/commonjs/component/schema.d.ts +28 -90
  14. package/dist/commonjs/component/schema.d.ts.map +1 -1
  15. package/dist/commonjs/component/schema.js +18 -76
  16. package/dist/commonjs/component/schema.js.map +1 -1
  17. package/dist/commonjs/component/util.d.ts +86 -148
  18. package/dist/commonjs/component/util.d.ts.map +1 -1
  19. package/dist/commonjs/nextjs/index.d.ts.map +1 -1
  20. package/dist/commonjs/nextjs/index.js +0 -3
  21. package/dist/commonjs/nextjs/index.js.map +1 -1
  22. package/dist/commonjs/plugins/convex/index.d.ts +11 -14
  23. package/dist/commonjs/plugins/convex/index.d.ts.map +1 -1
  24. package/dist/commonjs/plugins/convex/index.js +4 -10
  25. package/dist/commonjs/plugins/convex/index.js.map +1 -1
  26. package/dist/commonjs/plugins/cross-domain/client.d.ts +1 -1
  27. package/dist/commonjs/plugins/cross-domain/index.d.ts +3 -5
  28. package/dist/commonjs/plugins/cross-domain/index.d.ts.map +1 -1
  29. package/dist/commonjs/plugins/cross-domain/index.js +5 -19
  30. package/dist/commonjs/plugins/cross-domain/index.js.map +1 -1
  31. package/dist/commonjs/react/client.d.ts +1 -1
  32. package/dist/commonjs/react/client.d.ts.map +1 -1
  33. package/dist/commonjs/react/client.js +9 -3
  34. package/dist/commonjs/react/client.js.map +1 -1
  35. package/dist/commonjs/react-start/index.d.ts +3 -37
  36. package/dist/commonjs/react-start/index.d.ts.map +1 -1
  37. package/dist/commonjs/react-start/index.js +4 -20
  38. package/dist/commonjs/react-start/index.js.map +1 -1
  39. package/dist/esm/client/adapter.d.ts +1 -10
  40. package/dist/esm/client/adapter.d.ts.map +1 -1
  41. package/dist/esm/client/adapter.js +192 -183
  42. package/dist/esm/client/adapter.js.map +1 -1
  43. package/dist/esm/client/index.d.ts +179 -238
  44. package/dist/esm/client/index.d.ts.map +1 -1
  45. package/dist/esm/client/index.js +67 -60
  46. package/dist/esm/client/index.js.map +1 -1
  47. package/dist/esm/component/lib.d.ts +548 -218
  48. package/dist/esm/component/lib.d.ts.map +1 -1
  49. package/dist/esm/component/lib.js +286 -315
  50. package/dist/esm/component/lib.js.map +1 -1
  51. package/dist/esm/component/schema.d.ts +28 -90
  52. package/dist/esm/component/schema.d.ts.map +1 -1
  53. package/dist/esm/component/schema.js +18 -76
  54. package/dist/esm/component/schema.js.map +1 -1
  55. package/dist/esm/component/util.d.ts +86 -148
  56. package/dist/esm/component/util.d.ts.map +1 -1
  57. package/dist/esm/nextjs/index.d.ts.map +1 -1
  58. package/dist/esm/nextjs/index.js +0 -3
  59. package/dist/esm/nextjs/index.js.map +1 -1
  60. package/dist/esm/plugins/convex/index.d.ts +11 -14
  61. package/dist/esm/plugins/convex/index.d.ts.map +1 -1
  62. package/dist/esm/plugins/convex/index.js +4 -10
  63. package/dist/esm/plugins/convex/index.js.map +1 -1
  64. package/dist/esm/plugins/cross-domain/client.d.ts +1 -1
  65. package/dist/esm/plugins/cross-domain/index.d.ts +3 -5
  66. package/dist/esm/plugins/cross-domain/index.d.ts.map +1 -1
  67. package/dist/esm/plugins/cross-domain/index.js +5 -19
  68. package/dist/esm/plugins/cross-domain/index.js.map +1 -1
  69. package/dist/esm/react/client.d.ts +1 -1
  70. package/dist/esm/react/client.d.ts.map +1 -1
  71. package/dist/esm/react/client.js +9 -3
  72. package/dist/esm/react/client.js.map +1 -1
  73. package/dist/esm/react-start/index.d.ts +3 -37
  74. package/dist/esm/react-start/index.d.ts.map +1 -1
  75. package/dist/esm/react-start/index.js +4 -20
  76. package/dist/esm/react-start/index.js.map +1 -1
  77. package/package.json +5 -20
  78. package/src/client/adapter.ts +195 -191
  79. package/src/client/cors.ts +425 -0
  80. package/src/client/index.ts +80 -61
  81. package/src/component/_generated/api.d.ts +149 -605
  82. package/src/component/lib.ts +335 -444
  83. package/src/component/schema.ts +19 -81
  84. package/src/nextjs/index.ts +0 -3
  85. package/src/plugins/convex/index.ts +4 -12
  86. package/src/plugins/cross-domain/index.ts +5 -19
  87. package/src/react/client.tsx +11 -5
  88. package/src/react-start/index.ts +6 -33
  89. package/dist/commonjs/component/adapterTest.d.ts +0 -19
  90. package/dist/commonjs/component/adapterTest.d.ts.map +0 -1
  91. package/dist/commonjs/component/adapterTest.js +0 -82
  92. package/dist/commonjs/component/adapterTest.js.map +0 -1
  93. package/dist/commonjs/utils/index.d.ts +0 -2
  94. package/dist/commonjs/utils/index.d.ts.map +0 -1
  95. package/dist/commonjs/utils/index.js +0 -8
  96. package/dist/commonjs/utils/index.js.map +0 -1
  97. package/dist/esm/component/adapterTest.d.ts +0 -19
  98. package/dist/esm/component/adapterTest.d.ts.map +0 -1
  99. package/dist/esm/component/adapterTest.js +0 -82
  100. package/dist/esm/component/adapterTest.js.map +0 -1
  101. package/dist/esm/utils/index.d.ts +0 -2
  102. package/dist/esm/utils/index.d.ts.map +0 -1
  103. package/dist/esm/utils/index.js +0 -8
  104. package/dist/esm/utils/index.js.map +0 -1
  105. package/src/client/adapter.test.ts +0 -144
  106. package/src/component/adapterTest.ts +0 -141
  107. package/src/react-start/vite-env.d.ts +0 -2
  108. /package/src/{utils/index.ts → util.ts} +0 -0
@@ -0,0 +1,425 @@
1
+ /**
2
+ * This file defines a CorsHttpRouter class that extends Convex's HttpRouter.
3
+ * It provides CORS (Cross-Origin Resource Sharing) support for HTTP routes.
4
+ *
5
+ * The CorsHttpRouter:
6
+ * 1. Allows specifying allowed origins for CORS.
7
+ * 2. Overrides the route method to add CORS headers to all non-OPTIONS requests.
8
+ * 3. Automatically adds an OPTIONS route to handle CORS preflight requests.
9
+ * 4. Uses the handleCors helper function to apply CORS headers consistently.
10
+ *
11
+ * This router simplifies the process of making Convex HTTP endpoints
12
+ * accessible to web applications hosted on different domains while
13
+ * maintaining proper CORS configuration.
14
+ */
15
+ import {
16
+ type GenericActionCtx,
17
+ httpActionGeneric,
18
+ httpRouter,
19
+ HttpRouter,
20
+ ROUTABLE_HTTP_METHODS,
21
+ type RoutableMethod,
22
+ type PublicHttpAction,
23
+ type RouteSpec,
24
+ type RouteSpecWithPath,
25
+ type RouteSpecWithPathPrefix,
26
+ } from "convex/server";
27
+
28
+ export const DEFAULT_EXPOSED_HEADERS = [
29
+ // For Range requests
30
+ "Content-Range",
31
+ "Accept-Ranges",
32
+ ];
33
+
34
+ export type CorsConfig = {
35
+ /**
36
+ * Whether to allow credentials in the request.
37
+ * When true, the request can include cookies and authentication headers.
38
+ * @default false
39
+ */
40
+ allowCredentials?: boolean;
41
+ /**
42
+ * An array of allowed origins: what domains are allowed to make requests.
43
+ * For example, ["https://example.com"] would only allow requests from
44
+ * https://example.com.
45
+ * You can also use wildcards to allow all subdomains of a given domain.
46
+ * E.g. ["*.example.com"] would allow requests from:
47
+ * - https://subdomain.example.com
48
+ * - https://example.com
49
+ * @default ["*"]
50
+ */
51
+ allowedOrigins?: string[] | ((req: Request) => Promise<string[]>);
52
+ /**
53
+ * An array of allowed headers: what headers are allowed to be sent in
54
+ * the request.
55
+ * @default ["Content-Type"]
56
+ */
57
+ allowedHeaders?: string[];
58
+ /**
59
+ * An array of exposed headers: what headers are allowed to be sent in
60
+ * the response.
61
+ * Note: if you pass in an empty array, it will not expose any headers.
62
+ * If you want to extend the default exposed headers, you can do so by
63
+ * passing in [...DEFAULT_EXPOSED_HEADERS, ...yourHeaders].
64
+ * @default {@link DEFAULT_EXPOSED_HEADERS}
65
+ */
66
+ exposedHeaders?: string[];
67
+ /**
68
+ * The maximum age of the preflight request in seconds.
69
+ * @default 86400 (1 day)
70
+ */
71
+ browserCacheMaxAge?: number;
72
+ /**
73
+ * Whether to block requests from origins that are not in the allowedOrigins list.
74
+ * @default true
75
+ */
76
+ enforceAllowOrigins?: boolean;
77
+ /**
78
+ * Whether to log debugging information about CORS requests.
79
+ * @default false
80
+ */
81
+ debug?: boolean;
82
+ };
83
+
84
+ type RouteSpecWithCors = RouteSpec & CorsConfig;
85
+
86
+ /**
87
+ * Factory function to create a router that adds CORS support to routes.
88
+ * @param allowedOrigins An array of allowed origins for CORS.
89
+ * @returns A function to use instead of http.route when you want CORS.
90
+ */
91
+ export const corsRouter = (http: HttpRouter, corsConfig?: CorsConfig) => {
92
+ const allowedExactMethodsByPath: Map<string, Set<string>> = new Map();
93
+ const allowedPrefixMethodsByPath: Map<string, Set<string>> = new Map();
94
+ return {
95
+ http,
96
+ route: (routeSpec: RouteSpecWithCors): void => {
97
+ const tempRouter = httpRouter();
98
+ tempRouter.exactRoutes = http.exactRoutes;
99
+ tempRouter.prefixRoutes = http.prefixRoutes;
100
+
101
+ const config = {
102
+ ...corsConfig,
103
+ ...routeSpec,
104
+ };
105
+
106
+ const httpCorsHandler = handleCors({
107
+ originalHandler: routeSpec.handler,
108
+ allowedMethods: [routeSpec.method],
109
+ ...config,
110
+ });
111
+ /**
112
+ * Figure out what kind of route we're adding: exact or prefix and handle
113
+ * accordingly.
114
+ */
115
+ if ("path" in routeSpec) {
116
+ let methods = allowedExactMethodsByPath.get(routeSpec.path);
117
+ if (!methods) {
118
+ methods = new Set<string>();
119
+ allowedExactMethodsByPath.set(routeSpec.path, methods);
120
+ }
121
+ methods.add(routeSpec.method);
122
+ tempRouter.route({
123
+ path: routeSpec.path,
124
+ method: routeSpec.method,
125
+ handler: httpCorsHandler,
126
+ });
127
+ handleExactRoute(tempRouter, routeSpec, config, Array.from(methods));
128
+ } else {
129
+ let methods = allowedPrefixMethodsByPath.get(routeSpec.pathPrefix);
130
+ if (!methods) {
131
+ methods = new Set<string>();
132
+ allowedPrefixMethodsByPath.set(routeSpec.pathPrefix, methods);
133
+ }
134
+ methods.add(routeSpec.method);
135
+ tempRouter.route({
136
+ pathPrefix: routeSpec.pathPrefix,
137
+ method: routeSpec.method,
138
+ handler: httpCorsHandler,
139
+ });
140
+ handlePrefixRoute(tempRouter, routeSpec, config, Array.from(methods));
141
+ }
142
+
143
+ /**
144
+ * Copy the routes from the temporary router to the main router.
145
+ */
146
+ http.exactRoutes = new Map(tempRouter.exactRoutes);
147
+ http.prefixRoutes = new Map(tempRouter.prefixRoutes);
148
+ },
149
+ };
150
+ };
151
+
152
+ /**
153
+ * Handles exact route matching and adds OPTIONS handler.
154
+ * @param tempRouter Temporary router instance.
155
+ * @param routeSpec Route specification for exact matching.
156
+ */
157
+ function handleExactRoute(
158
+ tempRouter: HttpRouter,
159
+ routeSpec: RouteSpecWithPath,
160
+ config: CorsConfig,
161
+ allowedMethods: string[]
162
+ ): void {
163
+ const currentMethodsForPath = tempRouter.exactRoutes.get(routeSpec.path);
164
+ /**
165
+ * Add the OPTIONS handler for the given path
166
+ */
167
+ const optionsHandler = createOptionsHandlerForMethods(allowedMethods, config);
168
+ currentMethodsForPath?.set("OPTIONS", optionsHandler);
169
+ tempRouter.exactRoutes.set(routeSpec.path, new Map(currentMethodsForPath));
170
+ }
171
+
172
+ /**
173
+ * Handles prefix route matching and adds OPTIONS handler.
174
+ * @param tempRouter Temporary router instance.
175
+ * @param routeSpec Route specification for prefix matching.
176
+ */
177
+ function handlePrefixRoute(
178
+ tempRouter: HttpRouter,
179
+ routeSpec: RouteSpecWithPathPrefix,
180
+ config: CorsConfig,
181
+ allowedMethods: string[]
182
+ ): void {
183
+ /**
184
+ * prefixRoutes is structured differently than exactRoutes. It's defined as
185
+ * a Map<string, Map<string, PublicHttpAction>> where the KEY is the
186
+ * METHOD and the VALUE is a map of paths and handlers.
187
+ */
188
+ const optionsHandler = createOptionsHandlerForMethods(allowedMethods, config);
189
+
190
+ const optionsPrefixes =
191
+ tempRouter.prefixRoutes.get("OPTIONS") ||
192
+ new Map<string, PublicHttpAction>();
193
+ optionsPrefixes.set(routeSpec.pathPrefix, optionsHandler);
194
+
195
+ tempRouter.prefixRoutes.set("OPTIONS", optionsPrefixes);
196
+ }
197
+
198
+ /**
199
+ * Creates an OPTIONS handler for the given HTTP methods.
200
+ * @param methods Array of HTTP methods to be allowed.
201
+ * @returns A CORS-enabled OPTIONS handler.
202
+ */
203
+ function createOptionsHandlerForMethods(
204
+ methods: string[],
205
+ config: CorsConfig
206
+ ): PublicHttpAction {
207
+ return handleCors({
208
+ ...config,
209
+ allowedMethods: methods,
210
+ });
211
+ }
212
+
213
+ export default corsRouter;
214
+
215
+ /**
216
+ * handleCors() is a higher-order function that wraps a Convex HTTP action handler to add CORS support.
217
+ * It allows for customization of allowed HTTP methods and origins for cross-origin requests.
218
+ *
219
+ * The function:
220
+ * 1. Validates and normalizes the allowed HTTP methods.
221
+ * 2. Generates appropriate CORS headers based on the provided configuration.
222
+ * 3. Handles preflight OPTIONS requests automatically.
223
+ * 4. Wraps the original handler to add CORS headers to its response.
224
+ *
225
+ * This helper simplifies the process of making Convex HTTP actions accessible
226
+ * to web applications hosted on different domains.
227
+ */
228
+
229
+ const SECONDS_IN_A_DAY = 60 * 60 * 24;
230
+
231
+ /**
232
+ * Example CORS origins:
233
+ * - "*" (allow all origins)
234
+ * - "https://example.com" (allow a specific domain)
235
+ * - "https://*.example.com" (allow all subdomains of example.com)
236
+ * - "https://example1.com, https://example2.com" (allow multiple specific domains)
237
+ * - "null" (allow requests from data URLs or local files)
238
+ */
239
+
240
+ const handleCors = ({
241
+ originalHandler,
242
+ allowedMethods = ["OPTIONS"],
243
+ allowedOrigins = ["*"],
244
+ allowedHeaders = ["Content-Type"],
245
+ exposedHeaders = DEFAULT_EXPOSED_HEADERS,
246
+ allowCredentials = false,
247
+ browserCacheMaxAge = SECONDS_IN_A_DAY,
248
+ enforceAllowOrigins = true,
249
+ debug = false,
250
+ }: {
251
+ originalHandler?: PublicHttpAction;
252
+ allowedMethods?: string[];
253
+ } & CorsConfig) => {
254
+ const uniqueMethods = Array.from(
255
+ new Set(
256
+ allowedMethods.map((method) => method.toUpperCase() as RoutableMethod)
257
+ )
258
+ );
259
+ const filteredMethods = uniqueMethods.filter((method) =>
260
+ ROUTABLE_HTTP_METHODS.includes(method)
261
+ );
262
+
263
+ if (filteredMethods.length === 0) {
264
+ throw new Error("No valid HTTP methods provided");
265
+ }
266
+
267
+ /**
268
+ * Ensure OPTIONS is not duplicated if it was passed in
269
+ * E.g. if allowedMethods = ["GET", "OPTIONS"]
270
+ */
271
+ const allowMethods = filteredMethods.includes("OPTIONS")
272
+ ? filteredMethods.join(", ")
273
+ : [...filteredMethods].join(", ");
274
+
275
+ /**
276
+ * Build up the set of CORS headers
277
+ */
278
+ const commonHeaders: Record<string, string> = {
279
+ Vary: "Origin",
280
+ };
281
+ if (allowCredentials) {
282
+ commonHeaders["Access-Control-Allow-Credentials"] = "true";
283
+ }
284
+ if (exposedHeaders.length > 0) {
285
+ commonHeaders["Access-Control-Expose-Headers"] = exposedHeaders.join(", ");
286
+ }
287
+
288
+ async function parseAllowedOrigins(request: Request): Promise<string[]> {
289
+ return Array.isArray(allowedOrigins)
290
+ ? allowedOrigins
291
+ : await allowedOrigins(request);
292
+ }
293
+
294
+ // Helper function to check if origin is allowed (including wildcard subdomain matching)
295
+ async function isAllowedOrigin(request: Request): Promise<boolean> {
296
+ const requestOrigin = request.headers.get("origin");
297
+ if (!requestOrigin) return false;
298
+ return (await parseAllowedOrigins(request)).some((allowed) => {
299
+ if (allowed === "*") return true;
300
+ if (allowed === requestOrigin) return true;
301
+ if (allowed.startsWith("*.")) {
302
+ const wildcardDomain = allowed.slice(1); // ".bar.com"
303
+ const rootDomain = allowed.slice(2); // "bar.com"
304
+ try {
305
+ const url = new URL(requestOrigin);
306
+ return (
307
+ url.protocol === "https:" &&
308
+ (url.hostname.endsWith(wildcardDomain) ||
309
+ url.hostname === rootDomain)
310
+ );
311
+ } catch {
312
+ return false; // Invalid URL format
313
+ }
314
+ }
315
+ return false;
316
+ });
317
+ }
318
+
319
+ /**
320
+ * Return our modified HTTP action
321
+ */
322
+ return httpActionGeneric(
323
+ async (ctx: GenericActionCtx<any>, request: Request) => {
324
+ if (debug) {
325
+ console.log("CORS request", {
326
+ path: request.url,
327
+ origin: request.headers.get("origin"),
328
+ headers: request.headers,
329
+ method: request.method,
330
+ body: request.body,
331
+ });
332
+ }
333
+ const requestOrigin = request.headers.get("origin");
334
+ const parsedAllowedOrigins = await parseAllowedOrigins(request);
335
+
336
+ if (debug) {
337
+ console.log("allowed origins", parsedAllowedOrigins);
338
+ }
339
+
340
+ // Handle origin matching
341
+ let allowOrigins: string | null = null;
342
+ if (parsedAllowedOrigins.includes("*") && !allowCredentials) {
343
+ allowOrigins = "*";
344
+ } else if (requestOrigin) {
345
+ // Check if the request origin matches any of the allowed origins
346
+ // (including wildcard subdomain matching if configured)
347
+ if (await isAllowedOrigin(request)) {
348
+ allowOrigins = requestOrigin;
349
+ }
350
+ }
351
+
352
+ if (enforceAllowOrigins && !allowOrigins) {
353
+ // Origin not allowed
354
+ console.error(
355
+ `Request from origin ${requestOrigin} blocked, missing from allowed origins: ${parsedAllowedOrigins.join()}`
356
+ );
357
+ return new Response(null, { status: 403 });
358
+ }
359
+ /**
360
+ * OPTIONS has no handler and just returns headers
361
+ */
362
+ if (request.method === "OPTIONS") {
363
+ const responseHeaders = new Headers({
364
+ ...commonHeaders,
365
+ "Access-Control-Allow-Origin": allowOrigins ?? "",
366
+ "Access-Control-Allow-Methods": allowMethods,
367
+ "Access-Control-Allow-Headers": allowedHeaders.join(", "),
368
+ "Access-Control-Max-Age": browserCacheMaxAge.toString(),
369
+ });
370
+ if (debug) {
371
+ console.log("CORS OPTIONS response headers", responseHeaders);
372
+ }
373
+ return new Response(null, {
374
+ status: 204,
375
+ headers: responseHeaders,
376
+ });
377
+ }
378
+
379
+ /**
380
+ * If the method is not OPTIONS, it must pass a handler
381
+ */
382
+ if (!originalHandler) {
383
+ throw new Error("No PublicHttpAction provider to CORS handler");
384
+ }
385
+
386
+ /**
387
+ * First, execute the original handler
388
+ */
389
+ const innerHandler = ("_handler" in originalHandler
390
+ ? (originalHandler["_handler"] as PublicHttpAction)
391
+ : originalHandler) as unknown as (
392
+ ctx: GenericActionCtx<any>,
393
+ request: Request
394
+ ) => Promise<Response>;
395
+ const originalResponse = await innerHandler(ctx, request);
396
+
397
+ /**
398
+ * Second, get a copy of the original response's headers
399
+ */
400
+ const newHeaders = new Headers(originalResponse.headers);
401
+ newHeaders.set("Access-Control-Allow-Origin", allowOrigins ?? "");
402
+
403
+ /**
404
+ * Third, add or update our CORS headers
405
+ */
406
+ Object.entries(commonHeaders).forEach(([key, value]) => {
407
+ newHeaders.set(key, value);
408
+ });
409
+
410
+ if (debug) {
411
+ console.log("CORS response headers", newHeaders);
412
+ }
413
+
414
+ /**
415
+ * Fourth, return the modified Response.
416
+ * A Response object is immutable, so we create a new one to return here.
417
+ */
418
+ return new Response(originalResponse.body, {
419
+ status: originalResponse.status,
420
+ statusText: originalResponse.statusText,
421
+ headers: newHeaders,
422
+ });
423
+ }
424
+ );
425
+ };
@@ -16,36 +16,33 @@ import { type GenericId, Infer, v } from "convex/values";
16
16
  import type { api } from "../component/_generated/api";
17
17
  import schema from "../component/schema";
18
18
  import { convexAdapter } from "./adapter";
19
+ import corsRouter from "./cors";
20
+ import { getByArgsValidator, updateArgsInputValidator } from "../component/lib";
19
21
  import { betterAuth } from "better-auth";
20
22
  import { omit } from "convex-helpers";
21
23
  import { createCookieGetter } from "better-auth/cookies";
22
24
  import { fetchQuery } from "convex/nextjs";
23
25
  import { JWT_COOKIE_NAME } from "../plugins/convex";
24
- import { requireEnv } from "../utils";
25
- import { partial } from "convex-helpers/validators";
26
- import { adapterArgsValidator, adapterWhereValidator } from "../component/lib";
27
- import { corsRouter } from "convex-helpers/server/cors";
26
+ import { requireEnv } from "../util";
28
27
  export { convexAdapter };
29
28
 
30
29
  const createUserFields = omit(schema.tables.user.validator.fields, ["userId"]);
31
30
  const createUserValidator = v.object(createUserFields);
32
31
  const createUserArgsValidator = v.object({
33
32
  input: v.object({
34
- model: v.literal("user"),
35
- data: v.object(createUserFields),
33
+ ...createUserFields,
34
+ table: v.literal("user"),
36
35
  }),
37
36
  });
38
37
  const updateUserArgsValidator = v.object({
39
- input: v.object({
40
- model: v.literal("user"),
41
- where: v.optional(v.array(adapterWhereValidator)),
42
- update: v.object(partial(createUserFields)),
43
- }),
38
+ input: updateArgsInputValidator("user"),
44
39
  });
40
+ const deleteUserArgsValidator = v.object(getByArgsValidator);
41
+
45
42
  const createSessionArgsValidator = v.object({
46
43
  input: v.object({
47
- model: v.literal("session"),
48
- data: v.object(schema.tables.session.validator.fields),
44
+ table: v.literal("session"),
45
+ ...schema.tables.session.validator.fields,
49
46
  }),
50
47
  });
51
48
 
@@ -64,7 +61,7 @@ export type AuthFunctions = {
64
61
  deleteUser: FunctionReference<
65
62
  "mutation",
66
63
  "internal",
67
- Infer<typeof adapterArgsValidator>
64
+ Infer<typeof deleteUserArgsValidator>
68
65
  >;
69
66
  updateUser: FunctionReference<
70
67
  "mutation",
@@ -131,14 +128,10 @@ export class BetterAuth<UserId extends string = string> {
131
128
  if (!identity) {
132
129
  return null;
133
130
  }
134
- const doc = await ctx.runQuery(this.component.lib.findOne, {
135
- model: "user",
136
- where: [
137
- {
138
- field: "userId",
139
- value: identity.subject,
140
- },
141
- ],
131
+ const doc = await ctx.runQuery(this.component.lib.getBy, {
132
+ table: "user",
133
+ field: "userId",
134
+ value: identity.subject,
142
135
  });
143
136
  if (!doc) {
144
137
  return null;
@@ -189,38 +182,35 @@ export class BetterAuth<UserId extends string = string> {
189
182
  createUser: internalMutationGeneric({
190
183
  args: createUserArgsValidator,
191
184
  handler: async (ctx, args) => {
192
- const userId = await opts.onCreateUser(ctx, args.input.data);
185
+ const userId = await opts.onCreateUser(ctx, args.input);
186
+ const input = { ...args.input, table: "user", userId };
193
187
  return ctx.runMutation(this.component.lib.create, {
194
- input: {
195
- ...args.input,
196
- data: { ...args.input.data, userId },
197
- },
188
+ input,
198
189
  });
199
190
  },
200
191
  }),
201
192
  deleteUser: internalMutationGeneric({
202
- args: adapterArgsValidator,
193
+ args: deleteUserArgsValidator,
203
194
  handler: async (ctx, args) => {
204
- const doc = await ctx.runMutation(this.component.lib.deleteOne, args);
205
- if (doc && opts.onDeleteUser) {
195
+ const doc = await ctx.runMutation(this.component.lib.deleteBy, args);
196
+ if (opts.onDeleteUser) {
206
197
  await opts.onDeleteUser(ctx, doc.userId as UserId);
207
198
  }
208
- return doc;
209
199
  },
210
200
  }),
211
201
  updateUser: internalMutationGeneric({
212
202
  args: updateUserArgsValidator,
213
203
  handler: async (ctx, args) => {
214
204
  const updatedUser = await ctx.runMutation(
215
- this.component.lib.updateOne,
216
- { input: args.input }
205
+ this.component.lib.update,
206
+ args
217
207
  );
218
208
  // Type narrowing
219
209
  if (!("emailVerified" in updatedUser)) {
220
210
  throw new Error("invalid user");
221
211
  }
222
212
  if (opts.onUpdateUser) {
223
- await opts.onUpdateUser(ctx, omit(updatedUser, ["_id"]));
213
+ await opts.onUpdateUser(ctx, omit(updatedUser, ["id"]));
224
214
  }
225
215
  return updatedUser;
226
216
  },
@@ -228,9 +218,14 @@ export class BetterAuth<UserId extends string = string> {
228
218
  createSession: internalMutationGeneric({
229
219
  args: createSessionArgsValidator,
230
220
  handler: async (ctx, args) => {
231
- const session = await ctx.runMutation(this.component.lib.create, {
232
- input: args.input,
233
- });
221
+ const session = await ctx.runMutation(
222
+ this.component.lib.create,
223
+ args
224
+ );
225
+ // Type narrowing
226
+ if (!("ipAddress" in session)) {
227
+ throw new Error("invalid session");
228
+ }
234
229
  await opts.onCreateSession?.(ctx, session);
235
230
  return session;
236
231
  },
@@ -247,7 +242,6 @@ export class BetterAuth<UserId extends string = string> {
247
242
  const path = betterAuthOptions.basePath ?? "/api/auth";
248
243
  const authRequestHandler = httpActionGeneric(async (ctx, request) => {
249
244
  if (this.config.verbose) {
250
- console.log("options.baseURL", betterAuthOptions.baseURL);
251
245
  console.log("request headers", request.headers);
252
246
  }
253
247
  const auth = createAuth(ctx);
@@ -258,20 +252,15 @@ export class BetterAuth<UserId extends string = string> {
258
252
  return response;
259
253
  });
260
254
 
261
- const wellKnown = http.lookup("/.well-known/openid-configuration", "GET");
262
-
263
- // If registerRoutes is used multiple times, this may already be defined
264
- if (!wellKnown) {
265
- // Redirect root well-known to api well-known
266
- http.route({
267
- path: "/.well-known/openid-configuration",
268
- method: "GET",
269
- handler: httpActionGeneric(async () => {
270
- const url = `${requireEnv("CONVEX_SITE_URL")}/api/auth/convex/.well-known/openid-configuration`;
271
- return Response.redirect(url);
272
- }),
273
- });
274
- }
255
+ // Redirect root well-known to api well-known
256
+ http.route({
257
+ path: "/.well-known/openid-configuration",
258
+ method: "GET",
259
+ handler: httpActionGeneric(async () => {
260
+ const url = `${requireEnv("CONVEX_SITE_URL")}/api/auth/convex/.well-known/openid-configuration`;
261
+ return Response.redirect(url);
262
+ }),
263
+ });
275
264
 
276
265
  if (!opts.cors) {
277
266
  http.route({
@@ -288,20 +277,50 @@ export class BetterAuth<UserId extends string = string> {
288
277
 
289
278
  return;
290
279
  }
291
- const cors = corsRouter(http, {
292
- allowedOrigins: async (request) => {
293
- const trustedOriginsOption =
294
- (await createAuth({} as any).$context).options.trustedOrigins ?? [];
295
- const trustedOrigins = Array.isArray(trustedOriginsOption)
296
- ? trustedOriginsOption
297
- : await trustedOriginsOption(request);
298
- return trustedOrigins.map((origin) =>
280
+
281
+ const trustedOrigins = [
282
+ ...(Array.isArray(betterAuthOptions.trustedOrigins)
283
+ ? betterAuthOptions.trustedOrigins
284
+ : [betterAuthOptions.trustedOrigins]),
285
+ betterAuthOptions.baseURL!,
286
+ ];
287
+ // The crossDomain plugin adds siteUrl to trustedOrigins
288
+ const trustedOriginsFromPlugins =
289
+ betterAuthOptions.plugins?.reduce((acc, plugin) => {
290
+ if (plugin.options?.trustedOrigins) {
291
+ acc.push(...plugin.options.trustedOrigins);
292
+ }
293
+ return acc;
294
+ }, [] as string[]) ?? [];
295
+
296
+ // Reuse trustedOrigins as default for allowedOrigins
297
+ const allowedOrigins = async (request: Request) => {
298
+ return (
299
+ await Promise.all(
300
+ [...trustedOrigins, ...trustedOriginsFromPlugins].map(
301
+ async (origin) => {
302
+ if (!origin) {
303
+ return [];
304
+ }
305
+ if (typeof origin === "function") {
306
+ return origin(request);
307
+ }
308
+ return [origin];
309
+ }
310
+ )
311
+ )
312
+ )
313
+ .flat()
314
+ .map((origin) =>
299
315
  // Strip trailing wildcards, unsupported for allowedOrigins
300
316
  origin.endsWith("*") && origin.length > 1
301
317
  ? origin.slice(0, -1)
302
318
  : origin
303
319
  );
304
- },
320
+ };
321
+
322
+ const cors = corsRouter(http, {
323
+ allowedOrigins,
305
324
  allowCredentials: true,
306
325
  allowedHeaders: ["Content-Type", "Better-Auth-Cookie"],
307
326
  exposedHeaders: ["Set-Better-Auth-Cookie"],