@better-auth/core 1.5.0-beta.5 → 1.5.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 (43) hide show
  1. package/.turbo/turbo-build.log +26 -22
  2. package/dist/api/index.d.mts +10 -20
  3. package/dist/context/endpoint-context.mjs +7 -3
  4. package/dist/context/global.d.mts +7 -0
  5. package/dist/context/global.mjs +37 -0
  6. package/dist/context/index.d.mts +2 -1
  7. package/dist/context/index.mjs +2 -20
  8. package/dist/context/request-state.mjs +7 -3
  9. package/dist/context/transaction.d.mts +1 -1
  10. package/dist/context/transaction.mjs +7 -3
  11. package/dist/db/adapter/factory.mjs +13 -13
  12. package/dist/db/adapter/get-default-model-name.mjs +1 -1
  13. package/dist/db/adapter/get-id-field.d.mts +1 -1
  14. package/dist/db/adapter/get-id-field.mjs +2 -2
  15. package/dist/error/index.d.mts +3 -1
  16. package/dist/error/index.mjs +2 -3
  17. package/dist/index.d.mts +4 -4
  18. package/dist/social-providers/apple.mjs +3 -1
  19. package/dist/social-providers/gitlab.mjs +1 -1
  20. package/dist/types/context.d.mts +17 -16
  21. package/dist/types/cookie.d.mts +9 -17
  22. package/dist/types/index.d.mts +3 -3
  23. package/dist/types/init-options.d.mts +17 -29
  24. package/dist/utils/url.d.mts +20 -0
  25. package/dist/utils/url.mjs +32 -0
  26. package/package.json +1 -1
  27. package/src/context/endpoint-context.ts +7 -6
  28. package/src/context/global.ts +57 -0
  29. package/src/context/index.ts +1 -29
  30. package/src/context/request-state.ts +7 -6
  31. package/src/context/transaction.ts +7 -7
  32. package/src/db/adapter/factory.ts +13 -13
  33. package/src/db/adapter/get-default-model-name.ts +1 -1
  34. package/src/db/adapter/get-id-field.ts +2 -2
  35. package/src/error/index.ts +2 -3
  36. package/src/social-providers/apple.ts +12 -3
  37. package/src/social-providers/gitlab.ts +1 -1
  38. package/src/types/context.ts +137 -131
  39. package/src/types/cookie.ts +6 -4
  40. package/src/types/index.ts +4 -1
  41. package/src/types/init-options.ts +26 -32
  42. package/src/utils/url.ts +43 -0
  43. package/tsdown.config.ts +8 -0
