@better-auth/core 1.7.0-beta.5 → 1.7.0-beta.7

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 (87) hide show
  1. package/dist/api/index.d.mts +44 -1
  2. package/dist/api/index.mjs +40 -1
  3. package/dist/context/global.mjs +1 -1
  4. package/dist/context/transaction.d.mts +7 -4
  5. package/dist/context/transaction.mjs +6 -3
  6. package/dist/db/adapter/factory.mjs +57 -31
  7. package/dist/db/adapter/index.d.mts +54 -10
  8. package/dist/db/adapter/types.d.mts +1 -1
  9. package/dist/db/type.d.mts +12 -7
  10. package/dist/instrumentation/tracer.mjs +1 -1
  11. package/dist/oauth2/create-authorization-url.d.mts +3 -1
  12. package/dist/oauth2/create-authorization-url.mjs +3 -1
  13. package/dist/oauth2/dpop.d.mts +142 -0
  14. package/dist/oauth2/dpop.mjs +246 -0
  15. package/dist/oauth2/index.d.mts +4 -3
  16. package/dist/oauth2/index.mjs +3 -2
  17. package/dist/oauth2/oauth-provider.d.mts +37 -3
  18. package/dist/oauth2/refresh-access-token.mjs +15 -1
  19. package/dist/oauth2/verify.d.mts +74 -15
  20. package/dist/oauth2/verify.mjs +172 -20
  21. package/dist/social-providers/apple.d.mts +2 -0
  22. package/dist/social-providers/atlassian.d.mts +2 -0
  23. package/dist/social-providers/cognito.d.mts +2 -0
  24. package/dist/social-providers/discord.d.mts +2 -0
  25. package/dist/social-providers/dropbox.d.mts +2 -0
  26. package/dist/social-providers/facebook.d.mts +2 -0
  27. package/dist/social-providers/figma.d.mts +2 -0
  28. package/dist/social-providers/github.d.mts +2 -0
  29. package/dist/social-providers/gitlab.d.mts +2 -0
  30. package/dist/social-providers/google.d.mts +2 -0
  31. package/dist/social-providers/huggingface.d.mts +2 -0
  32. package/dist/social-providers/index.d.mts +71 -0
  33. package/dist/social-providers/kakao.d.mts +2 -0
  34. package/dist/social-providers/kick.d.mts +2 -0
  35. package/dist/social-providers/line.d.mts +2 -0
  36. package/dist/social-providers/linear.d.mts +2 -0
  37. package/dist/social-providers/linkedin.d.mts +2 -0
  38. package/dist/social-providers/microsoft-entra-id.d.mts +12 -0
  39. package/dist/social-providers/microsoft-entra-id.mjs +17 -2
  40. package/dist/social-providers/naver.d.mts +2 -0
  41. package/dist/social-providers/notion.d.mts +2 -0
  42. package/dist/social-providers/paybin.d.mts +2 -0
  43. package/dist/social-providers/paypal.d.mts +2 -0
  44. package/dist/social-providers/polar.d.mts +2 -0
  45. package/dist/social-providers/railway.d.mts +2 -0
  46. package/dist/social-providers/reddit.d.mts +2 -0
  47. package/dist/social-providers/reddit.mjs +1 -1
  48. package/dist/social-providers/roblox.d.mts +2 -0
  49. package/dist/social-providers/salesforce.d.mts +2 -0
  50. package/dist/social-providers/slack.d.mts +2 -0
  51. package/dist/social-providers/spotify.d.mts +2 -0
  52. package/dist/social-providers/tiktok.d.mts +2 -0
  53. package/dist/social-providers/twitch.d.mts +2 -0
  54. package/dist/social-providers/twitter.d.mts +2 -0
  55. package/dist/social-providers/vercel.d.mts +2 -0
  56. package/dist/social-providers/vk.d.mts +2 -0
  57. package/dist/social-providers/wechat.d.mts +2 -0
  58. package/dist/social-providers/wechat.mjs +1 -1
  59. package/dist/social-providers/zoom.d.mts +2 -0
  60. package/dist/types/context.d.mts +17 -0
  61. package/dist/types/init-options.d.mts +45 -5
  62. package/dist/types/plugin-client.d.mts +12 -2
  63. package/dist/utils/host.d.mts +1 -1
  64. package/dist/utils/host.mjs +7 -0
  65. package/dist/utils/url.mjs +4 -3
  66. package/package.json +5 -5
  67. package/src/api/index.ts +82 -0
  68. package/src/context/transaction.ts +45 -12
  69. package/src/db/adapter/factory.ts +127 -72
  70. package/src/db/adapter/index.ts +54 -9
  71. package/src/db/adapter/types.ts +1 -0
  72. package/src/db/type.ts +12 -7
  73. package/src/oauth2/create-authorization-url.ts +4 -0
  74. package/src/oauth2/dpop.ts +568 -0
  75. package/src/oauth2/index.ts +45 -1
  76. package/src/oauth2/oauth-provider.ts +40 -2
  77. package/src/oauth2/refresh-access-token.ts +27 -3
  78. package/src/oauth2/verify-id-token.ts +2 -0
  79. package/src/oauth2/verify.ts +329 -66
  80. package/src/social-providers/microsoft-entra-id.ts +44 -1
  81. package/src/social-providers/reddit.ts +5 -1
  82. package/src/social-providers/wechat.ts +8 -1
  83. package/src/types/context.ts +18 -0
  84. package/src/types/init-options.ts +40 -8
  85. package/src/types/plugin-client.ts +16 -2
  86. package/src/utils/host.ts +25 -1
  87. package/src/utils/url.ts +10 -4
