@convex-dev/better-auth 0.6.1 → 0.7.0-alpha.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 (91) hide show
  1. package/README.md +8 -2
  2. package/dist/commonjs/client/adapter.d.ts.map +1 -1
  3. package/dist/commonjs/client/adapter.js +27 -9
  4. package/dist/commonjs/client/adapter.js.map +1 -1
  5. package/dist/commonjs/client/cors.d.ts +9 -4
  6. package/dist/commonjs/client/cors.d.ts.map +1 -1
  7. package/dist/commonjs/client/cors.js +44 -28
  8. package/dist/commonjs/client/cors.js.map +1 -1
  9. package/dist/commonjs/client/index.d.ts +1 -2
  10. package/dist/commonjs/client/index.d.ts.map +1 -1
  11. package/dist/commonjs/client/index.js +57 -61
  12. package/dist/commonjs/client/index.js.map +1 -1
  13. package/dist/commonjs/component/lib.d.ts +58 -0
  14. package/dist/commonjs/component/lib.d.ts.map +1 -1
  15. package/dist/commonjs/component/lib.js +28 -0
  16. package/dist/commonjs/component/lib.js.map +1 -1
  17. package/dist/commonjs/component/schema.d.ts +2 -0
  18. package/dist/commonjs/component/schema.d.ts.map +1 -1
  19. package/dist/commonjs/component/schema.js +3 -1
  20. package/dist/commonjs/component/schema.js.map +1 -1
  21. package/dist/commonjs/component/util.d.ts +6 -0
  22. package/dist/commonjs/component/util.d.ts.map +1 -1
  23. package/dist/commonjs/nextjs/index.d.ts.map +1 -1
  24. package/dist/commonjs/nextjs/index.js +3 -2
  25. package/dist/commonjs/nextjs/index.js.map +1 -1
  26. package/dist/commonjs/plugins/convex/index.d.ts +111 -2
  27. package/dist/commonjs/plugins/convex/index.d.ts.map +1 -1
  28. package/dist/commonjs/plugins/convex/index.js +66 -5
  29. package/dist/commonjs/plugins/convex/index.js.map +1 -1
  30. package/dist/commonjs/react-router/index.d.ts +10 -0
  31. package/dist/commonjs/react-router/index.d.ts.map +1 -0
  32. package/dist/commonjs/react-router/index.js +24 -0
  33. package/dist/commonjs/react-router/index.js.map +1 -0
  34. package/dist/commonjs/react-start/index.d.ts.map +1 -1
  35. package/dist/commonjs/react-start/index.js +2 -2
  36. package/dist/commonjs/react-start/index.js.map +1 -1
  37. package/dist/commonjs/util.d.ts +2 -0
  38. package/dist/commonjs/util.d.ts.map +1 -0
  39. package/dist/commonjs/util.js +8 -0
  40. package/dist/commonjs/util.js.map +1 -0
  41. package/dist/esm/client/adapter.d.ts.map +1 -1
  42. package/dist/esm/client/adapter.js +27 -9
  43. package/dist/esm/client/adapter.js.map +1 -1
  44. package/dist/esm/client/cors.d.ts +9 -4
  45. package/dist/esm/client/cors.d.ts.map +1 -1
  46. package/dist/esm/client/cors.js +44 -28
  47. package/dist/esm/client/cors.js.map +1 -1
  48. package/dist/esm/client/index.d.ts +1 -2
  49. package/dist/esm/client/index.d.ts.map +1 -1
  50. package/dist/esm/client/index.js +57 -61
  51. package/dist/esm/client/index.js.map +1 -1
  52. package/dist/esm/component/lib.d.ts +58 -0
  53. package/dist/esm/component/lib.d.ts.map +1 -1
  54. package/dist/esm/component/lib.js +28 -0
  55. package/dist/esm/component/lib.js.map +1 -1
  56. package/dist/esm/component/schema.d.ts +2 -0
  57. package/dist/esm/component/schema.d.ts.map +1 -1
  58. package/dist/esm/component/schema.js +3 -1
  59. package/dist/esm/component/schema.js.map +1 -1
  60. package/dist/esm/component/util.d.ts +6 -0
  61. package/dist/esm/component/util.d.ts.map +1 -1
  62. package/dist/esm/nextjs/index.d.ts.map +1 -1
  63. package/dist/esm/nextjs/index.js +3 -2
  64. package/dist/esm/nextjs/index.js.map +1 -1
  65. package/dist/esm/plugins/convex/index.d.ts +111 -2
  66. package/dist/esm/plugins/convex/index.d.ts.map +1 -1
  67. package/dist/esm/plugins/convex/index.js +66 -5
  68. package/dist/esm/plugins/convex/index.js.map +1 -1
  69. package/dist/esm/react-router/index.d.ts +10 -0
  70. package/dist/esm/react-router/index.d.ts.map +1 -0
  71. package/dist/esm/react-router/index.js +24 -0
  72. package/dist/esm/react-router/index.js.map +1 -0
  73. package/dist/esm/react-start/index.d.ts.map +1 -1
  74. package/dist/esm/react-start/index.js +2 -2
  75. package/dist/esm/react-start/index.js.map +1 -1
  76. package/dist/esm/util.d.ts +2 -0
  77. package/dist/esm/util.d.ts.map +1 -0
  78. package/dist/esm/util.js +8 -0
  79. package/dist/esm/util.js.map +1 -0
  80. package/package.json +1 -1
  81. package/src/client/adapter.ts +36 -11
  82. package/src/client/cors.ts +60 -38
  83. package/src/client/index.ts +65 -73
  84. package/src/component/_generated/api.d.ts +12 -0
  85. package/src/component/lib.ts +32 -0
  86. package/src/component/schema.ts +3 -1
  87. package/src/nextjs/index.ts +3 -2
  88. package/src/plugins/convex/index.ts +79 -11
  89. package/src/react-router/index.ts +31 -0
  90. package/src/react-start/index.ts +2 -2
  91. package/src/util.ts +7 -0