@@ -11,7 +11,7 @@ import type {
11
11
  import type { DBAdapter, Where } from "../db/adapter";
12
12
  import type { createLogger } from "../env";
13
13
  import type { OAuthProvider } from "../oauth2";
14
- import type { BetterAuthCookies } from "./cookie";
14
+ import type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
15
15
  import type { LiteralString } from "./helper";
16
16
  import type {
17
17
  BetterAuthOptions,
@@ -172,10 +172,7 @@ export interface InternalAdapter<
172
172
  type CreateCookieGetterFn = (
173
173
  cookieName: string,
174
174
  overrideAttributes?: Partial<CookieOptions> | undefined,
175
- ) => {
176
- name: string;
177
- attributes: CookieOptions;
178
- };
175
+ ) => BetterAuthCookie;
179
176
 
180
177
  type CheckPasswordFn<Options extends BetterAuthOptions = BetterAuthOptions> = (
181
178
  userId: string,
@@ -208,139 +205,148 @@ export type PluginContext = {
208
205
  ) => boolean;
209
206
  };
210
207
 
208
+ export type InfoContext = {
209
+ appName: string;
210
+ baseURL: string;
211
+ version: string;
212
+ };
213
+
211
214
  export type AuthContext<Options extends BetterAuthOptions = BetterAuthOptions> =
212
- PluginContext & {
213
- options: Options;
214
- appName: string;
215
- baseURL: string;
216
- trustedOrigins: string[];
217
- /**
218
- * Verifies whether url is a trusted origin according to the "trustedOrigins" configuration
219
- * @param url The url to verify against the "trustedOrigins" configuration
220
- * @param settings Specify supported pattern matching settings
221
- * @returns {boolean} true if the URL matches the origin pattern, false otherwise.
222
- */
223
- isTrustedOrigin: (
224
- url: string,
225
- settings?: { allowRelativePaths: boolean },
226
- ) => boolean;
227
- oauthConfig: {
215
+ PluginContext &
216
+ InfoContext & {
217
+ options: Options;
218
+ trustedOrigins: string[];
228
219
  /**
229
- * This is dangerous and should only be used in dev or staging environments.
220
+ * Verifies whether url is a trusted origin according to the "trustedOrigins" configuration
221
+ * @param url The url to verify against the "trustedOrigins" configuration
222
+ * @param settings Specify supported pattern matching settings
223
+ * @returns {boolean} true if the URL matches the origin pattern, false otherwise.
230
224
  */
231
- skipStateCookieCheck?: boolean | undefined;
225
+ isTrustedOrigin: (
226
+ url: string,
227
+ settings?: { allowRelativePaths: boolean },
228
+ ) => boolean;
229
+ oauthConfig: {
230
+ /**
231
+ * This is dangerous and should only be used in dev or staging environments.
232
+ */
233
+ skipStateCookieCheck?: boolean | undefined;
234
+ /**
235
+ * Strategy for storing OAuth state
236
+ *
237
+ * - "cookie": Store state in an encrypted cookie (stateless)
238
+ * - "database": Store state in the database
239
+ *
240
+ * @default "cookie"
241
+ */
242
+ storeStateStrategy: "database" | "cookie";
243
+ };
232
244
  /**
233
- * Strategy for storing OAuth state
234
- *
235
- * - "cookie": Store state in an encrypted cookie (stateless)
236
- * - "database": Store state in the database
237
- *
238
- * @default "cookie"
245
+ * New session that will be set after the request
246
+ * meaning: there is a `set-cookie` header that will set
247
+ * the session cookie. This is the fetched session. And it's set
248
+ * by `setNewSession` method.
239
249
  */
240
- storeStateStrategy: "database" | "cookie";
241
- };
242
- /**
243
- * New session that will be set after the request
244
- * meaning: there is a `set-cookie` header that will set
245
- * the session cookie. This is the fetched session. And it's set
246
- * by `setNewSession` method.
247
- */
248
- newSession: {
249
- session: Session & Record<string, any>;
250
- user: User & Record<string, any>;
251
- } | null;
252
- session: {
253
- session: Session & Record<string, any>;
254
- user: User & Record<string, any>;
255
- } | null;
256
- setNewSession: (
250
+ newSession: {
251
+ session: Session & Record<string, any>;
252
+ user: User & Record<string, any>;
253
+ } | null;
257
254
  session: {
258
255
  session: Session & Record<string, any>;
259
256
  user: User & Record<string, any>;
260
- } | null,
261
- ) => void;
262
- socialProviders: OAuthProvider[];
263
- authCookies: BetterAuthCookies;
264
- logger: ReturnType<typeof createLogger>;
265
- rateLimit: {
266
- enabled: boolean;
267
- window: number;
268
- max: number;
269
- storage: "memory" | "database" | "secondary-storage";
270
- } & BetterAuthRateLimitOptions;
271
- adapter: DBAdapter<Options>;
272
- internalAdapter: InternalAdapter<Options>;
273
- createAuthCookie: CreateCookieGetterFn;
274
- secret: string;
275
- sessionConfig: {
276
- updateAge: number;
277
- expiresIn: number;
278
- freshAge: number;
279
- cookieRefreshCache:
280
- | false
281
- | {
282
- enabled: true;
283
- updateAge: number;
284
- };
285
- };
286
- generateId: (options: {
287
- model: ModelNames;
288
- size?: number | undefined;
289
- }) => string | false;
290
- secondaryStorage: SecondaryStorage | undefined;
291
- password: {
292
- hash: (password: string) => Promise<string>;
293
- verify: (data: { password: string; hash: string }) => Promise<boolean>;
294
- config: {
295
- minPasswordLength: number;
296
- maxPasswordLength: number;
257
+ } | null;
258
+ setNewSession: (
259
+ session: {
260
+ session: Session & Record<string, any>;
261
+ user: User & Record<string, any>;
262
+ } | null,
263
+ ) => void;
264
+ socialProviders: OAuthProvider[];
265
+ authCookies: BetterAuthCookies;
266
+ logger: ReturnType<typeof createLogger>;
267
+ rateLimit: {
268
+ enabled: boolean;
269
+ window: number;
270
+ max: number;
271
+ storage: "memory" | "database" | "secondary-storage";
272
+ } & Omit<
273
+ BetterAuthRateLimitOptions,
274
+ "enabled" | "window" | "max" | "storage"
275
+ >;
276
+ adapter: DBAdapter<Options>;
277
+ internalAdapter: InternalAdapter<Options>;
278
+ createAuthCookie: CreateCookieGetterFn;
279
+ secret: string;
280
+ sessionConfig: {
281
+ updateAge: number;
282
+ expiresIn: number;
283
+ freshAge: number;
284
+ cookieRefreshCache:
285
+ | false
286
+ | {
287
+ enabled: true;
288
+ updateAge: number;
289
+ };
297
290
  };
298
- checkPassword: CheckPasswordFn<Options>;
291
+ generateId: (options: {
292
+ model: ModelNames;
293
+ size?: number | undefined;
294
+ }) => string | false;
295
+ secondaryStorage: SecondaryStorage | undefined;
296
+ password: {
297
+ hash: (password: string) => Promise<string>;
298
+ verify: (data: { password: string; hash: string }) => Promise<boolean>;
299
+ config: {
300
+ minPasswordLength: number;
301
+ maxPasswordLength: number;
302
+ };
303
+ checkPassword: CheckPasswordFn<Options>;
304
+ };
305
+ tables: BetterAuthDBSchema;
306
+ runMigrations: () => Promise<void>;
307
+ publishTelemetry: (event: {
308
+ type: string;
309
+ anonymousId?: string | undefined;
310
+ payload: Record<string, any>;
311
+ }) => Promise<void>;
312
+ /**
313
+ * Skip origin check for requests.
314
+ *
315
+ * - `true`: Skip for ALL requests (DANGEROUS - disables CSRF protection)
316
+ * - `string[]`: Skip only for specific paths (e.g., SAML callbacks)
317
+ * - `false`: Enable origin check (default)
318
+ *
319
+ * Paths support prefix matching (e.g., "/sso/saml2/callback" matches
320
+ * "/sso/saml2/callback/provider-name").
321
+ *
322
+ * @default false (true in test environments)
323
+ */
324
+ skipOriginCheck: boolean | string[];
325
+ /**
326
+ * This skips the CSRF check for all requests.
327
+ *
328
+ * This is inferred from the `options.advanced?.
329
+ * disableCSRFCheck` option.
330
+ *
331
+ * @default false
332
+ */
333
+ skipCSRFCheck: boolean;
334
+ /**
335
+ * Background task handler for deferred operations.
336
+ *
337
+ * This is inferred from the `options.advanced?.backgroundTasks?.handler` option.
338
+ * Defaults to a no-op that just runs the promise.
339
+ */
340
+ runInBackground: (promise: Promise<void>) => void;
341
+ /**
342
+ * Runs a task in the background if `runInBackground` is configured,
343
+ * otherwise awaits the task directly.
344
+ *
345
+ * This is useful for operations like sending emails where we want
346
+ * to avoid blocking the response when possible (for timing attack
347
+ * mitigation), but still ensure the operation completes.
348
+ */
349
+ runInBackgroundOrAwait: (
350
+ promise: Promise<unknown> | Promise<void> | void | unknown,
351
+ ) => Promise<unknown>;
299
352
  };
300
- tables: BetterAuthDBSchema;
301
- runMigrations: () => Promise<void>;
302
- publishTelemetry: (event: {
303
- type: string;
304
- anonymousId?: string | undefined;
305
- payload: Record<string, any>;
306
- }) => Promise<void>;
307
- /**
308
- * This skips the origin check for all requests.
309
- *
310
- * set to true by default for `test` environments and `false`
311
- * for other environments.
312
- *
313
- * It's inferred from the `options.advanced?.disableCSRFCheck`
314
- * option or `options.advanced?.disableOriginCheck` option.
315
- *
316
- * @default false
317
- */
318
- skipOriginCheck: boolean;
319
- /**
320
- * This skips the CSRF check for all requests.
321
- *
322
- * This is inferred from the `options.advanced?.
323
- * disableCSRFCheck` option.
324
- *
325
- * @default false
326
- */
327
- skipCSRFCheck: boolean;
328
- /**
329
- * Background task handler for deferred operations.
330
- *
331
- * This is inferred from the `options.advanced?.backgroundTasks?.handler` option.
332
- * Defaults to a no-op that just runs the promise.
333
- */
334
- runInBackground: (promise: Promise<void>) => void;
335
- /**
336
- * Runs a task in the background if `runInBackground` is configured,
337
- * otherwise awaits the task directly.
338
- *
339
- * This is useful for operations like sending emails where we want
340
- * to avoid blocking the response when possible (for timing attack
341
- * mitigation), but still ensure the operation completes.
342
- */
343
- runInBackgroundOrAwait: (
344
- promise: Promise<unknown> | Promise<void> | void | unknown,
345
- ) => Promise<unknown>;
346
- };
@@ -1,8 +1,10 @@
1
1
  import type { CookieOptions } from "better-call";
2
2
 
3
+ export type BetterAuthCookie = { name: string; attributes: CookieOptions };
4
+
3
5
  export type BetterAuthCookies = {
4
- sessionToken: { name: string; options: CookieOptions };
5
- sessionData: { name: string; options: CookieOptions };
6
- accountData: { name: string; options: CookieOptions };
7
- dontRememberToken: { name: string; options: CookieOptions };
6
+ sessionToken: BetterAuthCookie;
7
+ sessionData: BetterAuthCookie;
8
+ accountData: BetterAuthCookie;
9
+ dontRememberToken: BetterAuthCookie;
8
10
  };
@@ -4,15 +4,18 @@ export type {
4
4
  BetterAuthPluginRegistry,
5
5
  BetterAuthPluginRegistryIdentifier,
6
6
  GenericEndpointContext,
7
+ InfoContext,
7
8
  InternalAdapter,
8
9
  PluginContext,
9
10
  } from "./context";
10
- export type { BetterAuthCookies } from "./cookie";
11
+ export type { BetterAuthCookie, BetterAuthCookies } from "./cookie";
11
12
  export type * from "./helper";
12
13
  export type {
13
14
  BetterAuthAdvancedOptions,
14
15
  BetterAuthOptions,
15
16
  BetterAuthRateLimitOptions,
17
+ BetterAuthRateLimitRule,
18
+ BetterAuthRateLimitStorage,
16
19
  GenerateIdFn,
17
20
  } from "./init-options";
18
21
  export type { BetterAuthPlugin, HookEndpointContext } from "./plugin";
@@ -37,25 +37,37 @@ export type GenerateIdFn = (options: {
37
37
  size?: number | undefined;
38
38
  }) => string | false;
39
39
 
40
- export type BetterAuthRateLimitOptions = {
41
- /**
42
- * By default, rate limiting is only
43
- * enabled on production.
44
- */
45
- enabled?: boolean | undefined;
40
+ export interface BetterAuthRateLimitStorage {
41
+ get: (key: string) => Promise<RateLimit | null | undefined>;
42
+ set: (
43
+ key: string,
44
+ value: RateLimit,
45
+ update?: boolean | undefined,
46
+ ) => Promise<void>;
47
+ }
48
+
49
+ export type BetterAuthRateLimitRule = {
46
50
  /**
47
51
  * Default window to use for rate limiting. The value
48
52
  * should be in seconds.
49
53
  *
50
54
  * @default 10 seconds
51
55
  */
52
- window?: number | undefined;
56
+ window: number;
53
57
  /**
54
58
  * The default maximum number of requests allowed within the window.
55
59
  *
56
60
  * @default 100 requests
57
61
  */
58
- max?: number | undefined;
62
+ max: number;
63
+ };
64
+
65
+ export type BetterAuthRateLimitOptions = Optional<BetterAuthRateLimitRule> & {
66
+ /**
67
+ * By default, rate limiting is only
68
+ * enabled on production.
69
+ */
70
+ enabled?: boolean | undefined;
59
71
  /**
60
72
  * Custom rate limit rules to apply to
61
73
  * specific paths.
@@ -63,27 +75,12 @@ export type BetterAuthRateLimitOptions = {
63
75
  customRules?:
64
76
  | {
65
77
  [key: string]:
66
- | {
67
- /**
68
- * The window to use for the custom rule.
69
- */
70
- window: number;
71
- /**
72
- * The maximum number of requests allowed within the window.
73
- */
74
- max: number;
75
- }
78
+ | BetterAuthRateLimitRule
76
79
  | false
77
- | ((request: Request) =>
78
- | { window: number; max: number }
79
- | false
80
- | Promise<
81
- | {
82
- window: number;
83
- max: number;
84
- }
85
- | false
86
- >);
80
+ | ((
81
+ request: Request,
82
+ currentRule: BetterAuthRateLimitRule,
83
+ ) => Awaitable<false | BetterAuthRateLimitRule>);
87
84
  }
88
85
  | undefined;
89
86
  /**
@@ -113,10 +110,7 @@ export type BetterAuthRateLimitOptions = {
113
110
  * NOTE: If custom storage is used storage
114
111
  * is ignored
115
112
  */
116
- customStorage?: {
117
- get: (key: string) => Promise<RateLimit | undefined>;
118
- set: (key: string, value: RateLimit) => Promise<void>;
119
- };
113
+ customStorage?: BetterAuthRateLimitStorage;
120
114
  };
121
115
 
122
116
  export type BetterAuthAdvancedOptions = {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Normalizes a request pathname by removing the basePath prefix and trailing slashes.
3
+ * This is useful for matching paths against configured path lists.
4
+ *
5
+ * @param requestUrl - The full request URL
6
+ * @param basePath - The base path of the auth API (e.g., "/api/auth")
7
+ * @returns The normalized path without basePath prefix or trailing slashes,
8
+ * or "/" if URL parsing fails
9
+ *
10
+ * @example
11
+ * normalizePathname("http://localhost:3000/api/auth/sso/saml2/callback/provider1", "/api/auth")
12
+ * // Returns: "/sso/saml2/callback/provider1"
13
+ *
14
+ * normalizePathname("http://localhost:3000/sso/saml2/callback/provider1/", "/")
15
+ * // Returns: "/sso/saml2/callback/provider1"
16
+ */
17
+ export function normalizePathname(
18
+ requestUrl: string,
19
+ basePath: string,
20
+ ): string {
21
+ let pathname: string;
22
+ try {
23
+ pathname = new URL(requestUrl).pathname.replace(/\/+$/, "") || "/";
24
+ } catch {
25
+ return "/";
26
+ }
27
+
28
+ if (basePath === "/" || basePath === "") {
29
+ return pathname;
30
+ }
31
+
32
+ // Check for exact match or proper path boundary (basePath followed by "/" or end)
33
+ // This prevents "/api/auth" from matching "/api/authevil/..."
34
+ if (pathname === basePath) {
35
+ return "/";
36
+ }
37
+
38
+ if (pathname.startsWith(basePath + "/")) {
39
+ return pathname.slice(basePath.length).replace(/\/+$/, "") || "/";
40
+ }
41
+
42
+ return pathname;
43
+ }
package/tsdown.config.ts CHANGED
@@ -1,5 +1,10 @@
1
+ import { readFile } from "node:fs/promises";
1
2
  import { defineConfig } from "tsdown";
2
3
 
4
+ const packageJson = JSON.parse(
5
+ await readFile(new URL("./package.json", import.meta.url), "utf-8"),
6
+ );
7
+
3
8
  export default defineConfig({
4
9
  dts: { build: true, incremental: true },
5
10
  format: ["esm"],
@@ -19,6 +24,9 @@ export default defineConfig({
19
24
  "./src/error/index.ts",
20
25
  ],
21
26
  external: ["@better-auth/core/async_hooks"],
27
+ env: {
28
+ BETTER_AUTH_VERSION: packageJson.version,
29
+ },
22
30
  unbundle: true,
23
31
  clean: true,
24
32
  });