better-auth 1.6.1 → 1.6.3

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 (57) hide show
  1. package/dist/api/index.d.mts +0 -2
  2. package/dist/api/routes/account.mjs +1 -1
  3. package/dist/api/routes/callback.mjs +2 -2
  4. package/dist/api/routes/email-verification.mjs +1 -1
  5. package/dist/api/routes/password.mjs +1 -1
  6. package/dist/api/routes/session.d.mts +0 -1
  7. package/dist/api/routes/session.mjs +3 -4
  8. package/dist/api/routes/sign-in.mjs +1 -1
  9. package/dist/api/to-auth-endpoints.mjs +27 -3
  10. package/dist/auth/base.mjs +5 -24
  11. package/dist/client/plugins/index.d.mts +2 -2
  12. package/dist/client/query.mjs +4 -4
  13. package/dist/context/create-context.mjs +2 -2
  14. package/dist/context/helpers.mjs +61 -3
  15. package/dist/cookies/index.mjs +3 -3
  16. package/dist/crypto/index.mjs +2 -2
  17. package/dist/db/index.mjs +1 -1
  18. package/dist/db/internal-adapter.mjs +1 -1
  19. package/dist/index.d.mts +2 -2
  20. package/dist/index.mjs +2 -2
  21. package/dist/integrations/next-js.mjs +21 -12
  22. package/dist/oauth2/state.d.mts +1 -0
  23. package/dist/package.mjs +1 -1
  24. package/dist/plugins/admin/admin.d.mts +1 -3
  25. package/dist/plugins/admin/routes.mjs +2 -8
  26. package/dist/plugins/device-authorization/routes.mjs +1 -1
  27. package/dist/plugins/email-otp/routes.mjs +1 -1
  28. package/dist/plugins/index.d.mts +2 -2
  29. package/dist/plugins/jwt/utils.mjs +1 -1
  30. package/dist/plugins/mcp/index.mjs +20 -8
  31. package/dist/plugins/oauth-proxy/index.mjs +5 -1
  32. package/dist/plugins/oidc-provider/index.mjs +2 -2
  33. package/dist/plugins/organization/organization.d.mts +1 -3
  34. package/dist/plugins/organization/organization.mjs +1 -7
  35. package/dist/plugins/organization/routes/crud-invites.mjs +1 -1
  36. package/dist/plugins/organization/routes/crud-org.mjs +1 -1
  37. package/dist/plugins/organization/routes/crud-team.mjs +1 -1
  38. package/dist/plugins/phone-number/routes.mjs +1 -1
  39. package/dist/plugins/two-factor/backup-codes/index.d.mts +2 -1
  40. package/dist/plugins/two-factor/backup-codes/index.mjs +12 -17
  41. package/dist/plugins/two-factor/client.d.mts +12 -2
  42. package/dist/plugins/two-factor/client.mjs +1 -1
  43. package/dist/plugins/two-factor/index.d.mts +9 -2
  44. package/dist/plugins/two-factor/index.mjs +35 -3
  45. package/dist/plugins/two-factor/otp/index.mjs +1 -1
  46. package/dist/plugins/two-factor/schema.d.mts +6 -0
  47. package/dist/plugins/two-factor/schema.mjs +6 -0
  48. package/dist/plugins/two-factor/totp/index.mjs +19 -10
  49. package/dist/plugins/two-factor/types.d.mts +1 -1
  50. package/dist/state.d.mts +6 -0
  51. package/dist/state.mjs +18 -2
  52. package/dist/test-utils/test-instance.d.mts +0 -6
  53. package/dist/test-utils/test-instance.mjs +7 -1
  54. package/dist/utils/index.d.mts +1 -1
  55. package/dist/utils/url.d.mts +22 -15
  56. package/dist/utils/url.mjs +54 -28
  57. package/package.json +9 -9
@@ -10,22 +10,29 @@ declare function getHost(url: string): string | null;
10
10
  */
11
11
  declare function isDynamicBaseURLConfig(config: BaseURLConfig | undefined): config is DynamicBaseURLConfig;