@@ -1,9 +1,10 @@
1
1
  import { createCookieGetter } from "better-auth/cookies";
2
2
  import { betterFetch } from "@better-fetch/fetch";
3
+ import { JWT_COOKIE_NAME } from "../plugins/convex/index.js";
3
4
  export const getCookieName = async (createAuth) => {
4
5
  const auth = createAuth({});
5
6
  const createCookie = createCookieGetter(auth.options);
6
- const cookie = createCookie("convex_jwt");
7
+ const cookie = createCookie(JWT_COOKIE_NAME);
7
8
  return cookie.name;
8
9
  };
9
10
  export const fetchSession = async (createAuth, request) => {
@@ -15,7 +16,6 @@ export const fetchSession = async (createAuth, request) => {
15
16
  baseURL,
16
17
  headers: {
17
18
  cookie: request.headers.get("cookie") ?? "",
18
- origin: baseURL,
19
19
  },
20
20
  });
21
21
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react-start/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,UAAyE,EACzE,EAAE;IACF,MAAM,IAAI,GAAG,UAAU,CAAC,EAAS,CAAC,CAAC;IACnC,MAAM,YAAY,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAG/B,UAAa,EACb,OAAiB,EACjB,EAAE;IAGF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CACzC,uBAAuB,EACvB;QACE,OAAO;QACP,OAAO,EAAE;YACP,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE;YAC3C,MAAM,EAAE,OAAO;SAChB;KACF,CACF,CAAC;IACF,OAAO;QACL,OAAO;KACR,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,OAAgB,EAChB,IAAiC,EACjC,EAAE;IACF,MAAM,aAAa,GAAG,IAAI,EAAE,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAC9E,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,GAAG,aAAa,GAAG,UAAU,CAAC,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;IAC7E,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,OAAO,EAAE,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react-start/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,UAAyE,EACzE,EAAE;IACF,MAAM,IAAI,GAAG,UAAU,CAAC,EAAS,CAAC,CAAC;IACnC,MAAM,YAAY,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,YAAY,CAAC,eAAe,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAG/B,UAAa,EACb,OAAiB,EACjB,EAAE;IAGF,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,WAAW,CACzC,uBAAuB,EACvB;QACE,OAAO;QACP,OAAO,EAAE;YACP,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE;SAC5C;KACF,CACF,CAAC;IACF,OAAO;QACL,OAAO;KACR,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,OAAgB,EAChB,IAAiC,EACjC,EAAE;IACF,MAAM,aAAa,GAAG,IAAI,EAAE,aAAa,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAC9E,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,GAAG,aAAa,GAAG,UAAU,CAAC,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC;IAC7E,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,OAAO,EAAE,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const requireEnv: (name: string) => string;
2
+ //# sourceMappingURL=util.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/util.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,WAMtC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export const requireEnv = (name) => {
2
+ const value = process.env[name];
3
+ if (value === undefined) {
4
+ throw new Error(`Missing environment variable \`${name}\``);
5
+ }
6
+ return value;
7
+ };
8
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,EAAE;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,IAAI,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "bugs": {
7
7
  "url": "https://github.com/get-convex/better-auth/issues"
8
8
  },
