@better-auth/core 1.7.0-beta.4 → 1.7.0-beta.6

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 (170) hide show
  1. package/dist/api/index.d.mts +47 -4
  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/get-tables.mjs +3 -3
  10. package/dist/db/schema/account.d.mts +1 -1
  11. package/dist/db/schema/account.mjs +1 -1
  12. package/dist/db/type.d.mts +12 -7
  13. package/dist/env/env-impl.mjs +1 -1
  14. package/dist/error/codes.d.mts +5 -0
  15. package/dist/error/codes.mjs +5 -0
  16. package/dist/index.d.mts +2 -2
  17. package/dist/instrumentation/tracer.mjs +1 -1
  18. package/dist/oauth2/create-authorization-url.d.mts +4 -1
  19. package/dist/oauth2/create-authorization-url.mjs +5 -2
  20. package/dist/oauth2/dpop.d.mts +142 -0
  21. package/dist/oauth2/dpop.mjs +246 -0
  22. package/dist/oauth2/index.d.mts +6 -3
  23. package/dist/oauth2/index.mjs +5 -2
  24. package/dist/oauth2/oauth-provider.d.mts +128 -9
  25. package/dist/oauth2/refresh-access-token.mjs +1 -1
  26. package/dist/oauth2/scopes.d.mts +76 -0
  27. package/dist/oauth2/scopes.mjs +96 -0
  28. package/dist/oauth2/utils.mjs +2 -1
  29. package/dist/oauth2/verify-id-token.d.mts +26 -0
  30. package/dist/oauth2/verify-id-token.mjs +62 -0
  31. package/dist/oauth2/verify.d.mts +88 -15
  32. package/dist/oauth2/verify.mjs +187 -19
  33. package/dist/social-providers/apple.d.mts +14 -2
  34. package/dist/social-providers/apple.mjs +12 -36
  35. package/dist/social-providers/atlassian.d.mts +5 -1
  36. package/dist/social-providers/atlassian.mjs +4 -4
  37. package/dist/social-providers/cognito.d.mts +13 -2
  38. package/dist/social-providers/cognito.mjs +24 -32
  39. package/dist/social-providers/discord.d.mts +5 -1
  40. package/dist/social-providers/discord.mjs +7 -6
  41. package/dist/social-providers/dropbox.d.mts +5 -1
  42. package/dist/social-providers/dropbox.mjs +5 -5
  43. package/dist/social-providers/facebook.d.mts +21 -2
  44. package/dist/social-providers/facebook.mjs +46 -22
  45. package/dist/social-providers/figma.d.mts +5 -1
  46. package/dist/social-providers/figma.mjs +5 -5
  47. package/dist/social-providers/github.d.mts +5 -1
  48. package/dist/social-providers/github.mjs +4 -4
  49. package/dist/social-providers/gitlab.d.mts +5 -1
  50. package/dist/social-providers/gitlab.mjs +6 -6
  51. package/dist/social-providers/google.d.mts +29 -3
  52. package/dist/social-providers/google.mjs +24 -30
  53. package/dist/social-providers/huggingface.d.mts +5 -1
  54. package/dist/social-providers/huggingface.mjs +8 -8
  55. package/dist/social-providers/index.d.mts +222 -42
  56. package/dist/social-providers/kakao.d.mts +5 -1
  57. package/dist/social-providers/kakao.mjs +8 -8
  58. package/dist/social-providers/kick.d.mts +5 -1
  59. package/dist/social-providers/kick.mjs +4 -4
  60. package/dist/social-providers/line.d.mts +8 -2
  61. package/dist/social-providers/line.mjs +12 -14
  62. package/dist/social-providers/linear.d.mts +5 -1
  63. package/dist/social-providers/linear.mjs +4 -4
  64. package/dist/social-providers/linkedin.d.mts +5 -1
  65. package/dist/social-providers/linkedin.mjs +10 -10
  66. package/dist/social-providers/microsoft-entra-id.d.mts +41 -6
  67. package/dist/social-providers/microsoft-entra-id.mjs +40 -36
  68. package/dist/social-providers/naver.d.mts +5 -1
  69. package/dist/social-providers/naver.mjs +4 -4
  70. package/dist/social-providers/notion.d.mts +5 -1
  71. package/dist/social-providers/notion.mjs +4 -4
  72. package/dist/social-providers/paybin.d.mts +5 -1
  73. package/dist/social-providers/paybin.mjs +10 -10
  74. package/dist/social-providers/paypal.d.mts +5 -2
  75. package/dist/social-providers/paypal.mjs +8 -13
  76. package/dist/social-providers/polar.d.mts +5 -1
  77. package/dist/social-providers/polar.mjs +8 -8
  78. package/dist/social-providers/railway.d.mts +5 -1
  79. package/dist/social-providers/railway.mjs +9 -9
  80. package/dist/social-providers/reddit.d.mts +5 -1
  81. package/dist/social-providers/reddit.mjs +9 -8
  82. package/dist/social-providers/roblox.d.mts +5 -1
  83. package/dist/social-providers/roblox.mjs +5 -5
  84. package/dist/social-providers/salesforce.d.mts +5 -1
  85. package/dist/social-providers/salesforce.mjs +8 -8
  86. package/dist/social-providers/slack.d.mts +5 -1
  87. package/dist/social-providers/slack.mjs +9 -9
  88. package/dist/social-providers/spotify.d.mts +5 -1
  89. package/dist/social-providers/spotify.mjs +5 -5
  90. package/dist/social-providers/tiktok.d.mts +5 -1
  91. package/dist/social-providers/tiktok.mjs +9 -5
  92. package/dist/social-providers/twitch.d.mts +5 -1
  93. package/dist/social-providers/twitch.mjs +4 -4
  94. package/dist/social-providers/twitter.d.mts +6 -4
  95. package/dist/social-providers/twitter.mjs +9 -9
  96. package/dist/social-providers/vercel.d.mts +5 -1
  97. package/dist/social-providers/vercel.mjs +4 -7
  98. package/dist/social-providers/vk.d.mts +5 -1
  99. package/dist/social-providers/vk.mjs +5 -5
  100. package/dist/social-providers/wechat.d.mts +5 -1
  101. package/dist/social-providers/wechat.mjs +10 -6
  102. package/dist/social-providers/zoom.d.mts +6 -1
  103. package/dist/social-providers/zoom.mjs +15 -9
  104. package/dist/types/context.d.mts +27 -8
  105. package/dist/types/index.d.mts +1 -1
  106. package/dist/types/init-options.d.mts +137 -6
  107. package/dist/types/plugin-client.d.mts +12 -2
  108. package/dist/utils/host.mjs +4 -0
  109. package/dist/utils/url.mjs +4 -3
  110. package/package.json +7 -7
  111. package/src/api/index.ts +82 -0
  112. package/src/context/transaction.ts +45 -12
  113. package/src/db/adapter/factory.ts +127 -64
  114. package/src/db/adapter/index.ts +54 -9
  115. package/src/db/adapter/types.ts +1 -0
  116. package/src/db/get-tables.ts +8 -3
  117. package/src/db/schema/account.ts +14 -2
  118. package/src/db/type.ts +12 -7
  119. package/src/env/env-impl.ts +1 -2
  120. package/src/error/codes.ts +5 -0
  121. package/src/oauth2/create-authorization-url.ts +2 -2
  122. package/src/oauth2/dpop.ts +568 -0
  123. package/src/oauth2/index.ts +61 -2
  124. package/src/oauth2/oauth-provider.ts +140 -10
  125. package/src/oauth2/refresh-access-token.ts +2 -2
  126. package/src/oauth2/scopes.ts +118 -0
  127. package/src/oauth2/utils.ts +2 -5
  128. package/src/oauth2/verify-id-token.ts +111 -0
  129. package/src/oauth2/verify.ts +372 -58
  130. package/src/social-providers/apple.ts +24 -61
  131. package/src/social-providers/atlassian.ts +12 -8
  132. package/src/social-providers/cognito.ts +25 -47
  133. package/src/social-providers/discord.ts +19 -8
  134. package/src/social-providers/dropbox.ts +13 -7
  135. package/src/social-providers/facebook.ts +97 -51
  136. package/src/social-providers/figma.ts +13 -9
  137. package/src/social-providers/github.ts +12 -8
  138. package/src/social-providers/gitlab.ts +14 -8
  139. package/src/social-providers/google.ts +66 -47
  140. package/src/social-providers/huggingface.ts +12 -8
  141. package/src/social-providers/kakao.ts +16 -8
  142. package/src/social-providers/kick.ts +12 -7
  143. package/src/social-providers/line.ts +37 -37
  144. package/src/social-providers/linear.ts +12 -6
  145. package/src/social-providers/linkedin.ts +14 -10
  146. package/src/social-providers/microsoft-entra-id.ts +103 -59
  147. package/src/social-providers/naver.ts +12 -6
  148. package/src/social-providers/notion.ts +12 -6
  149. package/src/social-providers/paybin.ts +14 -11
  150. package/src/social-providers/paypal.ts +6 -25
  151. package/src/social-providers/polar.ts +12 -8
  152. package/src/social-providers/railway.ts +13 -9
  153. package/src/social-providers/reddit.ts +25 -10
  154. package/src/social-providers/roblox.ts +18 -7
  155. package/src/social-providers/salesforce.ts +12 -8
  156. package/src/social-providers/slack.ts +18 -9
  157. package/src/social-providers/spotify.ts +13 -7
  158. package/src/social-providers/tiktok.ts +13 -7
  159. package/src/social-providers/twitch.ts +12 -8
  160. package/src/social-providers/twitter.ts +17 -8
  161. package/src/social-providers/vercel.ts +16 -10
  162. package/src/social-providers/vk.ts +13 -7
  163. package/src/social-providers/wechat.ts +28 -9
  164. package/src/social-providers/zoom.ts +19 -6
  165. package/src/types/context.ts +26 -8
  166. package/src/types/index.ts +7 -0
  167. package/src/types/init-options.ts +159 -8
  168. package/src/types/plugin-client.ts +16 -2
  169. package/src/utils/host.ts +15 -0
  170. package/src/utils/url.ts +10 -4