12
12
  /**
13
- * Extracts the host from the request headers.
14
- * Tries x-forwarded-host first (for proxy setups), then falls back to host header.
13
+ * Check if a value is a `Request`
14
+ * - `instanceof`: works for native Request instances
15
+ * - `toString`: handles where instanceof check fails but the object is still a
16
+ * valid Request (e.g. cross-realm, polyfills). Paired with a shape check so
17
+ * an object that only spoofs `Symbol.toStringTag` without the real shape is
18
+ * rejected before downstream code tries to read `.headers` / `.url`.
15
19
  *
16
- * @param request The incoming request
17
- * @returns The host string or null if not found
20
+ * @param value The value to check
21
+ * @returns `true` if the value is a Request instance
18
22
  */
19
- declare function getHostFromRequest(request: Request): string | null;
23
+ declare function isRequestLike(value: unknown): value is Request;
20
24
  /**
21
- * Extracts the protocol from the request headers.
22
- * Tries x-forwarded-proto first (for proxy setups), then infers from request URL.
23
- *
24
- * @param request The incoming request
25
- * @param configProtocol Protocol override from config
26
- * @returns The protocol ("http" or "https")
25
+ * Extracts the host from a `Request` or `Headers`.
26
+ * Honors `x-forwarded-host` only when `trustedProxyHeaders` is enabled,
27
+ * then falls back to the `host` header and finally the request URL.
28
+ */
29
+ declare function getHostFromSource(source: Request | Headers, trustedProxyHeaders?: boolean): string | null;
30
+ /**
31
+ * Extracts the protocol from a `Request` or `Headers`.
32
+ * Honors `x-forwarded-proto` only when `trustedProxyHeaders` is enabled,
33
+ * then falls back to the request URL, then to "https".
27
34
  */
28
- declare function getProtocolFromRequest(request: Request, configProtocol?: "http" | "https" | "auto" | undefined): "http" | "https";
35
+ declare function getProtocolFromSource(source: Request | Headers, configProtocol?: "http" | "https" | "auto" | undefined, trustedProxyHeaders?: boolean): "http" | "https";
29
36
  /**
30
37
  * Matches a hostname against a host pattern.
31
38
  * Supports wildcard patterns like `*.vercel.app` or `preview-*.myapp.com`.
@@ -53,7 +60,7 @@ declare const matchesHostPattern: (host: string, pattern: string) => boolean;
53
60
  * @returns The resolved base URL with path
54
61
  * @throws BetterAuthError if host is not in allowedHosts and no fallback is set
55
62
  */
56
- declare function resolveDynamicBaseURL(config: DynamicBaseURLConfig, request: Request, basePath: string): string;
63
+ declare function resolveDynamicBaseURL(config: DynamicBaseURLConfig, source: Request | Headers, basePath: string, trustedProxyHeaders?: boolean): string;
57
64
  /**
58
65
  * Resolves the base URL from any config type (static string or dynamic object).
59
66
  * This is the main entry point for base URL resolution.
@@ -65,6 +72,6 @@ declare function resolveDynamicBaseURL(config: DynamicBaseURLConfig, request: Re
65
72
  * @param trustedProxyHeaders Whether to trust proxy headers (for legacy behavior)
66
73
  * @returns The resolved base URL with path
67
74
  */
68
- declare function resolveBaseURL(config: BaseURLConfig | undefined, basePath: string, request?: Request, loadEnv?: boolean, trustedProxyHeaders?: boolean): string | undefined;
75
+ declare function resolveBaseURL(config: BaseURLConfig | undefined, basePath: string, source?: Request | Headers, loadEnv?: boolean, trustedProxyHeaders?: boolean): string | undefined;
69
76
  //#endregion
