better-auth 1.6.11 → 1.6.12

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 (65) hide show
  1. package/dist/api/index.d.mts +8 -2
  2. package/dist/api/routes/callback.d.mts +1 -1
  3. package/dist/api/routes/callback.mjs +36 -40
  4. package/dist/api/routes/email-verification.d.mts +1 -0
  5. package/dist/api/routes/email-verification.mjs +4 -3
  6. package/dist/api/routes/session.mjs +14 -9
  7. package/dist/api/routes/sign-in.d.mts +1 -0
  8. package/dist/api/routes/sign-in.mjs +2 -1
  9. package/dist/api/routes/sign-up.d.mts +1 -0
  10. package/dist/api/routes/sign-up.mjs +9 -7
  11. package/dist/api/routes/update-user.mjs +4 -4
  12. package/dist/client/parser.mjs +0 -1
  13. package/dist/client/plugins/index.d.mts +3 -3
  14. package/dist/client/proxy.mjs +2 -1
  15. package/dist/context/helpers.mjs +3 -2
  16. package/dist/cookies/cookie-utils.d.mts +24 -1
  17. package/dist/cookies/cookie-utils.mjs +85 -22
  18. package/dist/cookies/index.d.mts +2 -3
  19. package/dist/cookies/index.mjs +39 -11
  20. package/dist/cookies/session-store.mjs +4 -23
  21. package/dist/db/get-migration.mjs +4 -4
  22. package/dist/db/index.d.mts +2 -2
  23. package/dist/db/index.mjs +3 -2
  24. package/dist/db/internal-adapter.mjs +37 -30
  25. package/dist/db/schema.d.mts +15 -2
  26. package/dist/db/schema.mjs +26 -1
  27. package/dist/index.d.mts +2 -2
  28. package/dist/index.mjs +2 -2
  29. package/dist/oauth2/errors.mjs +16 -1
  30. package/dist/oauth2/link-account.mjs +3 -3
  31. package/dist/oauth2/state.mjs +8 -2
  32. package/dist/package.mjs +1 -1
  33. package/dist/plugins/access/access.mjs +11 -6
  34. package/dist/plugins/admin/admin.mjs +0 -4
  35. package/dist/plugins/admin/client.d.mts +1 -1
  36. package/dist/plugins/bearer/index.mjs +4 -9
  37. package/dist/plugins/captcha/index.mjs +2 -2
  38. package/dist/plugins/generic-oauth/index.d.mts +1 -1
  39. package/dist/plugins/generic-oauth/index.mjs +6 -6
  40. package/dist/plugins/generic-oauth/routes.mjs +34 -32
  41. package/dist/plugins/generic-oauth/types.d.mts +7 -0
  42. package/dist/plugins/last-login-method/client.mjs +2 -2
  43. package/dist/plugins/magic-link/index.mjs +0 -1
  44. package/dist/plugins/mcp/index.mjs +0 -4
  45. package/dist/plugins/multi-session/index.mjs +2 -2
  46. package/dist/plugins/oauth-proxy/index.mjs +44 -31
  47. package/dist/plugins/oauth-proxy/utils.mjs +3 -10
  48. package/dist/plugins/oidc-provider/index.mjs +0 -4
  49. package/dist/plugins/open-api/generator.mjs +16 -5
  50. package/dist/plugins/organization/adapter.mjs +61 -56
  51. package/dist/plugins/organization/client.d.mts +2 -1
  52. package/dist/plugins/organization/error-codes.d.mts +1 -0
  53. package/dist/plugins/organization/error-codes.mjs +2 -1
  54. package/dist/plugins/organization/routes/crud-invites.mjs +3 -0
  55. package/dist/plugins/two-factor/index.mjs +3 -2
  56. package/dist/plugins/username/index.d.mts +24 -2
  57. package/dist/plugins/username/index.mjs +49 -3
  58. package/dist/state.d.mts +2 -2
  59. package/dist/state.mjs +18 -4
  60. package/dist/test-utils/headers.mjs +2 -7
  61. package/dist/test-utils/test-instance.d.mts +24 -6
  62. package/dist/utils/index.d.mts +1 -1
  63. package/dist/utils/url.d.mts +2 -1
  64. package/dist/utils/url.mjs +9 -3
  65. package/package.json +15 -14
@@ -1,6 +1,6 @@
1
1
  import { getDate } from "../../utils/date.mjs";
2
2
  import { parseJSON } from "../../client/parser.mjs";
3
- import { getCurrentAdapter } from "@better-auth/core/context";
3
+ import { getCurrentAdapter, runWithTransaction } from "@better-auth/core/context";
4
4
  import { BetterAuthError } from "@better-auth/core/error";
5
5
  import { filterOutputFields } from "@better-auth/core/utils/db";
6
6
  //#region src/plugins/organization/adapter.ts