@@ -14,7 +14,6 @@ import type {
14
14
  Account,
15
15
  DBFieldAttribute,
16
16
  ModelNames,
17
- RateLimit,
18
17
  SecondaryStorage,
19
18
  Session,
20
19
  User,
@@ -187,12 +186,27 @@ export type DynamicBaseURLConfig = {
187
186
  export type BaseURLConfig = string | DynamicBaseURLConfig;
188
187
 
189
188
  export interface BetterAuthRateLimitStorage {
190
- get: (key: string) => Promise<RateLimit | null | undefined>;
191
- set: (
189
+ /**
190
+ * Atomically records one request against `key` within the rolling `window`
191
+ * (in seconds) and reports whether it is allowed.
192
+ *
193
+ * When `allowed` is true the count was incremented within the active window,
194
+ * or the window had elapsed and was reset to start at 1. When `allowed` is
195
+ * false the limit was already reached and `retryAfter` is the number of
196
+ * seconds until the window frees up.
197
+ *
198
+ * Performing the check and the increment in a single step closes the
199
+ * concurrent-bypass gap of the separate `get`/`set` path: N simultaneous
200
+ * requests can no longer all pass a stale read before any increment lands.
201
+ *
202
+ * Custom storages must implement this operation directly. Better Auth no
203
+ * longer accepts separate `get`/`set` rate-limit storage because that shape
204
+ * cannot enforce a distributed limit under concurrent requests.
205
+ */
206
+ consume: (
192
207
  key: string,
193
- value: RateLimit,
194
- update?: boolean | undefined,
195
- ) => Promise<void>;
208
+ rule: { window: number; max: number },
209
+ ) => Promise<{ allowed: boolean; retryAfter: number | null }>;
196
210
  }
197
211
 
198
212
  export type BetterAuthRateLimitRule = {
@@ -1045,6 +1059,20 @@ export type BetterAuthOptions = {
1045
1059
  * @default "compact"
1046
1060
  */
1047
1061
  strategy?: "compact" | "jwt" | "jwe";
1062
+ /**
1063
+ * JWT-specific configuration for `strategy: "jwt"`.
1064
+ */
1065
+ jwt?: {
1066
+ /**
1067
+ * Which signing key is used for cookie-cache JWTs.
1068
+ *
1069
+ * - `"secret"`: uses the Better Auth secret with HS256.
1070
+ * - `"jwt-plugin"`: uses the installed `jwt()` plugin's asymmetric signing keys.
1071
+ *
1072
+ * @default "secret"
1073
+ */
1074
+ signingKey?: "secret" | "jwt-plugin";
1075
+ };
1048
1076
  /**
1049
1077
  * Controls stateless cookie cache refresh behavior.
1050
1078
  *
@@ -1253,9 +1281,13 @@ export type BetterAuthOptions = {
1253
1281
  */
1254
1282
  storeStateStrategy?: "database" | "cookie";
1255
1283
  /**
1256
- * Store account data after oauth flow on a cookie
1284
+ * Store provider account data after an OAuth flow in an encrypted
1285
+ * cookie. This includes OAuth token material such as access tokens,
1286
+ * refresh tokens, ID tokens, scopes, and token expiry.
1257
1287
  *
1258
- * This is useful for database-less flow
1288
+ * This is useful for database-less flows, but large provider tokens can
1289
+ * still hit browser or proxy cookie/header limits even though Better Auth
1290
+ * chunks oversized account cookies.
1259
1291
  *
1260
1292
  * @default false
1261
1293
  *
@@ -6,7 +6,21 @@ import type {
6
6
  import type { Atom, WritableAtom } from "nanostores";
7
7
  import type { LiteralString } from "./helper";
8
8
  import type { BetterAuthOptions } from "./init-options";
9
- import type { BetterAuthPlugin } from "./plugin";
9
+
10
+ type InferableServerPlugin = {
11
+ id?: LiteralString | undefined;
12
+ endpoints?: Record<string, unknown> | undefined;
13
+ schema?: Record<string, { fields: Record<string, unknown> }> | undefined;
14
+ $ERROR_CODES?:
15
+ | Record<
16
+ string,
17
+ {
18
+ readonly code: string;
19
+ message: string;
20
+ }
21
+ >
22
+ | undefined;
23
+ };
10
24
 
11
25
  export interface ClientStore {
12
26
  notify: (signal: string) => void;
@@ -84,7 +98,7 @@ export interface BetterAuthClientPlugin {
84
98
  * only used for type inference. don't pass the
85
99
  * actual plugin
86
100
  */
87
- $InferServerPlugin?: BetterAuthPlugin | undefined;
101
+ $InferServerPlugin?: InferableServerPlugin | undefined;
88
102
  /**
89
103
  * Custom actions
90
104
  */
package/src/utils/host.ts CHANGED
@@ -49,7 +49,7 @@ export type HostKind =
49
49
  | "multicast"
50
50
  /** IPv4 limited broadcast `255.255.255.255`. */
51
51
  | "broadcast"
52
- /** Other RFC 6890 special-purpose ranges (0/8, 192.0.0/24, 240/4, 2001::/32, etc.). */
52
+ /** Other RFC 6890 special-purpose ranges (0.0.0.0/8, 192.0.0.0/24, 192.88.99.0/24, 240.0.0.0/4, 2001::/32, etc.). */
53
53
  | "reserved"
54
54
  /** Cloud metadata service FQDN (e.g. `metadata.google.internal`). */
55
55
  | "cloudMetadata"
@@ -183,6 +183,8 @@ function classifyIPv4(ip: string): HostKind {
183
183
  if (inIPv4Range(n, ipv4ToUint32("224.0.0.0"), 4)) return "multicast";
184
184
  if (inIPv4Range(n, ipv4ToUint32("0.0.0.0"), 8)) return "reserved";
185
185
  if (inIPv4Range(n, ipv4ToUint32("192.0.0.0"), 24)) return "reserved";
186
+ // 6to4 relay anycast (RFC 7526, deprecated), not globally reachable.
187
+ if (inIPv4Range(n, ipv4ToUint32("192.88.99.0"), 24)) return "reserved";
186
188
  if (inIPv4Range(n, ipv4ToUint32("240.0.0.0"), 4)) return "reserved";
187
189
 
188
190
  return "public";
@@ -231,10 +233,16 @@ function classifyIPv6(expanded: string): HostKind {
231
233
 
232
234
  if (firstByte === 0xff) return "multicast";
233
235
  if (firstByte === 0xfe && (secondByte & 0xc0) === 0x80) return "linkLocal";
236
+ // fec0::/10 — deprecated site-local (RFC 3879), not globally reachable.
237
+ if (firstByte === 0xfe && (secondByte & 0xc0) === 0xc0) return "reserved";
234
238
  if ((firstByte & 0xfe) === 0xfc) return "private";
235
239
 
236
240
  if (expanded.startsWith("2001:0db8:")) return "documentation";
237
241
 
242
+ // 2001:2::/48 — Benchmarking (RFC 5180). A specific non-globally-reachable
243
+ // block inside the otherwise-mixed 2001::/23 protocol-assignments space.
244
+ if (expanded.startsWith("2001:0002:0000:")) return "benchmarking";
245
+
238
246
  if (expanded.startsWith("2002:")) {
239
247
  const embedded = extractEmbeddedIPv4(expanded, 1);
240
248
  if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
@@ -247,6 +255,10 @@ function classifyIPv6(expanded: string): HostKind {
247
255
  return "reserved";
248
256
  }
249
257
 
258
+ // 64:ff9b:1::/48 — Local-Use IPv4/IPv6 Translation (RFC 8215). Distinct from
259
+ // the well-known NAT64 /96 prefix above and not globally reachable.
260
+ if (expanded.startsWith("0064:ff9b:0001:")) return "reserved";
261
+
250
262
  if (expanded.startsWith("2001:0000:")) {
251
263
  const embedded = extractEmbeddedIPv4(expanded, 6, { xor: true });
252
264
  if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
@@ -255,6 +267,18 @@ function classifyIPv6(expanded: string): HostKind {
255
267
 
256
268
  if (expanded.startsWith("0100:0000:0000:0000:")) return "reserved";
257
269
 
270
+ // 3fff::/20 — Documentation (RFC 9637). The /20 fixes the first 16 bits to
271
+ // `3fff` and the next nibble to 0, so only `3fff:0xxx` is in range.
272
+ if (expanded.startsWith("3fff:0")) return "documentation";
273
+
274
+ // 5f00::/16 — SRv6 SIDs (RFC 9602), not globally reachable.
275
+ if (expanded.startsWith("5f00:")) return "reserved";
276
+
277
+ // ::/96 — deprecated IPv4-compatible IPv6 (RFC 4291 §2.5.5.1). `::` and
278
+ // `::1` are matched above; the rest of the block embeds an IPv4 (e.g.
279
+ // `::127.0.0.1`) and is never a valid public target.
280
+ if (expanded.startsWith("0000:0000:0000:0000:0000:0000:")) return "reserved";
281
+
258
282
  return "public";
259
283
  }
260
284
 
package/src/utils/url.ts CHANGED
@@ -25,18 +25,24 @@ export function normalizePathname(
25
25
  return "/";
26
26
  }
27
27
 
28
- if (basePath === "/" || basePath === "") {
28
+ // Canonicalize the basePath the same way as the request pathname. A baseURL
29
+ // with a trailing slash yields a basePath like "/api/auth/"; without this it
30
+ // would never match the slash-stripped pathname and the prefix would leak
31
+ // through to disabledPaths and rate-limit special-rule matching.
32
+ const normalizedBasePath = basePath.replace(/\/+$/, "");
33
+
34
+ if (normalizedBasePath === "") {
29
35
  return pathname;
30
36
  }
31
37
 
32
38
  // Check for exact match or proper path boundary (basePath followed by "/" or end)
33
39
  // This prevents "/api/auth" from matching "/api/authevil/..."
34
- if (pathname === basePath) {
40
+ if (pathname === normalizedBasePath) {
35
41
  return "/";
36
42
  }
37
43
 
38
- if (pathname.startsWith(basePath + "/")) {
39
- return pathname.slice(basePath.length).replace(/\/+$/, "") || "/";
44
+ if (pathname.startsWith(normalizedBasePath + "/")) {
45
+ return pathname.slice(normalizedBasePath.length).replace(/\/+$/, "") || "/";
40
46
  }
41
47
 
42
48
  return pathname;