70
- export { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
77
+ export { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
@@ -93,41 +93,66 @@ function isDynamicBaseURLConfig(config) {
93
93
  return typeof config === "object" && config !== null && "allowedHosts" in config && Array.isArray(config.allowedHosts);
94
94
  }
95
95
  /**
96
- * Extracts the host from the request headers.
97
- * Tries x-forwarded-host first (for proxy setups), then falls back to host header.
96
+ * Check if a value is a `Request`
97
+ * - `instanceof`: works for native Request instances
98
+ * - `toString`: handles where instanceof check fails but the object is still a
99
+ * valid Request (e.g. cross-realm, polyfills). Paired with a shape check so
100
+ * an object that only spoofs `Symbol.toStringTag` without the real shape is
101
+ * rejected before downstream code tries to read `.headers` / `.url`.
98
102
  *
99
- * @param request The incoming request
100
- * @returns The host string or null if not found
103
+ * @param value The value to check
104
+ * @returns `true` if the value is a Request instance
105
+ */
106
+ function isRequestLike(value) {
107
+ if (value instanceof Request) return true;
108
+ if (typeof value !== "object" || value === null || Object.prototype.toString.call(value) !== "[object Request]") return false;
109
+ const v = value;
110
+ return typeof v.url === "string" && typeof v.headers === "object" && v.headers !== null && typeof v.headers.get === "function";
111
+ }
112
+ /**
113
+ * Extracts the host from a `Request` or `Headers`.
114
+ * Honors `x-forwarded-host` only when `trustedProxyHeaders` is enabled,
115
+ * then falls back to the `host` header and finally the request URL.
101
116
  */
102
- function getHostFromRequest(request) {
103
- const forwardedHost = request.headers.get("x-forwarded-host");
104
- if (forwardedHost && validateProxyHeader(forwardedHost, "host")) return forwardedHost;
105
- const host = request.headers.get("host");
117
+ function getHostFromSource(source, trustedProxyHeaders) {
118
+ const headers = isRequestLike(source) ? source.headers : source;
119
+ if (trustedProxyHeaders) {
120
+ const forwardedHost = headers.get("x-forwarded-host");
121
+ if (forwardedHost && validateProxyHeader(forwardedHost, "host")) return forwardedHost;
122
+ }
123
+ const host = headers.get("host");
106
124
  if (host && validateProxyHeader(host, "host")) return host;
107
- try {
108
- return new URL(request.url).host;
125
+ if (isRequestLike(source)) try {
126
+ return new URL(source.url).host;
109
127
  } catch {
110
128
  return null;
111
129
  }
130
+ return null;
112
131
  }
113
132
  /**
114
- * Extracts the protocol from the request headers.
115
- * Tries x-forwarded-proto first (for proxy setups), then infers from request URL.
116
- *
117
- * @param request The incoming request
118
- * @param configProtocol Protocol override from config
119
- * @returns The protocol ("http" or "https")
133
+ * Extracts the protocol from a `Request` or `Headers`.
134
+ * Honors `x-forwarded-proto` only when `trustedProxyHeaders` is enabled,
135
+ * then falls back to the request URL, then to "https".
120
136
  */
121
- function getProtocolFromRequest(request, configProtocol) {
137
+ function getProtocolFromSource(source, configProtocol, trustedProxyHeaders) {
122
138
  if (configProtocol === "http" || configProtocol === "https") return configProtocol;
123
- const forwardedProto = request.headers.get("x-forwarded-proto");
124
- if (forwardedProto && validateProxyHeader(forwardedProto, "proto")) return forwardedProto;
125
- try {
126
- const url = new URL(request.url);
139
+ const headers = isRequestLike(source) ? source.headers : source;
140
+ if (trustedProxyHeaders) {
141
+ const forwardedProto = headers.get("x-forwarded-proto");
142
+ if (forwardedProto && validateProxyHeader(forwardedProto, "proto")) return forwardedProto;
143
+ }
144
+ if (isRequestLike(source)) try {
145
+ const url = new URL(source.url);
127
146
  if (url.protocol === "http:" || url.protocol === "https:") return url.protocol.slice(0, -1);
128
147
  } catch {}
148
+ const host = getHostFromSource(source, trustedProxyHeaders);
149
+ if (host && isLoopbackHost(host)) return "http";
129
150
  return "https";
130
151
  }
152
+ function isLoopbackHost(host) {
153
+ const h = host.toLowerCase();
154
+ return h === "localhost" || h.startsWith("localhost:") || h === "127.0.0.1" || h.startsWith("127.0.0.1:") || h === "[::1]" || h.startsWith("[::1]:") || h === "0.0.0.0" || h.startsWith("0.0.0.0:");
155
+ }
131
156
  /**
132
157
  * Matches a hostname against a host pattern.
133
158
  * Supports wildcard patterns like `*.vercel.app` or `preview-*.myapp.com`.
@@ -161,13 +186,13 @@ const matchesHostPattern = (host, pattern) => {
161
186
  * @returns The resolved base URL with path
162
187
  * @throws BetterAuthError if host is not in allowedHosts and no fallback is set
163
188
  */
164
- function resolveDynamicBaseURL(config, request, basePath) {
165
- const host = getHostFromRequest(request);
189
+ function resolveDynamicBaseURL(config, source, basePath, trustedProxyHeaders) {
190
+ const host = getHostFromSource(source, trustedProxyHeaders);
166
191
  if (!host) {
167
192
  if (config.fallback) return withPath(config.fallback, basePath);
168
193
  throw new BetterAuthError("Could not determine host from request headers. Please provide a fallback URL in your baseURL config.");
169
194
  }
170
- if (config.allowedHosts.some((pattern) => matchesHostPattern(host, pattern))) return withPath(`${getProtocolFromRequest(request, config.protocol)}://${host}`, basePath);
195
+ if (config.allowedHosts.some((pattern) => matchesHostPattern(host, pattern))) return withPath(`${getProtocolFromSource(source, config.protocol, trustedProxyHeaders)}://${host}`, basePath);
171
196
  if (config.fallback) return withPath(config.fallback, basePath);
172
197
  throw new BetterAuthError(`Host "${host}" is not in the allowed hosts list. Allowed hosts: ${config.allowedHosts.join(", ")}. Add this host to your allowedHosts config or provide a fallback URL.`);
173
198
  }
@@ -182,14 +207,15 @@ function resolveDynamicBaseURL(config, request, basePath) {
182
207
  * @param trustedProxyHeaders Whether to trust proxy headers (for legacy behavior)
183
208
  * @returns The resolved base URL with path
184
209
  */
185
- function resolveBaseURL(config, basePath, request, loadEnv, trustedProxyHeaders) {
210
+ function resolveBaseURL(config, basePath, source, loadEnv, trustedProxyHeaders) {
186
211
  if (isDynamicBaseURLConfig(config)) {
187
- if (request) return resolveDynamicBaseURL(config, request, basePath);
212
+ if (source) return resolveDynamicBaseURL(config, source, basePath, trustedProxyHeaders);
188
213
  if (config.fallback) return withPath(config.fallback, basePath);
189
- return getBaseURL(void 0, basePath, request, loadEnv, trustedProxyHeaders);
214
+ return getBaseURL(void 0, basePath, void 0, loadEnv, trustedProxyHeaders);
190
215
  }
216
+ const request = isRequestLike(source) ? source : void 0;
191
217
  if (typeof config === "string") return getBaseURL(config, basePath, request, loadEnv, trustedProxyHeaders);
192
218
  return getBaseURL(void 0, basePath, request, loadEnv, trustedProxyHeaders);
193
219
  }
194
220
  //#endregion
195
- export { getBaseURL, getHost, getHostFromRequest, getOrigin, getProtocol, getProtocolFromRequest, isDynamicBaseURLConfig, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
221
+ export { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-auth",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "The most comprehensive authentication framework for TypeScript.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -489,17 +489,17 @@
489
489
  "kysely": "^0.28.14",
490
490
  "nanostores": "^1.1.1",
491
491
  "zod": "^4.3.6",
492
- "@better-auth/core": "1.6.1",
493
- "@better-auth/drizzle-adapter": "1.6.1",
494
- "@better-auth/kysely-adapter": "1.6.1",
495
- "@better-auth/memory-adapter": "1.6.1",
496
- "@better-auth/mongo-adapter": "1.6.1",
497
- "@better-auth/prisma-adapter": "1.6.1",
498
- "@better-auth/telemetry": "1.6.1"
492
+ "@better-auth/core": "1.6.3",
493
+ "@better-auth/drizzle-adapter": "1.6.3",
494
+ "@better-auth/kysely-adapter": "1.6.3",
495
+ "@better-auth/memory-adapter": "1.6.3",
496
+ "@better-auth/mongo-adapter": "1.6.3",
497
+ "@better-auth/prisma-adapter": "1.6.3",
498
+ "@better-auth/telemetry": "1.6.3"
499
499
  },
500
500
  "devDependencies": {
501
501
  "@lynx-js/react": "^0.116.3",
502
- "@sveltejs/kit": "^2.53.3",
502
+ "@sveltejs/kit": "^2.57.1",
503
503
  "@tanstack/react-start": "^1.163.2",
504
504
  "@tanstack/solid-start": "^1.163.2",
505
505
  "@types/bun": "^1.3.9",