@@ -184,46 +184,49 @@ const getOrgAdapter = (context, options) => {
184
184
  });
185
185
  },
186
186
  deleteMember: async ({ memberId, organizationId, userId: _userId }) => {
187
- const adapter = await getCurrentAdapter(baseAdapter);
188
- let userId;
189
- if (!_userId) {
190
- const member = await adapter.findOne({
187
+ return runWithTransaction(baseAdapter, async () => {
188
+ const adapter = await getCurrentAdapter(baseAdapter);
189
+ let userId;
190
+ if (!_userId) {
191
+ const member = await adapter.findOne({
192
+ model: "member",
193
+ where: [{
194
+ field: "id",
195
+ value: memberId
196
+ }]
197
+ });
198
+ if (!member) throw new BetterAuthError("Member not found");
199
+ userId = member.userId;
200
+ } else userId = _userId;
201
+ const member = await adapter.delete({
191
202
  model: "member",
192
203
  where: [{
193
204
  field: "id",
194
205
  value: memberId
195
206
  }]
196
207
  });
197
- if (!member) throw new BetterAuthError("Member not found");
198
- userId = member.userId;
199
- } else userId = _userId;
200
- const member = await adapter.delete({
201
- model: "member",
202
- where: [{
203
- field: "id",
204
- value: memberId
205
- }]
208
+ if (options?.teams?.enabled) {
209
+ const teams = await adapter.findMany({
210
+ model: "team",
211
+ where: [{
212
+ field: "organizationId",
213
+ value: organizationId
214
+ }]
215
+ });
216
+ if (teams.length > 0) await adapter.deleteMany({
217
+ model: "teamMember",
218
+ where: [{
219
+ field: "userId",
220
+ value: userId
221
+ }, {
222
+ field: "teamId",
223
+ value: teams.map((team) => team.id),
224
+ operator: "in"
225
+ }]
226
+ });
227
+ }
228
+ return member;
206
229
  });
207
- if (options?.teams?.enabled) {
208
- const teams = await adapter.findMany({
209
- model: "team",
210
- where: [{
211
- field: "organizationId",
212
- value: organizationId
213
- }]
214
- });
215
- await Promise.all(teams.map((team) => adapter.deleteMany({
216
- model: "teamMember",
217
- where: [{
218
- field: "teamId",
219
- value: team.id
220
- }, {
221
- field: "userId",
222
- value: userId
223
- }]
224
- })));
225
- }
226
- return member;
227
230
  },
228
231
  updateOrganization: async (organizationId, data) => {
229
232
  const organization = await (await getCurrentAdapter(baseAdapter)).update({
@@ -244,29 +247,31 @@ const getOrgAdapter = (context, options) => {
244
247
  }, orgAdditionalFields);
245
248
  },
246
249
  deleteOrganization: async (organizationId) => {
247
- const adapter = await getCurrentAdapter(baseAdapter);
248
- await adapter.deleteMany({
249
- model: "member",
250
- where: [{
251
- field: "organizationId",
252
- value: organizationId
253
- }]
254
- });
255
- await adapter.deleteMany({
256
- model: "invitation",
257
- where: [{
258
- field: "organizationId",
259
- value: organizationId
260
- }]
261
- });
262
- await adapter.delete({
263
- model: "organization",
264
- where: [{
265
- field: "id",
266
- value: organizationId
267
- }]
250
+ return runWithTransaction(baseAdapter, async () => {
251
+ const adapter = await getCurrentAdapter(baseAdapter);
252
+ await adapter.deleteMany({
253
+ model: "member",
254
+ where: [{
255
+ field: "organizationId",
256
+ value: organizationId
257
+ }]
258
+ });
259
+ await adapter.deleteMany({
260
+ model: "invitation",
261
+ where: [{
262
+ field: "organizationId",
263
+ value: organizationId
264
+ }]
265
+ });
266
+ await adapter.delete({
267
+ model: "organization",
268
+ where: [{
269
+ field: "id",
270
+ value: organizationId
271
+ }]
272
+ });
273
+ return organizationId;
268
274
  });
269
- return organizationId;
270
275
  },
271
276
  setActiveOrganization: async (sessionToken, organizationId, ctx) => {
272
277
  return await context.internalAdapter.updateSession(sessionToken, { activeOrganizationId: organizationId });
@@ -273,6 +273,7 @@ declare const organizationClient: <CO extends OrganizationClientOptions>(options
273
273
  ROLE_NAME_IS_ALREADY_TAKEN: _better_auth_core_utils_error_codes0.RawError<"ROLE_NAME_IS_ALREADY_TAKEN">;
274
274
  CANNOT_DELETE_A_PRE_DEFINED_ROLE: _better_auth_core_utils_error_codes0.RawError<"CANNOT_DELETE_A_PRE_DEFINED_ROLE">;
275
275
  ROLE_IS_ASSIGNED_TO_MEMBERS: _better_auth_core_utils_error_codes0.RawError<"ROLE_IS_ASSIGNED_TO_MEMBERS">;
276
+ INVALID_TEAM_ID: _better_auth_core_utils_error_codes0.RawError<"INVALID_TEAM_ID">;
276
277
  };
277
278
  };
278
279
  declare const inferOrgAdditionalFields: <O extends {
@@ -371,4 +372,4 @@ declare const inferOrgAdditionalFields: <O extends {
371
372
  additionalFields: unknown;
372
373
  } ? K : never]: S_1[K] } : undefined : undefined : undefined : S;
373
374
  //#endregion
374
- export { clientSideHasPermission, inferOrgAdditionalFields, organizationClient };
375
+ export { OrganizationClientOptions, clientSideHasPermission, inferOrgAdditionalFields, organizationClient };
@@ -60,6 +60,7 @@ declare const ORGANIZATION_ERROR_CODES: {
60
60
  ROLE_NAME_IS_ALREADY_TAKEN: _better_auth_core_utils_error_codes0.RawError<"ROLE_NAME_IS_ALREADY_TAKEN">;
61
61
  CANNOT_DELETE_A_PRE_DEFINED_ROLE: _better_auth_core_utils_error_codes0.RawError<"CANNOT_DELETE_A_PRE_DEFINED_ROLE">;
62
62
  ROLE_IS_ASSIGNED_TO_MEMBERS: _better_auth_core_utils_error_codes0.RawError<"ROLE_IS_ASSIGNED_TO_MEMBERS">;
63
+ INVALID_TEAM_ID: _better_auth_core_utils_error_codes0.RawError<"INVALID_TEAM_ID">;
63
64
  };
64
65
  //#endregion
65
66
  export { ORGANIZATION_ERROR_CODES };
@@ -58,7 +58,8 @@ const ORGANIZATION_ERROR_CODES = defineErrorCodes({
58
58
  INVALID_RESOURCE: "The provided permission includes an invalid resource",
59
59
  ROLE_NAME_IS_ALREADY_TAKEN: "That role name is already taken",
60
60
  CANNOT_DELETE_A_PRE_DEFINED_ROLE: "Cannot delete a pre-defined role",
61
- ROLE_IS_ASSIGNED_TO_MEMBERS: "Cannot delete a role that is assigned to members. Please reassign the members to a different role first"
61
+ ROLE_IS_ASSIGNED_TO_MEMBERS: "Cannot delete a role that is assigned to members. Please reassign the members to a different role first",
62
+ INVALID_TEAM_ID: "Team id contains a reserved character"
62
63
  });
63
64
  //#endregion
64
65
  export { ORGANIZATION_ERROR_CODES };
@@ -155,6 +155,9 @@ const createInvitation = (option) => {
155
155
  member
156
156
  }, ctx.context) : ctx.context.orgOptions.invitationLimit ?? 100;
157
157
  if ((await adapter.findPendingInvitations({ organizationId })).length >= invitationLimit) throw APIError.from("FORBIDDEN", ORGANIZATION_ERROR_CODES.INVITATION_LIMIT_REACHED);
158
+ if (ctx.context.orgOptions.teams?.enabled && "teamId" in ctx.body && ctx.body.teamId) {
159
+ if ((typeof ctx.body.teamId === "string" ? [ctx.body.teamId] : ctx.body.teamId).some((id) => id.includes(","))) throw APIError.from("BAD_REQUEST", ORGANIZATION_ERROR_CODES.INVALID_TEAM_ID);
160
+ }
158
161
  if (ctx.context.orgOptions.teams && ctx.context.orgOptions.teams.enabled && typeof ctx.context.orgOptions.teams.maximumMembersPerTeam !== "undefined" && "teamId" in ctx.body && ctx.body.teamId) {
159
162
  const teamIds = typeof ctx.body.teamId === "string" ? [ctx.body.teamId] : ctx.body.teamId;
160
163
  for (const teamId of teamIds) {
@@ -2,7 +2,7 @@ import { mergeSchema } from "../../db/schema.mjs";
2
2
  import { generateRandomString } from "../../crypto/random.mjs";
3
3
  import { symmetricEncrypt } from "../../crypto/index.mjs";
4
4
  import { deleteSessionCookie, expireCookie, setSessionCookie } from "../../cookies/index.mjs";
5
- import { sessionMiddleware } from "../../api/routes/session.mjs";
5
+ import { sensitiveSessionMiddleware, sessionMiddleware } from "../../api/routes/session.mjs";
6
6
  import { shouldRequirePassword, validatePassword } from "../../utils/password.mjs";
7
7
  import { PACKAGE_VERSION } from "../../version.mjs";
8
8
  import { TWO_FACTOR_ERROR_CODES } from "./error-code.mjs";
@@ -138,7 +138,7 @@ const twoFactor = (options) => {
138
138
  disableTwoFactor: createAuthEndpoint("/two-factor/disable", {
139
139
  method: "POST",
140
140
  body: disableTwoFactorBodySchema,
141
- use: [sessionMiddleware],
141
+ use: [sensitiveSessionMiddleware],
142
142
  metadata: { openapi: {
143
143
  summary: "Disable two factor authentication",
144
144
  description: "Use this endpoint to disable two factor authentication.",
@@ -224,6 +224,7 @@ const twoFactor = (options) => {
224
224
  */
225
225
  deleteSessionCookie(ctx, true);
226
226
  await ctx.context.internalAdapter.deleteSession(data.session.token);
227
+ ctx.context.setNewSession(null);
227
228
  const maxAge = options?.twoFactorCookieMaxAge ?? 600;
228
229
  const twoFactorCookie = ctx.context.createAuthCookie(TWO_FACTOR_COOKIE_NAME, { maxAge });
229
230
  const identifier = `2fa-${generateRandomString(20)}`;
@@ -90,9 +90,20 @@ declare const username: (options?: UsernameOptions | undefined) => {
90
90
  name: string;
91
91
  image?: string | null | undefined;
92
92
  } & Record<string, unknown>, context: _better_auth_core0.GenericEndpointContext | null): Promise<{
93
+ data: {
94
+ username: string;
95
+ displayUsername: string;
96
+ id: string;
97
+ createdAt: Date;
98
+ updatedAt: Date;
99
+ email: string;
100
+ emailVerified: boolean;
101
+ name: string;
102
+ image?: string | null | undefined;
103
+ };
104
+ } | {
93
105
  data: {
94
106
  displayUsername?: string | undefined;
95
- username?: string | undefined;
96
107
  id: string;
97
108
  createdAt: Date;
98
109
  updatedAt: Date;
@@ -115,7 +126,18 @@ declare const username: (options?: UsernameOptions | undefined) => {
115
126
  }> & Record<string, unknown>, context: _better_auth_core0.GenericEndpointContext | null): Promise<{
116
127
  data: {
117
128
  displayUsername?: string | undefined;
118
- username?: string | undefined;
129
+ username: string;
130
+ id?: string | undefined;
131
+ createdAt?: Date | undefined;
132
+ updatedAt?: Date | undefined;
133
+ email?: string | undefined;
134
+ emailVerified?: boolean | undefined;
135
+ name?: string | undefined;
136
+ image?: string | null | undefined;
137
+ };
138
+ } | {
139
+ data: {
140
+ displayUsername?: string | undefined;
119
141
  id?: string | undefined;
120
142
  createdAt?: Date | undefined;
121
143
  updatedAt?: Date | undefined;
@@ -28,6 +28,31 @@ const username = (options) => {
28
28
  const displayUsernameNormalizer = (displayUsername) => {
29
29
  return options?.displayUsernameNormalization ? options.displayUsernameNormalization(displayUsername) : displayUsername;
30
30
  };
31
+ const minUsernameLength = options?.minUsernameLength || 3;
32
+ const maxUsernameLength = options?.maxUsernameLength || 30;
33
+ const validator = options?.usernameValidator || defaultUsernameValidator;
34
+ const pathsWithHttpHookValidation = ["/sign-up/email", "/update-user"];
35
+ async function validateUsername(username, displayUsername, adapter, currentUserId) {
36
+ const usernameToValidate = options?.validationOrder?.username === "post-normalization" ? normalizer(username) : username;
37
+ if (usernameToValidate.length < minUsernameLength) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_TOO_SHORT);
38
+ if (usernameToValidate.length > maxUsernameLength) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_TOO_LONG);
39
+ if (!await validator(usernameToValidate)) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.INVALID_USERNAME);
40
+ const normalizedUsername = normalizer(username);
41
+ const existingUser = await adapter.findOne({
42
+ model: "user",
43
+ where: [{
44
+ field: "username",
45
+ value: normalizedUsername
46
+ }]
47
+ });
48
+ if (existingUser) {
49
+ if (!currentUserId || existingUser.id !== currentUserId) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.USERNAME_IS_ALREADY_TAKEN);
50
+ }
51
+ if (displayUsername && options?.displayUsernameValidator) {
52
+ const displayUsernameToValidate = options?.validationOrder?.displayUsername === "post-normalization" ? displayUsernameNormalizer(displayUsername) : displayUsername;
53
+ if (!await options.displayUsernameValidator(displayUsernameToValidate)) throw APIError.from("BAD_REQUEST", USERNAME_ERROR_CODES.INVALID_DISPLAY_USERNAME);
54
+ }
55
+ }
31
56
  return {
32
57
  id: "username",
33
58
  version: PACKAGE_VERSION,
@@ -36,18 +61,39 @@ const username = (options) => {
36
61
  create: { async before(user, context) {
37
62
  const username = "username" in user ? user.username : null;
38
63
  const displayUsername = "displayUsername" in user ? user.displayUsername : null;
64
+ const currentPath = context?.path;
65
+ const skipValidation = currentPath && pathsWithHttpHookValidation.includes(currentPath);
66
+ if (username) {
67
+ if (!skipValidation) await validateUsername(username, displayUsername, ctx.adapter);
68
+ return { data: {
69
+ ...user,
70
+ username: normalizer(username),
71
+ displayUsername: displayUsername ? displayUsernameNormalizer(displayUsername) : username
72
+ } };
73
+ }
39
74
  return { data: {
40
75
  ...user,
41
- ...username ? { username: normalizer(username) } : {},
42
76
  ...displayUsername ? { displayUsername: displayUsernameNormalizer(displayUsername) } : {}
43
77
  } };
44
78
  } },
45
79
  update: { async before(user, context) {
46
80
  const username = "username" in user ? user.username : null;
47
81
  const displayUsername = "displayUsername" in user ? user.displayUsername : null;
82
+ const currentPath = context?.path;
83
+ const skipValidation = currentPath && pathsWithHttpHookValidation.includes(currentPath);
84
+ if (username) {
85
+ if (!skipValidation) {
86
+ const currentUserId = context?.context?.session?.user?.id || ("id" in user ? user.id : null);
87
+ await validateUsername(username, displayUsername, ctx.adapter, currentUserId);
88
+ }
89
+ return { data: {
90
+ ...user,
91
+ username: normalizer(username),
92
+ ...displayUsername ? { displayUsername: displayUsernameNormalizer(displayUsername) } : {}
93
+ } };
94
+ }
48
95
  return { data: {
49
96
  ...user,
50
- ...username ? { username: normalizer(username) } : {},
51
97
  ...displayUsername ? { displayUsername: displayUsernameNormalizer(displayUsername) } : {}
52
98
  } };
53
99
  } }
@@ -153,7 +199,7 @@ const username = (options) => {
153
199
  if (!ctx.context.options?.emailVerification?.sendVerificationEmail) throw APIError.from("FORBIDDEN", USERNAME_ERROR_CODES.EMAIL_NOT_VERIFIED);
154
200
  if (ctx.context.options?.emailVerification?.sendOnSignIn) {
155
201
  const token = await createEmailVerificationToken(ctx.context.secret, user.email, void 0, ctx.context.options.emailVerification?.expiresIn);
156
- const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${ctx.body.callbackURL || "/"}`;
202
+ const url = `${ctx.context.baseURL}/verify-email?token=${token}&callbackURL=${encodeURIComponent(ctx.body.callbackURL || "/")}`;
157
203
  await ctx.context.runInBackgroundOrAwait(ctx.context.options.emailVerification.sendVerificationEmail({
158
204
  user,
159
205
  url,
package/dist/state.d.mts CHANGED
@@ -27,8 +27,8 @@ declare function generateGenericState(c: GenericEndpointContext, stateData: Stat
27
27
  state: string;
28
28
  codeVerifier: string;
29
29
  }>;
30
- declare function parseGenericState(c: GenericEndpointContext, state: string, settings?: {
31
- cookieName: string;
30
+ declare function parseGenericState(c: GenericEndpointContext, state: string | undefined, settings?: {
31
+ cookieName?: string;
32
32
  skipStateCookieCheck?: boolean;
33
33
  }): Promise<{
34
34
  [x: string]: unknown;
package/dist/state.mjs CHANGED
@@ -20,10 +20,19 @@ const stateDataSchema = z.looseObject({
20
20
  var StateError = class extends BetterAuthError {
21
21
  code;
22
22
  details;
23
+ /**
24
+ * The per-flow `errorCallbackURL` recovered from the parsed state, when the
25
+ * failure happened after the state was successfully parsed (for example a
26
+ * nonce or state-cookie mismatch). It was origin-validated at sign-in, so
27
+ * the callback can safely redirect there instead of the default error page.
28
+ * Absent when the state could not be parsed at all.
29
+ */
30
+ errorURL;
23
31
  constructor(message, options) {
24
32
  super(message, options);
25
33
  this.code = options.code;
26
34
  this.details = options.details;
35
+ this.errorURL = options.errorURL;
27
36
  }
28
37
  };
29
38
  async function generateGenericState(c, stateData, settings) {
@@ -62,6 +71,7 @@ async function generateGenericState(c, stateData, settings) {
62
71
  };
63
72
  }
64
73
  async function parseGenericState(c, state, settings) {
74
+ if (!state) throw new StateError("State not found in OAuth callback", { code: "state_not_found" });
65
75
  const storeStateStrategy = c.context.oauthConfig.storeStateStrategy;
66
76
  let parsedData;
67
77
  if (storeStateStrategy === "cookie") {
@@ -86,7 +96,8 @@ async function parseGenericState(c, state, settings) {
86
96
  }
87
97
  if (!parsedData.oauthState || parsedData.oauthState !== state) throw new StateError("State mismatch: OAuth state parameter does not match stored state", {
88
98
  code: "state_security_mismatch",
89
- details: { state }
99
+ details: { state },
100
+ errorURL: parsedData.errorURL
90
101
  });
91
102
  expireCookie(c, stateCookie);
92
103
  } else {
@@ -98,20 +109,23 @@ async function parseGenericState(c, state, settings) {
98
109
  parsedData = stateDataSchema.parse(JSON.parse(data.value));
99
110
  if (parsedData.oauthState !== void 0 && parsedData.oauthState !== state) throw new StateError("State mismatch: OAuth state parameter does not match stored state", {
100
111
  code: "state_security_mismatch",
101
- details: { state }
112
+ details: { state },
113
+ errorURL: parsedData.errorURL
102
114
  });
103
115
  const stateCookie = c.context.createAuthCookie(settings?.cookieName ?? "state");
104
116
  const stateCookieValue = await c.getSignedCookie(stateCookie.name, c.context.secret);
105
117
  if (!(settings?.skipStateCookieCheck ?? c.context.oauthConfig.skipStateCookieCheck) && (!stateCookieValue || stateCookieValue !== state)) throw new StateError("State mismatch: State not persisted correctly", {
106
118
  code: "state_security_mismatch",
107
- details: { state }
119
+ details: { state },
120
+ errorURL: parsedData.errorURL
108
121
  });
109
122
  expireCookie(c, stateCookie);
110
123
  await c.context.internalAdapter.deleteVerificationByIdentifier(state);
111
124
  }
112
125
  if (parsedData.expiresAt < Date.now()) throw new StateError("Invalid state: request expired", {
113
126
  code: "state_mismatch",
114
- details: { expiresAt: parsedData.expiresAt }
127
+ details: { expiresAt: parsedData.expiresAt },
128
+ errorURL: parsedData.errorURL
115
129
  });
116
130
  return parsedData;
117
131
  }
@@ -1,3 +1,4 @@
1
+ import { applySetCookies } from "../cookies/cookie-utils.mjs";
1
2
  //#region src/test-utils/headers.ts
2
3
  /**
3
4
  * converts set cookie containing headers to
@@ -9,13 +10,7 @@ function convertSetCookieToCookie(headers) {
9
10
  if (name.toLowerCase() === "set-cookie") setCookieHeaders.push(value);
10
11
  });
11
12
  if (setCookieHeaders.length === 0) return headers;
12
- const existingCookies = headers.get("cookie") || "";
13
- const cookies = existingCookies ? existingCookies.split("; ") : [];
14
- setCookieHeaders.forEach((setCookie) => {
15
- const cookiePair = setCookie.split(";")[0];
16
- cookies.push(cookiePair.trim());
17
- });
18
- headers.set("cookie", cookies.join("; "));
13
+ applySetCookies(headers, setCookieHeaders);
19
14
  return headers;
20
15
  }
21
16
  //#endregion
@@ -240,7 +240,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
240
240
  allowedMediaTypes: string[];
241
241
  scope: "server";
242
242
  };
243
- }, void>;
243
+ }, never>;
244
244
  readonly getSession: better_call0.StrictEndpoint<"/get-session", {
245
245
  method: ("GET" | "POST")[];
246
246
  operationId: string;
@@ -339,6 +339,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
339
339
  callbackURL: zod.ZodOptional<zod.ZodString>;
340
340
  rememberMe: zod.ZodOptional<zod.ZodBoolean>;
341
341
  }, zod_v4_core0.$strip>, zod.ZodRecord<zod.ZodString, zod.ZodAny>>;
342
+ cloneRequest: true;
342
343
  metadata: {
343
344
  allowedMediaTypes: string[];
344
345
  $Infer: {
@@ -505,6 +506,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
505
506
  method: "POST";
506
507
  operationId: string;
507
508
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
509
+ cloneRequest: true;
508
510
  body: zod.ZodObject<{
509
511
  email: zod.ZodString;
510
512
  password: zod.ZodString;
@@ -736,6 +738,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
736
738
  readonly sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
737
739
  method: "POST";
738
740
  operationId: string;
741
+ cloneRequest: true;
739
742
  body: zod.ZodObject<{
740
743
  email: zod.ZodEmail;
741
744
  callbackURL: zod.ZodOptional<zod.ZodString>;
@@ -2242,7 +2245,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
2242
2245
  allowedMediaTypes: string[];
2243
2246
  scope: "server";
2244
2247
  };
2245
- }, void>;
2248
+ }, never>;
2246
2249
  readonly getSession: better_call0.StrictEndpoint<"/get-session", {
2247
2250
  method: ("GET" | "POST")[];
2248
2251
  operationId: string;
@@ -2341,6 +2344,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
2341
2344
  callbackURL: zod.ZodOptional<zod.ZodString>;
2342
2345
  rememberMe: zod.ZodOptional<zod.ZodBoolean>;
2343
2346
  }, zod_v4_core0.$strip>, zod.ZodRecord<zod.ZodString, zod.ZodAny>>;
2347
+ cloneRequest: true;
2344
2348
  metadata: {
2345
2349
  allowedMediaTypes: string[];
2346
2350
  $Infer: {
@@ -2507,6 +2511,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
2507
2511
  method: "POST";
2508
2512
  operationId: string;
2509
2513
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
2514
+ cloneRequest: true;
2510
2515
  body: zod.ZodObject<{
2511
2516
  email: zod.ZodString;
2512
2517
  password: zod.ZodString;
@@ -2738,6 +2743,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
2738
2743
  readonly sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
2739
2744
  method: "POST";
2740
2745
  operationId: string;
2746
+ cloneRequest: true;
2741
2747
  body: zod.ZodObject<{
2742
2748
  email: zod.ZodEmail;
2743
2749
  callbackURL: zod.ZodOptional<zod.ZodString>;
@@ -4247,7 +4253,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
4247
4253
  allowedMediaTypes: string[];
4248
4254
  scope: "server";
4249
4255
  };
4250
- }, void>;
4256
+ }, never>;
4251
4257
  readonly getSession: better_call0.StrictEndpoint<"/get-session", {
4252
4258
  method: ("GET" | "POST")[];
4253
4259
  operationId: string;
@@ -4346,6 +4352,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
4346
4352
  callbackURL: zod.ZodOptional<zod.ZodString>;
4347
4353
  rememberMe: zod.ZodOptional<zod.ZodBoolean>;
4348
4354
  }, zod_v4_core0.$strip>, zod.ZodRecord<zod.ZodString, zod.ZodAny>>;
4355
+ cloneRequest: true;
4349
4356
  metadata: {
4350
4357
  allowedMediaTypes: string[];
4351
4358
  $Infer: {
@@ -4512,6 +4519,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
4512
4519
  method: "POST";
4513
4520
  operationId: string;
4514
4521
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
4522
+ cloneRequest: true;
4515
4523
  body: zod.ZodObject<{
4516
4524
  email: zod.ZodString;
4517
4525
  password: zod.ZodString;
@@ -4743,6 +4751,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
4743
4751
  readonly sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
4744
4752
  method: "POST";
4745
4753
  operationId: string;
4754
+ cloneRequest: true;
4746
4755
  body: zod.ZodObject<{
4747
4756
  email: zod.ZodEmail;
4748
4757
  callbackURL: zod.ZodOptional<zod.ZodString>;
@@ -6249,7 +6258,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
6249
6258
  allowedMediaTypes: string[];
6250
6259
  scope: "server";
6251
6260
  };
6252
- }, void>;
6261
+ }, never>;
6253
6262
  readonly getSession: better_call0.StrictEndpoint<"/get-session", {
6254
6263
  method: ("GET" | "POST")[];
6255
6264
  operationId: string;
@@ -6348,6 +6357,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
6348
6357
  callbackURL: zod.ZodOptional<zod.ZodString>;
6349
6358
  rememberMe: zod.ZodOptional<zod.ZodBoolean>;
6350
6359
  }, zod_v4_core0.$strip>, zod.ZodRecord<zod.ZodString, zod.ZodAny>>;
6360
+ cloneRequest: true;
6351
6361
  metadata: {
6352
6362
  allowedMediaTypes: string[];
6353
6363
  $Infer: {
@@ -6514,6 +6524,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
6514
6524
  method: "POST";
6515
6525
  operationId: string;
6516
6526
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
6527
+ cloneRequest: true;
6517
6528
  body: zod.ZodObject<{
6518
6529
  email: zod.ZodString;
6519
6530
  password: zod.ZodString;
@@ -6745,6 +6756,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
6745
6756
  readonly sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
6746
6757
  method: "POST";
6747
6758
  operationId: string;
6759
+ cloneRequest: true;
6748
6760
  body: zod.ZodObject<{
6749
6761
  email: zod.ZodEmail;
6750
6762
  callbackURL: zod.ZodOptional<zod.ZodString>;
@@ -8325,7 +8337,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
8325
8337
  allowedMediaTypes: string[];
8326
8338
  scope: "server";
8327
8339
  };
8328
- }, void>;
8340
+ }, never>;
8329
8341
  readonly getSession: better_call0.StrictEndpoint<"/get-session", {
8330
8342
  method: ("GET" | "POST")[];
8331
8343
  operationId: string;
@@ -8424,6 +8436,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
8424
8436
  callbackURL: zod.ZodOptional<zod.ZodString>;
8425
8437
  rememberMe: zod.ZodOptional<zod.ZodBoolean>;
8426
8438
  }, zod_v4_core0.$strip>, zod.ZodRecord<zod.ZodString, zod.ZodAny>>;
8439
+ cloneRequest: true;
8427
8440
  metadata: {
8428
8441
  allowedMediaTypes: string[];
8429
8442
  $Infer: {
@@ -8590,6 +8603,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
8590
8603
  method: "POST";
8591
8604
  operationId: string;
8592
8605
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
8606
+ cloneRequest: true;
8593
8607
  body: zod.ZodObject<{
8594
8608
  email: zod.ZodString;
8595
8609
  password: zod.ZodString;
@@ -8821,6 +8835,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
8821
8835
  readonly sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
8822
8836
  method: "POST";
8823
8837
  operationId: string;
8838
+ cloneRequest: true;
8824
8839
  body: zod.ZodObject<{
8825
8840
  email: zod.ZodEmail;
8826
8841
  callbackURL: zod.ZodOptional<zod.ZodString>;
@@ -10327,7 +10342,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
10327
10342
  allowedMediaTypes: string[];
10328
10343
  scope: "server";
10329
10344
  };
10330
- }, void>;
10345
+ }, never>;
10331
10346
  readonly getSession: better_call0.StrictEndpoint<"/get-session", {
10332
10347
  method: ("GET" | "POST")[];
10333
10348
  operationId: string;
@@ -10426,6 +10441,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
10426
10441
  callbackURL: zod.ZodOptional<zod.ZodString>;
10427
10442
  rememberMe: zod.ZodOptional<zod.ZodBoolean>;
10428
10443
  }, zod_v4_core0.$strip>, zod.ZodRecord<zod.ZodString, zod.ZodAny>>;
10444
+ cloneRequest: true;
10429
10445
  metadata: {
10430
10446
  allowedMediaTypes: string[];
10431
10447
  $Infer: {
@@ -10592,6 +10608,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
10592
10608
  method: "POST";
10593
10609
  operationId: string;
10594
10610
  use: ((inputContext: better_call0.MiddlewareInputContext<better_call0.MiddlewareOptions>) => Promise<void>)[];
10611
+ cloneRequest: true;
10595
10612
  body: zod.ZodObject<{
10596
10613
  email: zod.ZodString;
10597
10614
  password: zod.ZodString;
@@ -10823,6 +10840,7 @@ declare function getTestInstance<O extends Partial<BetterAuthOptions>, C extends
10823
10840
  readonly sendVerificationEmail: better_call0.StrictEndpoint<"/send-verification-email", {
10824
10841
  method: "POST";
10825
10842
  operationId: string;
10843
+ cloneRequest: true;
10826
10844
  body: zod.ZodObject<{
10827
10845
  email: zod.ZodEmail;
10828
10846
  callbackURL: zod.ZodOptional<zod.ZodString>;
@@ -1,4 +1,4 @@
1
1
  import { generateState, parseState } from "../oauth2/state.mjs";
2
2
  import { StateData, generateGenericState, parseGenericState } from "../state.mjs";
3
3
  import { HIDE_METADATA } from "./hide-metadata.mjs";
4
- import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL } from "./url.mjs";
4
+ import { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes } from "./url.mjs";
@@ -1,6 +1,7 @@
1
1
  import { BaseURLConfig, DynamicBaseURLConfig } from "@better-auth/core";
2
2
 
3
3
  //#region src/utils/url.d.ts
4
+ declare function trimTrailingSlashes(value: string): string;
4
5
  declare function getBaseURL(url?: string, path?: string, request?: Request, loadEnv?: boolean, trustedProxyHeaders?: boolean | undefined): string | undefined;
5
6
  declare function getOrigin(url: string): string | null;
6
7
  declare function getProtocol(url: string): string | null;
@@ -74,4 +75,4 @@ declare function resolveDynamicBaseURL(config: DynamicBaseURLConfig, source: Req
74
75
  */
75
76
  declare function resolveBaseURL(config: BaseURLConfig | undefined, basePath: string, source?: Request | Headers, loadEnv?: boolean, trustedProxyHeaders?: boolean): string | undefined;
76
77
  //#endregion
77
- export { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL };
78
+ export { getBaseURL, getHost, getHostFromSource, getOrigin, getProtocol, getProtocolFromSource, isDynamicBaseURLConfig, isRequestLike, matchesHostPattern, resolveBaseURL, resolveDynamicBaseURL, trimTrailingSlashes };