9
- "version": "0.6.1",
9
+ "version": "0.7.0-alpha.0",
10
10
  "license": "Apache-2.0",
11
11
  "keywords": [
12
12
  "convex",
@@ -80,7 +80,7 @@ export const convexAdapter = <
80
80
  limit,
81
81
  }): Promise<any[]> => {
82
82
  if (offset) {
83
- throw new Error("where clause not supported");
83
+ throw new Error("offset not supported");
84
84
  }
85
85
  if (
86
86
  model === "jwks" &&
@@ -90,18 +90,12 @@ export const convexAdapter = <
90
90
  ) {
91
91
  return ctx.runQuery(component.component.lib.getJwks, { limit });
92
92
  }
93
- if (where?.length !== 1 || where[0].operator !== "eq") {
93
+ if (
94
+ where?.length !== 1 ||
95
+ (where[0].operator && where[0].operator !== "eq")
96
+ ) {
94
97
  throw new Error("where clause not supported");
95
98
  }
96
- if (offset) {
97
- throw new Error("offset not supported");
98
- }
99
- if (model === "account" && where[0].field === "userId") {
100
- return ctx.runQuery(component.component.lib.getAccountsByUserId, {
101
- userId: where[0].value as any,
102
- limit,
103
- });
104
- }
105
99
  if (model === "verification" && where[0].field === "identifier") {
106
100
  return ctx.runQuery(
107
101
  component.component.lib.listVerificationsByIdentifier,
@@ -112,6 +106,18 @@ export const convexAdapter = <
112
106
  }
113
107
  );
114
108
  }
109
+ if (model === "account" && where[0].field === "userId" && !sortBy) {
110
+ return ctx.runQuery(component.component.lib.getAccountsByUserId, {
111
+ userId: where[0].value as any,
112
+ limit,
113
+ });
114
+ }
115
+ if (model === "session" && where[0].field === "userId" && !sortBy) {
116
+ return ctx.runQuery(component.component.lib.getSessionsByUserId, {
117
+ userId: where[0].value as any,
118
+ limit,
119
+ });
120
+ }
115
121
  throw new Error("where clause not supported");
116
122
  },
117
123
  count: async ({ where }) => {
@@ -184,6 +190,24 @@ export const convexAdapter = <
184
190
  userId: where[0].value as any,
185
191
  });
186
192
  }
193
+ if (
194
+ model === "session" &&
195
+ where?.length === 2 &&
196
+ where[0].operator === "eq" &&
197
+ where[0].connector === "AND" &&
198
+ where[0].field === "userId" &&
199
+ where[1].operator === "lte" &&
200
+ where[1].field === "expiresAt" &&
201
+ typeof where[1].value === "number"
202
+ ) {
203
+ return ctx.runMutation(
204
+ component.component.lib.deleteExpiredSessions,
205
+ {
206
+ userId: where[0].value as string,
207
+ expiresAt: where[1].value as number,
208
+ }
209
+ );
210
+ }
187
211
  throw new Error("where clause not supported");
188
212
  // return count;
189
213
  },
@@ -208,6 +232,7 @@ export const convexAdapter = <
208
232
  where[0].operator === "eq" &&
209
233
  where[0].connector === "AND" &&
210
234
  where[0].field === "userId" &&
235
+ where[1].operator === "eq" &&
211
236
  where[1].field === "providerId"