@@ -10,12 +10,13 @@ import type {
10
10
  } from "../db";
11
11
  import type { DBAdapter, Where } from "../db/adapter";
12
12
  import type { createLogger } from "../env";
13
- import type { OAuthProvider } from "../oauth2";
13
+ import type { UpstreamProvider } from "../oauth2";
14
14
  import type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
15
15
  import type { Awaitable, LiteralString } from "./helper";
16
16
  import type {
17
17
  BetterAuthOptions,
18
18
  BetterAuthRateLimitOptions,
19
+ UserProvisioningSource,
19
20
  } from "./init-options";
20
21
  import type { BetterAuthPlugin } from "./plugin";
21
22
  import type { SecretConfig } from "./secret";
@@ -87,16 +88,15 @@ export type GenericEndpointContext<
87
88
  export interface InternalAdapter<
88
89
  _Options extends BetterAuthOptions = BetterAuthOptions,
89
90
  > {
90
- createOAuthUser(
91
- user: Omit<User, "id" | "createdAt" | "updatedAt">,
92
- account: Omit<Account, "userId" | "id" | "createdAt" | "updatedAt"> &
93
- Partial<Account>,
94
- ): Promise<{ user: User; account: Account }>;
95
-
96
91
  createUser<T extends Record<string, any>>(
97
92
  user: Omit<User, "id" | "createdAt" | "updatedAt" | "emailVerified"> &
98
93
  Partial<User> &
99
94
  Record<string, any>,
95
+ /**
96
+ * Provisioning source. The creation seam adds `action: "create-user"` and
97
+ * runs the `user.validateUserInfo` gate.
98
+ */
99
+ source: UserProvisioningSource,
100
100
  ): Promise<T & User>;
101
101
 
102
102
  createAccount<T extends Record<string, any>>(
@@ -237,6 +237,24 @@ export interface InternalAdapter<
237
237
  */
238
238
  consumeVerificationValue(identifier: string): Promise<Verification | null>;
239
239
 
240
+ /**
241
+ * First-writer-wins create keyed by a deterministic primary key derived from
242
+ * `identifier`. Returns `true` when this caller created the row and `false`
243
+ * when a row for the same identifier already existed.
244
+ *
245
+ * The dual of `consumeVerificationValue`: reserve races to create a marker
246
+ * exactly once, where consume races to delete one exactly once. Use it for
247
+ * replay tombstones (a SAML assertion id, a JWT `jti`) where the first caller
248
+ * wins. The database path is atomic via the primary key. Secondary-storage-only
249
+ * verification is not supported for reservation and runtime implementations
250
+ * should fail closed unless verification is backed by the database.
251
+ */
252
+ reserveVerificationValue(data: {
253
+ identifier: string;
254
+ value: string;
255
+ expiresAt: Date;
256
+ }): Promise<boolean>;
257
+
240
258
  updateVerificationByIdentifier(
241
259
  identifier: string,
242
260
  data: Partial<Verification>,
@@ -351,7 +369,7 @@ export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
351
369
  user: User & Record<string, any>;
352
370
  } | null,
353
371
  ) => void;
354
- socialProviders: OAuthProvider[];
372
+ socialProviders: UpstreamProvider[];
355
373
  authCookies: BetterAuthCookies;
356
374
  logger: ReturnType<typeof createLogger>;
357
375
  rateLimit: {
@@ -24,6 +24,13 @@ export type {
24
24
  DynamicBaseURLConfig,
25
25
  GenerateIdFn,
26
26
  StoreIdentifierOption,
27
+ UserProvisioningSource,
28
+ ValidateUserInfoAction,
29
+ ValidateUserInfoMethod,
30
+ ValidateUserInfoOAuthInfo,
31
+ ValidateUserInfoResult,
32
+ ValidateUserInfoSource,
33
+ ValidateUserInfoSSOInfo,
27
34
  } from "./init-options";
28
35
  export type {
29
36
  BetterAuthPlugin,
@@ -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,
@@ -47,6 +46,98 @@ export type GenerateIdFn = (options: {
47
46
  size?: number | undefined;
48
47
  }) => string | false;
49
48
 
49
+ /**
50
+ * What Better Auth is about to do with an incoming identity when
51
+ * {@link BetterAuthOptions.user}'s `validateUserInfo` runs.
52
+ *
53
+ * - `create-user`: a brand-new user record is about to be created.
54
+ * - `link-account`: a new provider account is about to be linked to an
55
+ * already-existing user.
56
+ * - `sign-in`: an existing OAuth or SSO user is signing in again. This is the
57
+ * one case where the provider can assert *changed* data, so the hook receives
58
+ * the fresh provider email and profile (not the stored row), letting a domain
59
+ * or org policy reject a user whose provider identity moved out of bounds.
60
+ *
61
+ * Non-provider returning sign-ins are not re-validated: they carry only the
62
+ * stored row, which has not changed since `create-user` gated it. Use the admin
63
+ * plugin's ban controls or a `databaseHooks.session.create.before` hook to
64
+ * block those.
65
+ */
66
+ export type ValidateUserInfoAction = "create-user" | "link-account" | "sign-in";
67
+
68
+ /**
69
+ * The authentication method that produced the incoming user info. The named
70
+ * methods cover Better Auth's built-ins; the open `string` keeps it extensible
71
+ * for plugins (for example `"scim"`).
72
+ */
73
+ export type ValidateUserInfoMethod =
74
+ | "oauth"
75
+ | "sso-oidc"
76
+ | "sso-saml"
77
+ | "email-password"
78
+ | "magic-link"
79
+ | "email-otp"
80
+ | "anonymous"
81
+ | "siwe"
82
+ | "phone-number"
83
+ | "admin"
84
+ | (string & {});
85
+
86
+ /** OAuth-specific provisioning context; present only when `method` is `"oauth"`. */
87
+ export type ValidateUserInfoOAuthInfo = {
88
+ /** The social or generic OAuth provider id (e.g. `"google"`). */
89
+ providerId: string;
90
+ /** The raw provider profile (userinfo or id-token claims), unmapped. */
91
+ profile?: Record<string, unknown> | undefined;
92
+ };
93
+
94
+ /** SSO-specific provisioning context; present for OIDC and SAML SSO methods. */
95
+ export type ValidateUserInfoSSOInfo = {
96
+ /** The configured SSO provider id. */
97
+ providerId: string;
98
+ /** The raw OIDC claims or SAML assertion attributes, unmapped. */
99
+ profile?: Record<string, unknown> | undefined;
100
+ };
101
+
102
+ /** Provisioning origin passed to `createUser`; the creation seam adds `action: "create-user"` to build {@link ValidateUserInfoSource}. */
103
+ export type UserProvisioningSource = {
104
+ method: ValidateUserInfoMethod;
105
+ /** Provider id and raw profile; present iff `method` is `"oauth"`. */
106
+ oauth?: ValidateUserInfoOAuthInfo | undefined;
107
+ /** Provider id and raw profile; present iff `method` is `"sso-oidc"` or `"sso-saml"`. */
108
+ sso?: ValidateUserInfoSSOInfo | undefined;
109
+ };
110
+
111
+ /**
112
+ * The context passed to `validateUserInfo`: the lifecycle
113
+ * {@link ValidateUserInfoAction}, the {@link ValidateUserInfoMethod}, and (for
114
+ * OAuth/SSO provider methods) protocol-specific provider metadata.
115
+ *
116
+ * ```ts
117
+ * // Scope to one OAuth provider:
118
+ * if (source.oauth?.providerId !== "google") return;
119
+ * // Branch on the method:
120
+ * if (source.method === "anonymous") return { error: "no_anonymous" };
121
+ * // Inspect SSO claims:
122
+ * if (source.method === "sso-saml" && source.sso?.profile?.department !== "eng") {
123
+ * return { error: "invalid_department" };
124
+ * }
125
+ * ```
126
+ */
127
+ export type ValidateUserInfoSource = UserProvisioningSource & {
128
+ action: ValidateUserInfoAction;
129
+ };
130
+
131
+ export type ValidateUserInfoResult = {
132
+ /** A short, machine-readable rejection code, surfaced to the client. */
133
+ error: string;
134
+ /**
135
+ * A human-readable reason, surfaced to the client. Do not put sensitive
136
+ * details here.
137
+ */
138
+ errorDescription?: string | undefined;
139
+ };
140
+
50
141
  /**
51
142
  * Configuration for dynamic base URL resolution.
52
143
  * Allows Better Auth to work with multiple domains (e.g., Vercel preview deployments).
@@ -95,12 +186,27 @@ export type DynamicBaseURLConfig = {
95
186
  export type BaseURLConfig = string | DynamicBaseURLConfig;
96
187
 
97
188
  export interface BetterAuthRateLimitStorage {
98
- get: (key: string) => Promise<RateLimit | null | undefined>;
99
- 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: (
100
207
  key: string,
101
- value: RateLimit,
102
- update?: boolean | undefined,
103
- ) => Promise<void>;
208
+ rule: { window: number; max: number },
209
+ ) => Promise<{ allowed: boolean; retryAfter: number | null }>;
104
210
  }
105
211
 
106
212
  export type BetterAuthRateLimitRule = {
@@ -777,6 +883,33 @@ export type BetterAuthOptions = {
777
883
  */
778
884
  user?:
779
885
  | (BetterAuthDBOptions<"user", keyof BaseUser> & {
886
+ /**
887
+ * Gate which identities Better Auth admits. Called just before
888
+ * `create-user`, `link-account`, and (for OAuth) `sign-in`, across
889
+ * every authentication method, including stateless setups with no
890
+ * persistent database. On `sign-in` the hook receives the *fresh*
891
+ * provider email and profile, so a domain policy can reject a user
892
+ * whose provider identity moved out of bounds.
893
+ *
894
+ * Non-provider returning sign-ins are not re-validated; use the admin
895
+ * plugin's ban controls or a `databaseHooks.session.create.before`
896
+ * hook for those.
897
+ *
898
+ * Return nothing to allow; return `{ error }` to reject. Browser flows
899
+ * redirect to the configured error URL; programmatic flows surface a
900
+ * `403`.
901
+ *
902
+ * TODO: rename to `validateUser` (and the `ValidateUserInfo*` types).
903
+ * "UserInfo" is the OIDC term and misleads for the email/password,
904
+ * SIWE, phone, and admin methods.
905
+ */
906
+ validateUserInfo?: (
907
+ data: {
908
+ user: Partial<User> & Record<string, unknown>;
909
+ source: ValidateUserInfoSource;
910
+ },
911
+ context: GenericEndpointContext,
912
+ ) => Awaitable<void | ValidateUserInfoResult>;
780
913
  /**
781
914
  * Changing email configuration
782
915
  */
@@ -926,6 +1059,20 @@ export type BetterAuthOptions = {
926
1059
  * @default "compact"
927
1060
  */
928
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
+ };
929
1076
  /**
930
1077
  * Controls stateless cookie cache refresh behavior.
931
1078
  *
@@ -1134,9 +1281,13 @@ export type BetterAuthOptions = {
1134
1281
  */
1135
1282
  storeStateStrategy?: "database" | "cookie";
1136
1283
  /**
1137
- * 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.
1138
1287
  *
1139
- * 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.
1140
1291
  *
1141
1292
  * @default false
1142
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
@@ -235,6 +235,10 @@ function classifyIPv6(expanded: string): HostKind {
235
235
 
236
236
  if (expanded.startsWith("2001:0db8:")) return "documentation";
237
237
 
238
+ // 2001:2::/48 — Benchmarking (RFC 5180). A specific non-globally-reachable
239
+ // block inside the otherwise-mixed 2001::/23 protocol-assignments space.
240
+ if (expanded.startsWith("2001:0002:0000:")) return "benchmarking";
241
+
238
242
  if (expanded.startsWith("2002:")) {
239
243
  const embedded = extractEmbeddedIPv4(expanded, 1);
240
244
  if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
@@ -247,6 +251,10 @@ function classifyIPv6(expanded: string): HostKind {
247
251
  return "reserved";
248
252
  }
249
253
 
254
+ // 64:ff9b:1::/48 — Local-Use IPv4/IPv6 Translation (RFC 8215). Distinct from
255
+ // the well-known NAT64 /96 prefix above and not globally reachable.
256
+ if (expanded.startsWith("0064:ff9b:0001:")) return "reserved";
257
+
250
258
  if (expanded.startsWith("2001:0000:")) {
251
259
  const embedded = extractEmbeddedIPv4(expanded, 6, { xor: true });
252
260
  if (embedded && classifyIPv4(embedded) !== "public") return "reserved";
@@ -255,6 +263,13 @@ function classifyIPv6(expanded: string): HostKind {
255
263
 
256
264
  if (expanded.startsWith("0100:0000:0000:0000:")) return "reserved";
257
265
 
266
+ // 3fff::/20 — Documentation (RFC 9637). The /20 fixes the first 16 bits to
267
+ // `3fff` and the next nibble to 0, so only `3fff:0xxx` is in range.
268
+ if (expanded.startsWith("3fff:0")) return "documentation";
269
+
270
+ // 5f00::/16 — SRv6 SIDs (RFC 9602), not globally reachable.
271
+ if (expanded.startsWith("5f00:")) return "reserved";
272
+
258
273
  return "public";
259
274
  }
260
275
 
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;