212
237
  ) {
213
238
  return ctx.runMutation(
@@ -13,14 +13,16 @@
13
13
  * maintaining proper CORS configuration.
14
14
  */
15
15
  import {
16
- GenericActionCtx,
16
+ type GenericActionCtx,
17
17
  httpActionGeneric,
18
18
  httpRouter,
19
19
  HttpRouter,
20
- PublicHttpAction,
21
- RouteSpec,
22
- RouteSpecWithPath,
23
- RouteSpecWithPathPrefix,
20
+ ROUTABLE_HTTP_METHODS,
21
+ type RoutableMethod,
22
+ type PublicHttpAction,
23
+ type RouteSpec,
24
+ type RouteSpecWithPath,
25
+ type RouteSpecWithPathPrefix,
24
26
  } from "convex/server";
25
27
 
26
28
  export const DEFAULT_EXPOSED_HEADERS = [
@@ -46,7 +48,7 @@ export type CorsConfig = {
46
48
  * - https://example.com
47
49
  * @default ["*"]
48
50
  */
49
- allowedOrigins?: string[];
51
+ allowedOrigins?: string[] | ((req: Request) => Promise<string[]>);
50
52
  /**
51
53
  * An array of allowed headers: what headers are allowed to be sent in
52
54
  * the request.
@@ -68,10 +70,15 @@ export type CorsConfig = {
68
70
  */
69
71
  browserCacheMaxAge?: number;
70
72
  /**
71
- * Whether to log verbose information about CORS requests.
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.
72
79
  * @default false
73
80
  */
74
- verbose?: boolean;
81
+ debug?: boolean;
75
82
  };
76
83
 
77
84
  type RouteSpecWithCors = RouteSpec & CorsConfig;
@@ -219,8 +226,6 @@ export default corsRouter;
219
226
  * to web applications hosted on different domains.
220
227
  */
221
228
 
222
- import { ROUTABLE_HTTP_METHODS, RoutableMethod } from "convex/server";
223
-
224
229
  const SECONDS_IN_A_DAY = 60 * 60 * 24;
225
230
 
226
231
  /**
@@ -240,7 +245,8 @@ const handleCors = ({
240
245
  exposedHeaders = DEFAULT_EXPOSED_HEADERS,
241
246
  allowCredentials = false,
242
247
  browserCacheMaxAge = SECONDS_IN_A_DAY,
243
- verbose = false,
248
+ enforceAllowOrigins = true,
249
+ debug = false,
244
250
  }: {
245
251
  originalHandler?: PublicHttpAction;
246
252
  allowedMethods?: string[];
@@ -279,9 +285,17 @@ const handleCors = ({
279
285
  commonHeaders["Access-Control-Expose-Headers"] = exposedHeaders.join(", ");
280
286
  }
281
287
 
288
+ async function parseAllowedOrigins(request: Request): Promise<string[]> {
289
+ return Array.isArray(allowedOrigins)
290
+ ? allowedOrigins
291
+ : await allowedOrigins(request);
292
+ }
293
+
282
294
  // Helper function to check if origin is allowed (including wildcard subdomain matching)
283
- function isAllowedOrigin(requestOrigin: string): boolean {
284
- return allowedOrigins.some((allowed) => {
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) => {
285
299
  if (allowed === "*") return true;
286
300
  if (allowed === requestOrigin) return true;
287
301
  if (allowed.startsWith("*.")) {
@@ -307,38 +321,38 @@ const handleCors = ({
307
321
  */
308
322
  return httpActionGeneric(
309
323
  async (ctx: GenericActionCtx<any>, request: Request) => {
310
- if (verbose) {
311
- console.log("path", request.url);
312
- console.log("origin", request.headers.get("origin"));
313
- console.log("headers", request.headers);
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
+ });
314
332
  }
315
- const requestOriginRaw =
316
- request.headers.get("Origin") ??
317
- request.headers.get("Referer") ??
318
- request.headers.get("Expo-Origin");
333
+ const requestOrigin = request.headers.get("origin");
334
+ const parsedAllowedOrigins = await parseAllowedOrigins(request);
319
335
 
320
- const requestOrigin =
321
- typeof requestOriginRaw === "string" &&
322
- requestOriginRaw.startsWith("http")
323
- ? new URL(requestOriginRaw).origin
324
- : requestOriginRaw;
336
+ if (debug) {
337
+ console.log("allowed origins", parsedAllowedOrigins);
338
+ }
325
339
 
326
340
  // Handle origin matching
327
341
  let allowOrigins: string | null = null;
328
- if (allowedOrigins.includes("*") && !allowCredentials) {
342
+ if (parsedAllowedOrigins.includes("*") && !allowCredentials) {
329
343
  allowOrigins = "*";
330
344
  } else if (requestOrigin) {
331
345
  // Check if the request origin matches any of the allowed origins
332
346
  // (including wildcard subdomain matching if configured)
333
- if (isAllowedOrigin(requestOrigin)) {
347
+ if (await isAllowedOrigin(request)) {
334
348
  allowOrigins = requestOrigin;
335
349
  }
336
350
  }
337
351
 
338
- if (!allowOrigins) {
352
+ if (enforceAllowOrigins && !allowOrigins) {
339
353
  // Origin not allowed
340
354
  console.error(
341
- `Request from origin ${requestOrigin} blocked, missing from allowed origins (${allowedOrigins.join()})`
355
+ `Request from origin ${requestOrigin} blocked, missing from allowed origins: ${parsedAllowedOrigins.join()}`
342
356
  );
343
357
  return new Response(null, { status: 403 });
344
358
  }
@@ -346,15 +360,19 @@ const handleCors = ({
346
360
  * OPTIONS has no handler and just returns headers
347
361
  */
348
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
+ }
349
373
  return new Response(null, {
350
374
  status: 204,
351
- headers: new Headers({
352
- ...commonHeaders,
353
- "Access-Control-Allow-Origin": allowOrigins,
354
- "Access-Control-Allow-Methods": allowMethods,
355
- "Access-Control-Allow-Headers": allowedHeaders.join(", "),
356
- "Access-Control-Max-Age": browserCacheMaxAge.toString(),
357
- }),
375
+ headers: responseHeaders,
358
376
  });
359
377
  }
360
378
 
@@ -380,7 +398,7 @@ const handleCors = ({
380
398
  * Second, get a copy of the original response's headers
381
399
  */
382
400
  const newHeaders = new Headers(originalResponse.headers);
383
- newHeaders.set("Access-Control-Allow-Origin", allowOrigins);
401
+ newHeaders.set("Access-Control-Allow-Origin", allowOrigins ?? "");
384
402
 
385
403
  /**
386
404
  * Third, add or update our CORS headers
@@ -389,6 +407,10 @@ const handleCors = ({
389
407
  newHeaders.set(key, value);
390
408
  });
391
409
 
410
+ if (debug) {
411
+ console.log("CORS response headers", newHeaders);
412
+ }
413
+
392
414
  /**
393
415
  * Fourth, return the modified Response.
394
416
  * A Response object is immutable, so we create a new one to return here.
@@ -22,6 +22,8 @@ import { betterAuth } from "better-auth";
22
22
  import { omit } from "convex-helpers";
23
23
  import { createCookieGetter } from "better-auth/cookies";
24
24
  import { fetchQuery } from "convex/nextjs";
25
+ import { JWT_COOKIE_NAME } from "../plugins/convex";
26
+ import { requireEnv } from "../util";
25
27
  export { convexAdapter };
26
28
 
27
29
  const createUserFields = omit(schema.tables.user.validator.fields, ["userId"]);
@@ -147,7 +149,7 @@ export class BetterAuth<UserId extends string = string> {
147
149
  ) {
148
150
  const auth = createAuth({} as any);
149
151
  const createCookie = createCookieGetter(auth.options);
150
- const cookie = createCookie("convex_jwt");
152
+ const cookie = createCookie(JWT_COOKIE_NAME);
151
153
  return cookie.name;
152
154
  }
153
155
 
@@ -234,41 +236,10 @@ export class BetterAuth<UserId extends string = string> {
234
236
  registerRoutes(
235
237
  http: HttpRouter,
236
238
  createAuth: (ctx: GenericActionCtx<any>) => ReturnType<typeof betterAuth>,
237
- opts?: {
238
- path?: string;
239
- allowedOrigins?: string[];
240
- }
239
+ opts = { cors: false }
241
240
  ) {
242
- const path = opts?.path ?? "/api/auth";
243
- const options = createAuth({} as any).options;
244
- const trustedOriginsOption: string[] = Array.isArray(options.trustedOrigins)
245
- ? options.trustedOrigins
246
- : [];
247
-
248
- const trustedOrigins = createAuth({} as any).options.plugins?.reduce(
249
- (acc, plugin) => {
250
- if (plugin.options?.trustedOrigins) {
251
- acc.push(...plugin.options.trustedOrigins);
252
- }
253
- return acc;
254
- },
255
- [...trustedOriginsOption, options.baseURL].filter(Boolean) as string[]
256
- );
257
- // Reuse trustedOrigins as default for allowedOrigins
258
- const allowedOrigins =
259
- opts?.allowedOrigins ??
260
- trustedOrigins?.map((origin) =>
261
- // Strip trailing wildcards, unsupported for allowedOrigins
262
- origin.endsWith("*") && origin.length > 1 ? origin.slice(0, -1) : origin
263
- );
264
- const requireEnv = (name: string) => {
265
- const value = process.env[name];
266
- if (value === undefined) {
267
- throw new Error(`Missing environment variable \`${name}\``);
268
- }
269
- return value;
270
- };
271
-
241
+ const betterAuthOptions = createAuth({} as any).options;
242
+ const path = betterAuthOptions.basePath ?? "/api/auth";
272
243
  const authRequestHandler = httpActionGeneric(async (ctx, request) => {
273
244
  const auth = createAuth(ctx);
274
245
  const response = await auth.handler(request);
@@ -278,14 +249,7 @@ export class BetterAuth<UserId extends string = string> {
278
249
  return response;
279
250
  });
280
251
 
281
- const cors = corsRouter(http, {
282
- allowedOrigins,
283
- allowCredentials: true,
284
- allowedHeaders: ["Authorization", "Content-Type", "Better-Auth-Cookie"],
285
- verbose: this.config?.verbose,
286
- exposedHeaders: ["Set-Better-Auth-Cookie"],
287
- });
288
-
252
+ // Redirect root well-known to api well-known
289
253
  http.route({
290
254
  path: "/.well-known/openid-configuration",
291
255
  method: "GET",
@@ -295,40 +259,68 @@ export class BetterAuth<UserId extends string = string> {
295
259
  }),
296
260
  });
297
261
 
298
- http.route({
299
- path: `${path}/convex/.well-known/openid-configuration`,
300
- method: "GET",
301
- handler: authRequestHandler,
302
- });
303
-
304
- http.route({
305
- path: `${path}/convex/jwks`,
306
- method: "GET",
307
- handler: authRequestHandler,
308
- });
262
+ if (!opts.cors) {
263
+ http.route({
264
+ pathPrefix: `${path}/`,
265
+ method: "GET",
266
+ handler: authRequestHandler,
267
+ });
309
268
 
310
- http.route({
311
- pathPrefix: `${path}/callback/`,
312
- method: "GET",
313
- handler: authRequestHandler,
314
- });
269
+ http.route({
270
+ pathPrefix: `${path}/`,
271
+ method: "POST",
272
+ handler: authRequestHandler,
273
+ });
274
+ }
315
275
 
316
- http.route({
317
- path: `${path}/magic-link/verify`,
318
- method: "GET",
319
- handler: authRequestHandler,
320
- });
276
+ const trustedOrigins = [
277
+ ...(Array.isArray(betterAuthOptions.trustedOrigins)
278
+ ? betterAuthOptions.trustedOrigins
279
+ : [betterAuthOptions.trustedOrigins]),
280
+ betterAuthOptions.baseURL!,
281
+ ];
282
+ // The crossDomain plugin adds siteUrl to trustedOrigins
283
+ const trustedOriginsFromPlugins =
284
+ betterAuthOptions.plugins?.reduce((acc, plugin) => {
285
+ if (plugin.options?.trustedOrigins) {
286
+ acc.push(...plugin.options.trustedOrigins);
287
+ }
288
+ return acc;
289
+ }, [] as string[]) ?? [];
321
290
 
322
- http.route({
323
- path: `${path}/verify-email`,
324
- method: "GET",
325
- handler: authRequestHandler,
326
- });
291
+ // Reuse trustedOrigins as default for allowedOrigins
292
+ const allowedOrigins = async (request: Request) => {
293
+ return (
294
+ await Promise.all(
295
+ [...trustedOrigins, ...trustedOriginsFromPlugins].map(
296
+ async (origin) => {
297
+ if (!origin) {
298
+ return [];
299
+ }
300
+ if (typeof origin === "function") {
301
+ return origin(request);
302
+ }
303
+ return [origin];
304
+ }
305
+ )
306
+ )
307
+ )
308
+ .flat()
309
+ .map((origin) =>
310
+ // Strip trailing wildcards, unsupported for allowedOrigins
311
+ origin.endsWith("*") && origin.length > 1
312
+ ? origin.slice(0, -1)
313
+ : origin
314
+ );
315
+ };
327
316
 
328
- http.route({
329
- pathPrefix: `${path}/reset-password/`,
330
- method: "GET",
331
- handler: authRequestHandler,
317
+ const cors = corsRouter(http, {
318
+ allowedOrigins,
319
+ allowCredentials: true,
320
+ allowedHeaders: ["Content-Type", "Better-Auth-Cookie"],
321
+ exposedHeaders: ["Set-Better-Auth-Cookie"],
322
+ debug: this.config?.verbose,
323
+ enforceAllowOrigins: false,
332
324
  });
333
325
 
334
326
  cors.route({
@@ -130,6 +130,12 @@ export type Mounts = {
130
130
  },
131
131
  any
132
132
  >;
133
+ deleteExpiredSessions: FunctionReference<
134
+ "mutation",
135
+ "public",
136
+ { expiresAt: number; userId: string },
137
+ any
138
+ >;
133
139
  deleteOldVerifications: FunctionReference<
134
140
  "action",
135
141
  "public",
@@ -188,6 +194,12 @@ export type Mounts = {
188
194
  >;
189
195
  getCurrentSession: FunctionReference<"query", "public", {}, any>;
190
196
  getJwks: FunctionReference<"query", "public", { limit?: number }, any>;
197
+ getSessionsByUserId: FunctionReference<
198
+ "query",
199
+ "public",
200
+ { limit?: number; userId: string },
201
+ any
202
+ >;
191
203
  listVerificationsByIdentifier: FunctionReference<
192
204
  "query",
193
205
  "public",
@@ -181,6 +181,19 @@ export const getAccountsByUserId = query({
181
181
  },
182
182
  });
183
183
 
184
+ export const getSessionsByUserId = query({
185
+ args: { userId: v.string(), limit: v.optional(v.number()) },
186
+ handler: async (ctx, args) => {
187
+ const query = ctx.db
188
+ .query("session")
189
+ .withIndex("userId", (q) => q.eq("userId", args.userId));
190
+ const docs = args.limit
191
+ ? await query.take(args.limit)
192
+ : await query.collect();
193
+ return docs.map((doc) => transformOutput(doc, "session"));
194
+ },
195
+ });
196
+
184
197
  export const getJwks = query({
185
198
  args: {
186
199
  limit: v.optional(v.number()),
@@ -276,6 +289,25 @@ export const deleteOldVerifications = action({
276
289
  },
277
290
  });
278
291
 
292
+ export const deleteExpiredSessions = mutation({
293
+ args: {
294
+ userId: v.string(),
295
+ expiresAt: v.number(),
296
+ },
297
+ handler: async (ctx, args) => {
298
+ const docs = await ctx.db
299
+ .query("session")
300
+ .withIndex("userId_expiresAt", (q) =>
301
+ q.eq("userId", args.userId).lt("expiresAt", args.expiresAt)
302
+ )
303
+ .collect();
304
+ await asyncMap(docs, async (doc) => {
305
+ await ctx.db.delete(doc._id);
306
+ });
307
+ return docs.length;
308
+ },
309
+ });
310
+
279
311
  export const deleteAllForUserPage = mutation({
280
312
  args: {
281
313
  table: v.string(),
@@ -25,7 +25,9 @@ const schema = defineSchema({
25
25
  userId: v.string(),
26
26
  })
27
27
  .index("token", ["token"])
28
- .index("userId", ["userId"]),
28
+ .index("userId", ["userId"])
29
+ .index("expiresAt", ["expiresAt"])
30
+ .index("userId_expiresAt", ["userId", "expiresAt"]),
29
31
 
30
32
  account: defineTable({
31
33
  accountId: v.string(),
@@ -1,6 +1,7 @@
1
1
  import { betterAuth } from "better-auth";
2
2
  import { createCookieGetter } from "better-auth/cookies";
3
3
  import { GenericActionCtx } from "convex/server";
4
+ import { JWT_COOKIE_NAME } from "../plugins/convex";
4
5
 
5
6
  export const getToken = async (
6
7
  createAuth: (ctx: GenericActionCtx<any>) => ReturnType<typeof betterAuth>
@@ -9,9 +10,9 @@ export const getToken = async (
9
10
  const cookieStore = await cookies();
10
11
  const auth = createAuth({} as any);
11
12
  const createCookie = createCookieGetter(auth.options);
12
- const cookie = createCookie("convex_jwt");
13
+ const cookie = createCookie(JWT_COOKIE_NAME);
13
14
  const token = cookieStore.get(cookie.name);
14
- return typeof token === "string" ? token : token?.value;
15
+ return token?.value;
15
16
  };
16
17
 
17
18
  const handler = (request: Request, opts?: { convexSiteUrl?: string }) => {