better-auth 1.5.4 → 1.5.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 (117) hide show
  1. package/dist/adapters/index.d.mts +25 -1
  2. package/dist/adapters/index.mjs +9 -1
  3. package/dist/adapters/index.mjs.map +1 -0
  4. package/dist/api/index.d.mts +36 -10
  5. package/dist/api/index.mjs +19 -4
  6. package/dist/api/index.mjs.map +1 -1
  7. package/dist/api/middlewares/origin-check.mjs +17 -8
  8. package/dist/api/middlewares/origin-check.mjs.map +1 -1
  9. package/dist/api/routes/account.d.mts +1 -1
  10. package/dist/api/routes/email-verification.d.mts +0 -1
  11. package/dist/api/routes/password.d.mts +1 -0
  12. package/dist/api/routes/password.mjs +2 -1
  13. package/dist/api/routes/password.mjs.map +1 -1
  14. package/dist/api/routes/session.d.mts +0 -1
  15. package/dist/api/routes/sign-in.d.mts +16 -2
  16. package/dist/api/routes/sign-in.mjs +10 -2
  17. package/dist/api/routes/sign-in.mjs.map +1 -1
  18. package/dist/api/routes/sign-up.d.mts +0 -1
  19. package/dist/api/routes/sign-up.mjs +3 -2
  20. package/dist/api/routes/sign-up.mjs.map +1 -1
  21. package/dist/api/routes/update-session.d.mts +0 -1
  22. package/dist/api/routes/update-user.d.mts +0 -1
  23. package/dist/api/to-auth-endpoints.mjs +49 -12
  24. package/dist/api/to-auth-endpoints.mjs.map +1 -1
  25. package/dist/auth/full.d.mts +0 -1
  26. package/dist/auth/minimal.d.mts +0 -1
  27. package/dist/client/index.d.mts +3 -4
  28. package/dist/client/index.mjs.map +1 -1
  29. package/dist/client/path-to-object.d.mts +9 -2
  30. package/dist/client/query.mjs +3 -2
  31. package/dist/client/query.mjs.map +1 -1
  32. package/dist/client/session-refresh.d.mts +11 -3
  33. package/dist/client/session-refresh.mjs +13 -8
  34. package/dist/client/session-refresh.mjs.map +1 -1
  35. package/dist/client/types.d.mts +0 -1
  36. package/dist/context/create-context.mjs +4 -1
  37. package/dist/context/create-context.mjs.map +1 -1
  38. package/dist/context/helpers.mjs +10 -4
  39. package/dist/context/helpers.mjs.map +1 -1
  40. package/dist/cookies/index.d.mts +0 -1
  41. package/dist/cookies/session-store.d.mts +0 -2
  42. package/dist/db/get-migration.mjs +3 -2
  43. package/dist/db/get-migration.mjs.map +1 -1
  44. package/dist/db/index.d.mts +2 -2
  45. package/dist/db/internal-adapter.d.mts +2 -1
  46. package/dist/db/internal-adapter.mjs +1 -1
  47. package/dist/db/internal-adapter.mjs.map +1 -1
  48. package/dist/db/schema.d.mts +0 -1
  49. package/dist/db/with-hooks.d.mts +6 -2
  50. package/dist/db/with-hooks.mjs +72 -31
  51. package/dist/db/with-hooks.mjs.map +1 -1
  52. package/dist/index.d.mts +0 -2
  53. package/dist/integrations/node.d.mts +0 -1
  54. package/dist/oauth2/link-account.d.mts +0 -1
  55. package/dist/plugins/admin/access/statement.d.mts +0 -2
  56. package/dist/plugins/admin/admin.d.mts +0 -1
  57. package/dist/plugins/admin/client.d.mts +0 -2
  58. package/dist/plugins/admin/types.d.mts +0 -2
  59. package/dist/plugins/anonymous/types.d.mts +0 -1
  60. package/dist/plugins/email-otp/index.mjs +2 -1
  61. package/dist/plugins/email-otp/index.mjs.map +1 -1
  62. package/dist/plugins/email-otp/otp-token.mjs +31 -2
  63. package/dist/plugins/email-otp/otp-token.mjs.map +1 -1
  64. package/dist/plugins/email-otp/routes.mjs +60 -59
  65. package/dist/plugins/email-otp/routes.mjs.map +1 -1
  66. package/dist/plugins/email-otp/types.d.mts +12 -0
  67. package/dist/plugins/email-otp/utils.mjs +4 -1
  68. package/dist/plugins/email-otp/utils.mjs.map +1 -1
  69. package/dist/plugins/generic-oauth/client.d.mts +0 -1
  70. package/dist/plugins/generic-oauth/index.d.mts +0 -1
  71. package/dist/plugins/index.d.mts +0 -3
  72. package/dist/plugins/jwt/types.d.mts +0 -1
  73. package/dist/plugins/magic-link/index.d.mts +2 -0
  74. package/dist/plugins/magic-link/index.mjs +5 -3
  75. package/dist/plugins/magic-link/index.mjs.map +1 -1
  76. package/dist/plugins/mcp/index.d.mts +0 -1
  77. package/dist/plugins/oidc-provider/authorize.mjs +13 -4
  78. package/dist/plugins/oidc-provider/authorize.mjs.map +1 -1
  79. package/dist/plugins/oidc-provider/error.mjs +12 -2
  80. package/dist/plugins/oidc-provider/error.mjs.map +1 -1
  81. package/dist/plugins/oidc-provider/index.d.mts +0 -1
  82. package/dist/plugins/oidc-provider/types.d.mts +0 -1
  83. package/dist/plugins/one-time-token/index.d.mts +0 -1
  84. package/dist/plugins/organization/access/statement.d.mts +0 -2
  85. package/dist/plugins/organization/adapter.d.mts +0 -2
  86. package/dist/plugins/organization/adapter.mjs +2 -2
  87. package/dist/plugins/organization/adapter.mjs.map +1 -1
  88. package/dist/plugins/organization/client.d.mts +0 -5
  89. package/dist/plugins/organization/organization.d.mts +0 -2
  90. package/dist/plugins/organization/permission.d.mts +0 -1
  91. package/dist/plugins/organization/routes/crud-access-control.d.mts +0 -2
  92. package/dist/plugins/organization/routes/crud-invites.d.mts +0 -3
  93. package/dist/plugins/organization/routes/crud-invites.mjs +1 -1
  94. package/dist/plugins/organization/routes/crud-invites.mjs.map +1 -1
  95. package/dist/plugins/organization/routes/crud-members.d.mts +0 -3
  96. package/dist/plugins/organization/routes/crud-members.mjs +1 -1
  97. package/dist/plugins/organization/routes/crud-members.mjs.map +1 -1
  98. package/dist/plugins/organization/routes/crud-org.d.mts +0 -3
  99. package/dist/plugins/organization/routes/crud-team.d.mts +2 -3
  100. package/dist/plugins/organization/routes/crud-team.mjs +18 -14
  101. package/dist/plugins/organization/routes/crud-team.mjs.map +1 -1
  102. package/dist/plugins/organization/schema.d.mts +0 -1
  103. package/dist/plugins/organization/types.d.mts +0 -2
  104. package/dist/plugins/phone-number/types.d.mts +0 -1
  105. package/dist/plugins/siwe/index.d.mts +0 -1
  106. package/dist/plugins/test-utils/types.d.mts +0 -2
  107. package/dist/plugins/two-factor/client.d.mts +7 -0
  108. package/dist/plugins/two-factor/client.mjs +5 -1
  109. package/dist/plugins/two-factor/client.mjs.map +1 -1
  110. package/dist/plugins/two-factor/index.mjs +7 -1
  111. package/dist/plugins/two-factor/index.mjs.map +1 -1
  112. package/dist/plugins/two-factor/otp/index.d.mts +2 -2
  113. package/dist/plugins/two-factor/otp/index.mjs.map +1 -1
  114. package/dist/plugins/two-factor/types.d.mts +7 -1
  115. package/dist/test-utils/test-instance.d.mts +108 -21
  116. package/dist/types/index.d.mts +0 -1
  117. package/package.json +13 -10
@@ -6,8 +6,8 @@ import { setCookieCache, setSessionCookie } from "../../cookies/index.mjs";
6
6
  import { getSessionFromCtx, sensitiveSessionMiddleware } from "../../api/routes/session.mjs";
7
7
  import { APIError as APIError$1 } from "../../api/index.mjs";
8
8
  import { EMAIL_OTP_ERROR_CODES } from "./error-codes.mjs";
9
- import { splitAtLastColon } from "./utils.mjs";
10
- import { storeOTP, verifyStoredOTP } from "./otp-token.mjs";
9
+ import { splitAtLastColon, toOTPIdentifier } from "./utils.mjs";
10
+ import { storeOTP, tryReuseOTP, verifyStoredOTP } from "./otp-token.mjs";
11
11
  import { BASE_ERROR_CODES } from "@better-auth/core/error";
12
12
  import { createAuthEndpoint } from "@better-auth/core/api";
13
13
  import { deprecate } from "@better-auth/core/utils/deprecate";
@@ -20,6 +20,37 @@ const types = [
20
20
  "forget-password",
21
21
  "change-email"
22
22
  ];
23
+ /**
24
+ * Resolves the OTP to send: reuses an existing one if possible,
25
+ * otherwise generates and stores a new one.
26
+ *
27
+ * @internal
28
+ */
29
+ async function resolveOTP(ctx, opts, email, type) {
30
+ const identifier = toOTPIdentifier(type, email);
31
+ if (opts.resendStrategy === "reuse") {
32
+ const reused = await tryReuseOTP(ctx, opts, identifier);
33
+ if (reused) return reused;
34
+ }
35
+ const otp = opts.generateOTP({
36
+ email,
37
+ type
38
+ }, ctx) || defaultOTPGenerator(opts);
39
+ const storedOTP = await storeOTP(ctx, opts, otp);
40
+ await ctx.context.internalAdapter.createVerificationValue({
41
+ value: `${storedOTP}:0`,
42
+ identifier,
43
+ expiresAt: getDate(opts.expiresIn, "sec")
44
+ }).catch(async () => {
45
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
46
+ await ctx.context.internalAdapter.createVerificationValue({
47
+ value: `${storedOTP}:0`,
48
+ identifier,
49
+ expiresAt: getDate(opts.expiresIn, "sec")
50
+ });
51
+ });
52
+ return otp;
53
+ }
23
54
  const sendVerificationOTPBodySchema = z.object({
24
55
  email: z.string({}).meta({ description: "Email address to send the OTP" }),
25
56
  type: z.enum(types).meta({ description: "Type of the OTP" })
@@ -64,25 +95,11 @@ const sendVerificationOTP = (opts) => createAuthEndpoint("/email-otp/send-verifi
64
95
  ctx.context.logger.error("Use the /email-otp/request-email-change endpoint to send OTP for changing email");
65
96
  throw APIError$1.fromStatus("BAD_REQUEST", { message: "Invalid OTP type" });
66
97
  }
67
- const otp = opts.generateOTP({
68
- email,
69
- type: ctx.body.type
70
- }, ctx) || defaultOTPGenerator(opts);
71
- const storedOTP = await storeOTP(ctx, opts, otp);
72
- await ctx.context.internalAdapter.createVerificationValue({
73
- value: `${storedOTP}:0`,
74
- identifier: `${ctx.body.type}-otp-${email}`,
75
- expiresAt: getDate(opts.expiresIn, "sec")
76
- }).catch(async (error) => {
77
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`${ctx.body.type}-otp-${email}`);
78
- await ctx.context.internalAdapter.createVerificationValue({
79
- value: `${storedOTP}:0`,
80
- identifier: `${ctx.body.type}-otp-${email}`,
81
- expiresAt: getDate(opts.expiresIn, "sec")
82
- });
83
- });
84
- if (!await ctx.context.internalAdapter.findUserByEmail(email)) if (ctx.body.type === "sign-in" && !opts.disableSignUp) {} else {
85
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`${ctx.body.type}-otp-${email}`);
98
+ const identifier = toOTPIdentifier(ctx.body.type, email);
99
+ const otp = await resolveOTP(ctx, opts, email, ctx.body.type);
100
+ const shouldSendOTP = ctx.body.type === "sign-in" && !opts.disableSignUp;
101
+ if (!await ctx.context.internalAdapter.findUserByEmail(email) && !shouldSendOTP) {
102
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
86
103
  return ctx.json({ success: true });
87
104
  }
88
105
  await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
@@ -119,7 +136,7 @@ const createVerificationOTP = (opts) => createAuthEndpoint({
119
136
  const storedOTP = await storeOTP(ctx, opts, otp);
120
137
  await ctx.context.internalAdapter.createVerificationValue({
121
138
  value: `${storedOTP}:0`,
122
- identifier: `${ctx.body.type}-otp-${email}`,
139
+ identifier: toOTPIdentifier(ctx.body.type, email),
123
140
  expiresAt: getDate(opts.expiresIn, "sec")
124
141
  });
125
142
  return otp;
@@ -164,7 +181,7 @@ const getVerificationOTP = (opts) => createAuthEndpoint({
164
181
  } }
165
182
  }, async (ctx) => {
166
183
  const email = ctx.query.email.toLowerCase();
167
- const verificationValue = await ctx.context.internalAdapter.findVerificationValue(`${ctx.query.type}-otp-${email}`);
184
+ const verificationValue = await ctx.context.internalAdapter.findVerificationValue(toOTPIdentifier(ctx.query.type, email));
168
185
  if (!verificationValue || verificationValue.expiresAt < /* @__PURE__ */ new Date()) return ctx.json({ otp: null });
169
186
  if (opts.storeOTP === "hashed" || typeof opts.storeOTP === "object" && "hash" in opts.storeOTP) throw APIError$1.fromStatus("BAD_REQUEST", { message: "OTP is hashed, cannot return the plain text OTP" });
170
187
  const [storedOtp, _attempts] = splitAtLastColon(verificationValue.value);
@@ -217,21 +234,21 @@ const checkVerificationOTP = (opts) => createAuthEndpoint("/email-otp/check-veri
217
234
  const email = ctx.body.email.toLowerCase();
218
235
  if (!z.email().safeParse(email).success) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_EMAIL);
219
236
  if (!await ctx.context.internalAdapter.findUserByEmail(email)) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
220
- const verificationValue = await ctx.context.internalAdapter.findVerificationValue(`${ctx.body.type}-otp-${email}`);
237
+ const identifier = toOTPIdentifier(ctx.body.type, email);
238
+ const verificationValue = await ctx.context.internalAdapter.findVerificationValue(identifier);
221
239
  if (!verificationValue) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
222
- const otpIdentifier = `${ctx.body.type}-otp-${email}`;
223
240
  if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {
224
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(otpIdentifier);
241
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
225
242
  throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.OTP_EXPIRED);
226
243
  }
227
244
  const [otpValue, attempts] = splitAtLastColon(verificationValue.value);
228
245
  const allowedAttempts = opts?.allowedAttempts || 3;
229
246
  if (attempts && parseInt(attempts) >= allowedAttempts) {
230
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(otpIdentifier);
247
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
231
248
  throw APIError$1.from("FORBIDDEN", EMAIL_OTP_ERROR_CODES.TOO_MANY_ATTEMPTS);
232
249
  }
233
250
  if (!await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp)) {
234
- await ctx.context.internalAdapter.updateVerificationByIdentifier(otpIdentifier, { value: `${otpValue}:${parseInt(attempts || "0") + 1}` });
251
+ await ctx.context.internalAdapter.updateVerificationByIdentifier(identifier, { value: `${otpValue}:${parseInt(attempts || "0") + 1}` });
235
252
  throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
236
253
  }
237
254
  return ctx.json({ success: true });
@@ -291,7 +308,7 @@ const verifyEmailOTP = (opts) => createAuthEndpoint("/email-otp/verify-email", {
291
308
  }, async (ctx) => {
292
309
  const email = ctx.body.email.toLowerCase();
293
310
  if (!z.email().safeParse(email).success) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.INVALID_EMAIL);
294
- await atomicVerifyOTP(ctx, opts, `email-verification-otp-${email}`, ctx.body.otp);
311
+ await atomicVerifyOTP(ctx, opts, toOTPIdentifier("email-verification", email), ctx.body.otp);
295
312
  const user = await ctx.context.internalAdapter.findUserByEmail(email);
296
313
  if (!user)
297
314
  /**
@@ -382,7 +399,7 @@ const signInEmailOTP = (opts) => createAuthEndpoint("/sign-in/email-otp", {
382
399
  }, async (ctx) => {
383
400
  const { email: rawEmail, otp, name, image, ...rest } = ctx.body;
384
401
  const email = rawEmail.toLowerCase();
385
- await atomicVerifyOTP(ctx, opts, `sign-in-otp-${email}`, otp);
402
+ await atomicVerifyOTP(ctx, opts, toOTPIdentifier("sign-in", email), otp);
386
403
  const user = await ctx.context.internalAdapter.findUserByEmail(email);
387
404
  if (!user) {
388
405
  if (opts.disableSignUp) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
@@ -450,18 +467,10 @@ const requestPasswordResetEmailOTP = (opts) => createAuthEndpoint("/email-otp/re
450
467
  } }
451
468
  }, async (ctx) => {
452
469
  const email = ctx.body.email;
453
- const otp = opts.generateOTP({
454
- email,
455
- type: "forget-password"
456
- }, ctx) || defaultOTPGenerator(opts);
457
- const storedOTP = await storeOTP(ctx, opts, otp);
458
- await ctx.context.internalAdapter.createVerificationValue({
459
- value: `${storedOTP}:0`,
460
- identifier: `forget-password-otp-${email}`,
461
- expiresAt: getDate(opts.expiresIn, "sec")
462
- });
470
+ const identifier = toOTPIdentifier("forget-password", email);
471
+ const otp = await resolveOTP(ctx, opts, email, "forget-password");
463
472
  if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
464
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`forget-password-otp-${email}`);
473
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
465
474
  return ctx.json({ success: true });
466
475
  }
467
476
  await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
@@ -510,18 +519,10 @@ const forgetPasswordEmailOTP = (opts) => {
510
519
  }, async (ctx) => {
511
520
  warnDeprecation();
512
521
  const email = ctx.body.email;
513
- const otp = opts.generateOTP({
514
- email,
515
- type: "forget-password"
516
- }, ctx) || defaultOTPGenerator(opts);
517
- const storedOTP = await storeOTP(ctx, opts, otp);
518
- await ctx.context.internalAdapter.createVerificationValue({
519
- value: `${storedOTP}:0`,
520
- identifier: `forget-password-otp-${email}`,
521
- expiresAt: getDate(opts.expiresIn, "sec")
522
- });
522
+ const identifier = toOTPIdentifier("forget-password", email);
523
+ const otp = await resolveOTP(ctx, opts, email, "forget-password");
523
524
  if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
524
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`forget-password-otp-${email}`);
525
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);
525
526
  return ctx.json({ success: true });
526
527
  }
527
528
  await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
@@ -568,7 +569,7 @@ const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-pas
568
569
  } }
569
570
  }, async (ctx) => {
570
571
  const email = ctx.body.email;
571
- await atomicVerifyOTP(ctx, opts, `forget-password-otp-${email}`, ctx.body.otp);
572
+ await atomicVerifyOTP(ctx, opts, toOTPIdentifier("forget-password", email), ctx.body.otp);
572
573
  const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
573
574
  if (!user) throw APIError$1.from("BAD_REQUEST", BASE_ERROR_CODES.USER_NOT_FOUND);
574
575
  const minPasswordLength = ctx.context.password.config.minPasswordLength;
@@ -636,9 +637,9 @@ const requestEmailChangeEmailOTP = (opts) => createAuthEndpoint("/email-otp/requ
636
637
  }
637
638
  if (opts.changeEmail?.verifyCurrentEmail) {
638
639
  if (!ctx.body.otp) throw APIError$1.fromStatus("BAD_REQUEST", { message: "OTP is required to verify current email" });
639
- const currentEmailVerificationValue = await ctx.context.internalAdapter.findVerificationValue(`email-verification-otp-${email}`);
640
+ const currentEmailVerificationValue = await ctx.context.internalAdapter.findVerificationValue(toOTPIdentifier("email-verification", email));
640
641
  if (!currentEmailVerificationValue) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
641
- const currentEmailIdentifier = `email-verification-otp-${email}`;
642
+ const currentEmailIdentifier = toOTPIdentifier("email-verification", email);
642
643
  if (currentEmailVerificationValue.expiresAt < /* @__PURE__ */ new Date()) {
643
644
  await ctx.context.internalAdapter.deleteVerificationByIdentifier(currentEmailIdentifier);
644
645
  throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.OTP_EXPIRED);
@@ -662,11 +663,11 @@ const requestEmailChangeEmailOTP = (opts) => createAuthEndpoint("/email-otp/requ
662
663
  const storedOTP = await storeOTP(ctx, opts, otp);
663
664
  await ctx.context.internalAdapter.createVerificationValue({
664
665
  value: `${storedOTP}:0`,
665
- identifier: `change-email-otp-${email}-${newEmail}`,
666
+ identifier: toOTPIdentifier("change-email", `${email}-${newEmail}`),
666
667
  expiresAt: getDate(opts.expiresIn, "sec")
667
668
  });
668
669
  if (await ctx.context.internalAdapter.findUserByEmail(newEmail)) {
669
- await ctx.context.internalAdapter.deleteVerificationByIdentifier(`change-email-otp-${email}-${newEmail}`);
670
+ await ctx.context.internalAdapter.deleteVerificationByIdentifier(toOTPIdentifier("change-email", `${email}-${newEmail}`));
670
671
  return ctx.json({ success: true });
671
672
  }
672
673
  await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
@@ -723,9 +724,9 @@ const changeEmailEmailOTP = (opts) => createAuthEndpoint("/email-otp/change-emai
723
724
  ctx.context.logger.error("Email is the same");
724
725
  throw APIError$1.fromStatus("BAD_REQUEST", { message: "Email is the same" });
725
726
  }
726
- const verificationValue = await ctx.context.internalAdapter.findVerificationValue(`change-email-otp-${email}-${newEmail}`);
727
+ const verificationValue = await ctx.context.internalAdapter.findVerificationValue(toOTPIdentifier("change-email", `${email}-${newEmail}`));
727
728
  if (!verificationValue) throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.INVALID_OTP);
728
- const changeEmailIdentifier = `change-email-otp-${email}-${newEmail}`;
729
+ const changeEmailIdentifier = toOTPIdentifier("change-email", `${email}-${newEmail}`);
729
730
  if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {
730
731
  await ctx.context.internalAdapter.deleteVerificationByIdentifier(changeEmailIdentifier);
731
732
  throw APIError$1.from("BAD_REQUEST", EMAIL_OTP_ERROR_CODES.OTP_EXPIRED);
@@ -1 +1 @@
1
- {"version":3,"file":"routes.mjs","names":["APIError","ERROR_CODES"],"sources":["../../../src/plugins/email-otp/routes.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { BASE_ERROR_CODES } from \"@better-auth/core/error\";\nimport { deprecate } from \"@better-auth/core/utils/deprecate\";\nimport * as z from \"zod\";\nimport {\n\tAPIError,\n\tgetSessionFromCtx,\n\tsensitiveSessionMiddleware,\n} from \"../../api\";\nimport { setCookieCache, setSessionCookie } from \"../../cookies\";\nimport { generateRandomString, symmetricDecrypt } from \"../../crypto\";\nimport { parseUserInput, parseUserOutput } from \"../../db/schema\";\nimport { getDate } from \"../../utils/date\";\nimport { storeOTP, verifyStoredOTP } from \"./otp-token\";\nimport type { EmailOTPOptions } from \"./types\";\nimport { splitAtLastColon } from \"./utils\";\n\nconst types = [\n\t\"email-verification\",\n\t\"sign-in\",\n\t\"forget-password\",\n\t\"change-email\",\n] as const;\n\ntype WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };\n\ntype RequiredEmailOTPOptions = WithRequired<\n\tEmailOTPOptions,\n\t\"expiresIn\" | \"generateOTP\" | \"storeOTP\"\n>;\n\nimport { EMAIL_OTP_ERROR_CODES as ERROR_CODES } from \"./error-codes\";\n\nconst sendVerificationOTPBodySchema = z.object({\n\temail: z.string({}).meta({\n\t\tdescription: \"Email address to send the OTP\",\n\t}),\n\ttype: z.enum(types).meta({\n\t\tdescription: \"Type of the OTP\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/send-verification-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.sendVerificationOTP`\n *\n * **client:**\n * `authClient.emailOtp.sendVerificationOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-send-verification-otp)\n */\nexport const sendVerificationOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/send-verification-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: sendVerificationOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"sendEmailVerificationOTP\",\n\t\t\t\t\tdescription: \"Send a verification OTP to an email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tif (!opts?.sendVerificationOTP) {\n\t\t\t\tctx.context.logger.error(\"send email verification is not implemented\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"send email verification is not implemented\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst email = ctx.body.email.toLowerCase();\n\t\t\tconst isValidEmail = z.email().safeParse(email);\n\t\t\tif (!isValidEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\n\t\t\t// Enforce using the correct endpoint for change email OTP\n\t\t\tif (ctx.body.type === \"change-email\") {\n\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\"Use the /email-otp/request-email-change endpoint to send OTP for changing email\",\n\t\t\t\t);\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Invalid OTP type\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst otp =\n\t\t\t\topts.generateOTP({ email, type: ctx.body.type }, ctx) ||\n\t\t\t\tdefaultOTPGenerator(opts);\n\n\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\n\t\t\tawait ctx.context.internalAdapter\n\t\t\t\t.createVerificationValue({\n\t\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\t\tidentifier: `${ctx.body.type}-otp-${email}`,\n\t\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t\t})\n\t\t\t\t.catch(async (error) => {\n\t\t\t\t\t// might be duplicate key error\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t\t`${ctx.body.type}-otp-${email}`,\n\t\t\t\t\t);\n\t\t\t\t\t//try again\n\t\t\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\t\t\tidentifier: `${ctx.body.type}-otp-${email}`,\n\t\t\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tif (ctx.body.type === \"sign-in\" && !opts.disableSignUp) {\n\t\t\t\t\t// allow\n\t\t\t\t} else {\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t\t`${ctx.body.type}-otp-${email}`,\n\t\t\t\t\t);\n\t\t\t\t\treturn ctx.json({\n\t\t\t\t\t\tsuccess: true,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\topts.sendVerificationOTP(\n\t\t\t\t\t{\n\t\t\t\t\t\temail,\n\t\t\t\t\t\totp,\n\t\t\t\t\t\ttype: ctx.body.type,\n\t\t\t\t\t},\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst createVerificationOTPBodySchema = z.object({\n\temail: z.string({}).meta({\n\t\tdescription: \"Email address to send the OTP\",\n\t}),\n\ttype: z.enum(types).meta({\n\t\trequired: true,\n\t\tdescription: \"Type of the OTP\",\n\t}),\n});\n\nexport const createVerificationOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: createVerificationOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"createEmailVerificationOTP\",\n\t\t\t\t\tdescription: \"Create a verification OTP for an email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email.toLowerCase();\n\t\t\tconst otp =\n\t\t\t\topts.generateOTP({ email, type: ctx.body.type }, ctx) ||\n\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\tidentifier: `${ctx.body.type}-otp-${email}`,\n\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t});\n\t\t\treturn otp;\n\t\t},\n\t);\n\nconst getVerificationOTPBodySchema = z.object({\n\temail: z.string({}).meta({\n\t\tdescription: \"Email address the OTP was sent to\",\n\t}),\n\ttype: z.enum(types).meta({\n\t\trequired: true,\n\t\tdescription: \"Type of the OTP\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * GET `/email-otp/get-verification-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.getVerificationOTP`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-get-verification-otp)\n */\nexport const getVerificationOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tquery: getVerificationOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"getEmailVerificationOTP\",\n\t\t\t\t\tdescription: \"Get a verification OTP for an email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\tdescription: \"OTP retrieved successfully or not found/expired\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\totp: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The stored OTP, or null if not found or expired\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"otp\"],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.query.email.toLowerCase();\n\t\t\tconst verificationValue =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\t`${ctx.query.type}-otp-${email}`,\n\t\t\t\t);\n\t\t\tif (!verificationValue || verificationValue.expiresAt < new Date()) {\n\t\t\t\treturn ctx.json({\n\t\t\t\t\totp: null,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (\n\t\t\t\topts.storeOTP === \"hashed\" ||\n\t\t\t\t(typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP)\n\t\t\t) {\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"OTP is hashed, cannot return the plain text OTP\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst [storedOtp, _attempts] = splitAtLastColon(verificationValue.value);\n\t\t\tlet otp = storedOtp;\n\t\t\tif (opts.storeOTP === \"encrypted\") {\n\t\t\t\totp = await symmetricDecrypt({\n\t\t\t\t\tkey: ctx.context.secretConfig,\n\t\t\t\t\tdata: storedOtp,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n\t\t\t\totp = await opts.storeOTP.decrypt(storedOtp);\n\t\t\t}\n\n\t\t\treturn ctx.json({\n\t\t\t\totp,\n\t\t\t});\n\t\t},\n\t);\n\nconst checkVerificationOTPBodySchema = z.object({\n\temail: z.string().meta({\n\t\tdescription: \"Email address the OTP was sent to\",\n\t}),\n\ttype: z.enum(types).meta({\n\t\trequired: true,\n\t\tdescription: \"Type of the OTP\",\n\t}),\n\totp: z.string().meta({\n\t\trequired: true,\n\t\tdescription: \"OTP to verify\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * GET `/email-otp/check-verification-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.checkVerificationOTP`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-check-verification-otp)\n */\nexport const checkVerificationOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/check-verification-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: checkVerificationOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"verifyEmailWithOTP\",\n\t\t\t\t\tdescription: \"Verify an email with an OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email.toLowerCase();\n\t\t\tconst isValidEmail = z.email().safeParse(email);\n\t\t\tif (!isValidEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.USER_NOT_FOUND);\n\t\t\t}\n\t\t\tconst verificationValue =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\t`${ctx.body.type}-otp-${email}`,\n\t\t\t\t);\n\t\t\tif (!verificationValue) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t}\n\t\t\tconst otpIdentifier = `${ctx.body.type}-otp-${email}`;\n\t\t\tif (verificationValue.expiresAt < new Date()) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\totpIdentifier,\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.OTP_EXPIRED);\n\t\t\t}\n\n\t\t\tconst [otpValue, attempts] = splitAtLastColon(verificationValue.value);\n\t\t\tconst allowedAttempts = opts?.allowedAttempts || 3;\n\t\t\tif (attempts && parseInt(attempts) >= allowedAttempts) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\totpIdentifier,\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"FORBIDDEN\", ERROR_CODES.TOO_MANY_ATTEMPTS);\n\t\t\t}\n\t\t\tconst verified = await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp);\n\t\t\tif (!verified) {\n\t\t\t\tawait ctx.context.internalAdapter.updateVerificationByIdentifier(\n\t\t\t\t\totpIdentifier,\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: `${otpValue}:${parseInt(attempts || \"0\") + 1}`,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t}\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst verifyEmailOTPBodySchema = z.object({\n\temail: z.string({}).meta({\n\t\tdescription: \"Email address to verify\",\n\t}),\n\totp: z.string().meta({\n\t\trequired: true,\n\t\tdescription: \"OTP to verify\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/verify-email`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.verifyEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.verifyEmail`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-verify-email)\n */\nexport const verifyEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/verify-email\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: verifyEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Verify email with OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tstatus: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates if the verification was successful\",\n\t\t\t\t\t\t\t\t\t\t\t\tenum: [true],\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttoken: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Session token if autoSignInAfterVerification is enabled, otherwise null\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/User\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"status\", \"token\", \"user\"],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email.toLowerCase();\n\t\t\tconst isValidEmail = z.email().safeParse(email);\n\t\t\tif (!isValidEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\n\t\t\t// Use atomic verification to prevent race conditions\n\t\t\tawait atomicVerifyOTP(\n\t\t\t\tctx,\n\t\t\t\topts,\n\t\t\t\t`email-verification-otp-${email}`,\n\t\t\t\tctx.body.otp,\n\t\t\t);\n\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\t/**\n\t\t\t\t * safe to leak the existence of a user, given the user has already the OTP from the\n\t\t\t\t * email\n\t\t\t\t */\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.USER_NOT_FOUND);\n\t\t\t}\n\t\t\tif (ctx.context.options.emailVerification?.beforeEmailVerification) {\n\t\t\t\tawait ctx.context.options.emailVerification.beforeEmailVerification(\n\t\t\t\t\tuser.user,\n\t\t\t\t\tctx.request,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst updatedUser = await ctx.context.internalAdapter.updateUser(\n\t\t\t\tuser.user.id,\n\t\t\t\t{\n\t\t\t\t\temail,\n\t\t\t\t\temailVerified: true,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tawait ctx.context.options.emailVerification?.afterEmailVerification?.(\n\t\t\t\tupdatedUser,\n\t\t\t\tctx.request,\n\t\t\t);\n\n\t\t\tif (ctx.context.options.emailVerification?.autoSignInAfterVerification) {\n\t\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\tupdatedUser.id,\n\t\t\t\t);\n\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\tsession,\n\t\t\t\t\tuser: updatedUser,\n\t\t\t\t});\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tstatus: true,\n\t\t\t\t\ttoken: session.token,\n\t\t\t\t\tuser: parseUserOutput(ctx.context.options, updatedUser),\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst currentSession = await getSessionFromCtx(ctx);\n\t\t\tif (currentSession && updatedUser.emailVerified) {\n\t\t\t\tconst dontRememberMeCookie = await ctx.getSignedCookie(\n\t\t\t\t\tctx.context.authCookies.dontRememberToken.name,\n\t\t\t\t\tctx.context.secret,\n\t\t\t\t);\n\t\t\t\tawait setCookieCache(\n\t\t\t\t\tctx,\n\t\t\t\t\t{\n\t\t\t\t\t\tsession: currentSession.session,\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t...currentSession.user,\n\t\t\t\t\t\t\temailVerified: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t!!dontRememberMeCookie,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn ctx.json({\n\t\t\t\tstatus: true,\n\t\t\t\ttoken: null,\n\t\t\t\tuser: parseUserOutput(ctx.context.options, updatedUser),\n\t\t\t});\n\t\t},\n\t);\n\nconst signInEmailOTPBodySchema = z\n\t.object({\n\t\temail: z.string({}).meta({\n\t\t\tdescription: \"Email address to sign in\",\n\t\t}),\n\t\totp: z.string().meta({\n\t\t\trequired: true,\n\t\t\tdescription: \"OTP sent to the email\",\n\t\t}),\n\t\tname: z\n\t\t\t.string()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'User display name. Only used if the user is registering for the first time. Eg: \"my-name\"',\n\t\t\t})\n\t\t\t.optional(),\n\t\timage: z\n\t\t\t.string()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t\"User profile image URL. Only used if the user is registering for the first time.\",\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.and(z.record(z.string(), z.any()));\n\n/**\n * ### Endpoint\n *\n * POST `/sign-in/email-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.signInEmailOTP`\n *\n * **client:**\n * `authClient.signIn.emailOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-sign-in-email-otp)\n */\nexport const signInEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/sign-in/email-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: signInEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"signInWithEmailOTP\",\n\t\t\t\t\tdescription: \"Sign in with email and OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\ttoken: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Session token for the authenticated session\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/User\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"token\", \"user\"],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { email: rawEmail, otp, name, image, ...rest } = ctx.body;\n\t\t\tconst email = rawEmail.toLowerCase();\n\n\t\t\t// Use atomic verification to prevent race conditions\n\t\t\tawait atomicVerifyOTP(ctx, opts, `sign-in-otp-${email}`, otp);\n\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tif (opts.disableSignUp) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t\t}\n\t\t\t\tconst additionalFields = parseUserInput(\n\t\t\t\t\tctx.context.options,\n\t\t\t\t\trest,\n\t\t\t\t\t\"create\",\n\t\t\t\t);\n\t\t\t\tconst newUser = await ctx.context.internalAdapter.createUser({\n\t\t\t\t\t...additionalFields,\n\t\t\t\t\temail,\n\t\t\t\t\temailVerified: true,\n\t\t\t\t\tname: name || \"\",\n\t\t\t\t\timage,\n\t\t\t\t});\n\t\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\tnewUser.id,\n\t\t\t\t);\n\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\tsession,\n\t\t\t\t\tuser: newUser,\n\t\t\t\t});\n\t\t\t\treturn ctx.json({\n\t\t\t\t\ttoken: session.token,\n\t\t\t\t\tuser: parseUserOutput(ctx.context.options, newUser),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (!user.user.emailVerified) {\n\t\t\t\tawait ctx.context.internalAdapter.updateUser(user.user.id, {\n\t\t\t\t\temailVerified: true,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\tuser.user.id,\n\t\t\t);\n\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\tsession,\n\t\t\t\tuser: user.user,\n\t\t\t});\n\t\t\treturn ctx.json({\n\t\t\t\ttoken: session.token,\n\t\t\t\tuser: parseUserOutput(ctx.context.options, user.user),\n\t\t\t});\n\t\t},\n\t);\n\nconst requestPasswordResetEmailOTPBodySchema = z.object({\n\temail: z.string().meta({\n\t\tdescription: \"Email address to send the OTP\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/request-password-reset`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.requestPasswordResetEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.requestPasswordReset`\n *\n * @see [Read our docs to learn more.](https://www.better-auth.com/docs/plugins/email-otp#reset-password-with-otp)\n */\nexport const requestPasswordResetEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/request-password-reset\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: requestPasswordResetEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"requestPasswordResetWithEmailOTP\",\n\t\t\t\t\tdescription: \"Request password reset with email and OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates if the OTP was sent successfully\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email;\n\t\t\tconst otp =\n\t\t\t\topts.generateOTP({ email, type: \"forget-password\" }, ctx) ||\n\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\tidentifier: `forget-password-otp-${email}`,\n\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t});\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t`forget-password-otp-${email}`,\n\t\t\t\t);\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\topts.sendVerificationOTP(\n\t\t\t\t\t{\n\t\t\t\t\t\temail,\n\t\t\t\t\t\totp,\n\t\t\t\t\t\ttype: \"forget-password\",\n\t\t\t\t\t},\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst forgetPasswordEmailOTPBodySchema = z.object({\n\temail: z.string().meta({\n\t\tdescription: \"Email address to send the OTP\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/forget-password/email-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.forgetPasswordEmailOTP`\n *\n * **client:**\n * `authClient.forgetPassword.emailOtp`\n *\n * @deprecated Use `/email-otp/request-password-reset` instead.\n * @see [Read our docs to learn more.](https://www.better-auth.com/docs/plugins/email-otp#reset-password-with-otp)\n */\nexport const forgetPasswordEmailOTP = (opts: RequiredEmailOTPOptions) => {\n\tconst warnDeprecation = deprecate(\n\t\t() => {},\n\t\t'The \"/forget-password/email-otp\" endpoint is deprecated. ' +\n\t\t\t'Please use \"/email-otp/request-password-reset\" instead. ' +\n\t\t\t\"This endpoint will be removed in the next major version.\",\n\t);\n\n\treturn createAuthEndpoint(\n\t\t\"/forget-password/email-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: forgetPasswordEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"forgetPasswordWithEmailOTP\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Deprecated: Use /email-otp/request-password-reset instead.\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates if the OTP was sent successfully\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\twarnDeprecation();\n\t\t\tconst email = ctx.body.email;\n\t\t\tconst otp =\n\t\t\t\topts.generateOTP({ email, type: \"forget-password\" }, ctx) ||\n\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\tidentifier: `forget-password-otp-${email}`,\n\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t});\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t`forget-password-otp-${email}`,\n\t\t\t\t);\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\topts.sendVerificationOTP(\n\t\t\t\t\t{\n\t\t\t\t\t\temail,\n\t\t\t\t\t\totp,\n\t\t\t\t\t\ttype: \"forget-password\",\n\t\t\t\t\t},\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n};\n\nconst resetPasswordEmailOTPBodySchema = z.object({\n\temail: z.string().meta({\n\t\tdescription: \"Email address to reset the password\",\n\t}),\n\totp: z.string().meta({\n\t\tdescription: \"OTP sent to the email\",\n\t}),\n\tpassword: z.string().meta({\n\t\tdescription: \"New password\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/reset-password`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.resetPasswordEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.resetPassword`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-reset-password)\n */\nexport const resetPasswordEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/reset-password\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: resetPasswordEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"resetPasswordWithEmailOTP\",\n\t\t\t\t\tdescription: \"Reset password with email and OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email;\n\n\t\t\t// Use atomic verification to prevent race conditions\n\t\t\tawait atomicVerifyOTP(\n\t\t\t\tctx,\n\t\t\t\topts,\n\t\t\t\t`forget-password-otp-${email}`,\n\t\t\t\tctx.body.otp,\n\t\t\t);\n\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email, {\n\t\t\t\tincludeAccounts: true,\n\t\t\t});\n\t\t\tif (!user) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.USER_NOT_FOUND);\n\t\t\t}\n\t\t\tconst minPasswordLength = ctx.context.password.config.minPasswordLength;\n\t\t\tif (ctx.body.password.length < minPasswordLength) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);\n\t\t\t}\n\t\t\tconst maxPasswordLength = ctx.context.password.config.maxPasswordLength;\n\t\t\tif (ctx.body.password.length > maxPasswordLength) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.PASSWORD_TOO_LONG);\n\t\t\t}\n\t\t\tconst passwordHash = await ctx.context.password.hash(ctx.body.password);\n\t\t\tconst account = user.accounts?.find(\n\t\t\t\t(account) => account.providerId === \"credential\",\n\t\t\t);\n\t\t\tif (!account) {\n\t\t\t\tawait ctx.context.internalAdapter.createAccount({\n\t\t\t\t\tuserId: user.user.id,\n\t\t\t\t\tproviderId: \"credential\",\n\t\t\t\t\taccountId: user.user.id,\n\t\t\t\t\tpassword: passwordHash,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tawait ctx.context.internalAdapter.updatePassword(\n\t\t\t\t\tuser.user.id,\n\t\t\t\t\tpasswordHash,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (ctx.context.options.emailAndPassword?.onPasswordReset) {\n\t\t\t\tawait ctx.context.options.emailAndPassword.onPasswordReset(\n\t\t\t\t\t{\n\t\t\t\t\t\tuser: user.user,\n\t\t\t\t\t},\n\t\t\t\t\tctx.request,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (!user.user.emailVerified) {\n\t\t\t\tawait ctx.context.internalAdapter.updateUser(user.user.id, {\n\t\t\t\t\temailVerified: true,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteSessions(user.user.id);\n\t\t\t}\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst requestEmailChangeEmailOTPBodySchema = z.object({\n\tnewEmail: z.string().meta({\n\t\tdescription: \"New email address to send the OTP\",\n\t}),\n\totp: z.string().optional().meta({\n\t\tdescription:\n\t\t\t\"OTP sent to the current email. This is required if changeEmail.verifyCurrentEmail option is set to true\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/request-email-change`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.requestEmailChangeEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.requestEmailChange`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#change-email-with-otp)\n */\nexport const requestEmailChangeEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/request-email-change\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: requestEmailChangeEmailOTPBodySchema,\n\t\t\tuse: [sensitiveSessionMiddleware],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"requestEmailChangeWithEmailOTP\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Request email change with verification OTP sent to the new email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tif (!opts.changeEmail?.enabled) {\n\t\t\t\tctx.context.logger.error(\"Change email with OTP is disabled.\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Change email with OTP is disabled\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst email = ctx.context.session.user.email.toLowerCase();\n\t\t\tconst newEmail = ctx.body.newEmail.toLowerCase();\n\t\t\tconst isValidEmail = z.email().safeParse(newEmail);\n\t\t\tif (!isValidEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\t\t\tif (newEmail === email) {\n\t\t\t\tctx.context.logger.error(\"Email is the same\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Email is the same\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (opts.changeEmail?.verifyCurrentEmail) {\n\t\t\t\tif (!ctx.body.otp) {\n\t\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\t\tmessage: \"OTP is required to verify current email\",\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst currentEmailVerificationValue =\n\t\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\t\t`email-verification-otp-${email}`,\n\t\t\t\t\t);\n\t\t\t\tif (!currentEmailVerificationValue) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t\t}\n\t\t\t\tconst currentEmailIdentifier = `email-verification-otp-${email}`;\n\t\t\t\tif (currentEmailVerificationValue.expiresAt < new Date()) {\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t\tcurrentEmailIdentifier,\n\t\t\t\t\t);\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.OTP_EXPIRED);\n\t\t\t\t}\n\n\t\t\t\tconst [otpValue, attempts] = splitAtLastColon(\n\t\t\t\t\tcurrentEmailVerificationValue.value,\n\t\t\t\t);\n\t\t\t\tconst allowedAttempts = opts?.allowedAttempts || 3;\n\t\t\t\tif (attempts && parseInt(attempts) >= allowedAttempts) {\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t\tcurrentEmailIdentifier,\n\t\t\t\t\t);\n\t\t\t\t\tthrow APIError.from(\"FORBIDDEN\", ERROR_CODES.TOO_MANY_ATTEMPTS);\n\t\t\t\t}\n\n\t\t\t\tconst verified = await verifyStoredOTP(\n\t\t\t\t\tctx,\n\t\t\t\t\topts,\n\t\t\t\t\totpValue,\n\t\t\t\t\tctx.body.otp,\n\t\t\t\t);\n\t\t\t\tif (!verified) {\n\t\t\t\t\tawait ctx.context.internalAdapter.updateVerificationByIdentifier(\n\t\t\t\t\t\tcurrentEmailIdentifier,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: `${otpValue}:${parseInt(attempts || \"0\") + 1}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t\t}\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tcurrentEmailIdentifier,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tif (ctx.body.otp) {\n\t\t\t\t\tctx.context.logger.warn(\n\t\t\t\t\t\t\"OTP provided but not required for verifying current email. \" +\n\t\t\t\t\t\t\t\"If you want to require OTP verification for current email, \" +\n\t\t\t\t\t\t\t\"please set the changeEmail.verifyCurrentEmail option to true in the configuration\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst otp =\n\t\t\t\topts.generateOTP({ email: newEmail, type: \"change-email\" }, ctx) ||\n\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\tidentifier: `change-email-otp-${email}-${newEmail}`,\n\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t});\n\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(newEmail);\n\t\t\tif (user) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t`change-email-otp-${email}-${newEmail}`,\n\t\t\t\t);\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\topts.sendVerificationOTP(\n\t\t\t\t\t{\n\t\t\t\t\t\temail: newEmail,\n\t\t\t\t\t\totp,\n\t\t\t\t\t\ttype: \"change-email\",\n\t\t\t\t\t},\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst changeEmailEmailOTPBodySchema = z.object({\n\tnewEmail: z.string().meta({\n\t\tdescription: \"New email address to verify and change to\",\n\t}),\n\totp: z.string().meta({\n\t\tdescription: \"OTP sent to the new email\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/change-email`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.changeEmailEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.changeEmail`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#change-email-with-otp)\n */\nexport const changeEmailEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/change-email\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: changeEmailEmailOTPBodySchema,\n\t\t\tuse: [sensitiveSessionMiddleware],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"changeEmailWithEmailOTP\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Verify new email with OTP and change the email if verification is successful\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tif (!opts.changeEmail?.enabled) {\n\t\t\t\tctx.context.logger.error(\"Change email with OTP is disabled.\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Change email with OTP is disabled\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst session = ctx.context.session;\n\n\t\t\tconst email = session.user.email.toLowerCase();\n\t\t\tconst newEmail = ctx.body.newEmail.toLowerCase();\n\t\t\tconst isValidNewEmail = z.email().safeParse(newEmail);\n\t\t\tif (!isValidNewEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\t\t\tif (newEmail === email) {\n\t\t\t\tctx.context.logger.error(\"Email is the same\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Email is the same\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst verificationValue =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\t`change-email-otp-${email}-${newEmail}`,\n\t\t\t\t);\n\t\t\tif (!verificationValue) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t}\n\t\t\tconst changeEmailIdentifier = `change-email-otp-${email}-${newEmail}`;\n\t\t\tif (verificationValue.expiresAt < new Date()) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tchangeEmailIdentifier,\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.OTP_EXPIRED);\n\t\t\t}\n\n\t\t\tconst [otpValue, attempts] = splitAtLastColon(verificationValue.value);\n\t\t\tconst allowedAttempts = opts?.allowedAttempts || 3;\n\t\t\tif (attempts && parseInt(attempts) >= allowedAttempts) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tchangeEmailIdentifier,\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"FORBIDDEN\", ERROR_CODES.TOO_MANY_ATTEMPTS);\n\t\t\t}\n\n\t\t\tconst verified = await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp);\n\t\t\tif (!verified) {\n\t\t\t\tawait ctx.context.internalAdapter.updateVerificationByIdentifier(\n\t\t\t\t\tchangeEmailIdentifier,\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: `${otpValue}:${parseInt(attempts || \"0\") + 1}`,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t}\n\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\tchangeEmailIdentifier,\n\t\t\t);\n\n\t\t\tconst currentUser =\n\t\t\t\tawait ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!currentUser) {\n\t\t\t\t/**\n\t\t\t\t * safe to leak the existence of a user as a valid OTP has been provided\n\t\t\t\t */\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.USER_NOT_FOUND);\n\t\t\t}\n\n\t\t\tconst existingUserWithNewEmail =\n\t\t\t\tawait ctx.context.internalAdapter.findUserByEmail(newEmail);\n\t\t\tif (existingUserWithNewEmail) {\n\t\t\t\t/**\n\t\t\t\t * safe to leak the existence of a user as a valid OTP has been provided\n\t\t\t\t */\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Email already in use\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (ctx.context.options.emailVerification?.beforeEmailVerification) {\n\t\t\t\tawait ctx.context.options.emailVerification.beforeEmailVerification(\n\t\t\t\t\tcurrentUser.user,\n\t\t\t\t\tctx.request,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst updatedUser = await ctx.context.internalAdapter.updateUser(\n\t\t\t\tcurrentUser.user.id,\n\t\t\t\t{\n\t\t\t\t\temail: newEmail,\n\t\t\t\t\temailVerified: true,\n\t\t\t\t},\n\t\t\t);\n\t\t\tif (ctx.context.options.emailVerification?.afterEmailVerification) {\n\t\t\t\tawait ctx.context.options.emailVerification.afterEmailVerification(\n\t\t\t\t\tupdatedUser,\n\t\t\t\t\tctx.request,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\tsession: session.session,\n\t\t\t\tuser: {\n\t\t\t\t\t...session.user,\n\t\t\t\t\temail: newEmail,\n\t\t\t\t\temailVerified: true,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst defaultOTPGenerator = (options: EmailOTPOptions) =>\n\tgenerateRandomString(options.otpLength ?? 6, \"0-9\");\n\n/**\n * Atomically verifies OTP with race condition protection.\n * Deletes token before verification to prevent concurrent reuse.\n * Re-creates token with incremented attempts on failure.\n */\nasync function atomicVerifyOTP(\n\tctx: GenericEndpointContext,\n\topts: RequiredEmailOTPOptions,\n\tidentifier: string,\n\tprovidedOTP: string,\n): Promise<void> {\n\tconst verificationValue =\n\t\tawait ctx.context.internalAdapter.findVerificationValue(identifier);\n\n\tif (!verificationValue) {\n\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t}\n\n\tif (verificationValue.expiresAt < new Date()) {\n\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\tidentifier,\n\t\t);\n\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.OTP_EXPIRED);\n\t}\n\n\tconst [otpValue, attempts] = splitAtLastColon(verificationValue.value);\n\tconst allowedAttempts = opts?.allowedAttempts || 3;\n\n\tif (attempts && parseInt(attempts) >= allowedAttempts) {\n\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\tidentifier,\n\t\t);\n\t\tthrow APIError.from(\"FORBIDDEN\", ERROR_CODES.TOO_MANY_ATTEMPTS);\n\t}\n\n\t// Atomically delete token before verification to prevent race condition\n\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);\n\n\tconst verified = await verifyStoredOTP(ctx, opts, otpValue, providedOTP);\n\n\tif (!verified) {\n\t\t// Re-create with incremented attempts\n\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\tvalue: `${otpValue}:${parseInt(attempts || \"0\") + 1}`,\n\t\t\tidentifier,\n\t\t\texpiresAt: verificationValue.expiresAt,\n\t\t});\n\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"routes.mjs","names":["APIError","ERROR_CODES"],"sources":["../../../src/plugins/email-otp/routes.ts"],"sourcesContent":["import type { GenericEndpointContext } from \"@better-auth/core\";\nimport { createAuthEndpoint } from \"@better-auth/core/api\";\nimport { BASE_ERROR_CODES } from \"@better-auth/core/error\";\nimport { deprecate } from \"@better-auth/core/utils/deprecate\";\nimport * as z from \"zod\";\nimport {\n\tAPIError,\n\tgetSessionFromCtx,\n\tsensitiveSessionMiddleware,\n} from \"../../api\";\nimport { setCookieCache, setSessionCookie } from \"../../cookies\";\nimport { generateRandomString, symmetricDecrypt } from \"../../crypto\";\nimport { parseUserInput, parseUserOutput } from \"../../db/schema\";\nimport { getDate } from \"../../utils/date\";\nimport { EMAIL_OTP_ERROR_CODES as ERROR_CODES } from \"./error-codes\";\nimport { storeOTP, tryReuseOTP, verifyStoredOTP } from \"./otp-token\";\nimport type { EmailOTPOptions, RequiredEmailOTPOptions } from \"./types\";\nimport { splitAtLastColon, toOTPIdentifier } from \"./utils\";\n\nconst types = [\n\t\"email-verification\",\n\t\"sign-in\",\n\t\"forget-password\",\n\t\"change-email\",\n] as const;\n\n/**\n * Resolves the OTP to send: reuses an existing one if possible,\n * otherwise generates and stores a new one.\n *\n * @internal\n */\nasync function resolveOTP(\n\tctx: GenericEndpointContext,\n\topts: RequiredEmailOTPOptions,\n\temail: string,\n\ttype: (typeof types)[number],\n): Promise<string> {\n\tconst identifier = toOTPIdentifier(type, email);\n\n\tif (opts.resendStrategy === \"reuse\") {\n\t\tconst reused = await tryReuseOTP(ctx, opts, identifier);\n\t\tif (reused) return reused;\n\t}\n\n\tconst otp =\n\t\topts.generateOTP({ email, type }, ctx) || defaultOTPGenerator(opts);\n\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\n\tawait ctx.context.internalAdapter\n\t\t.createVerificationValue({\n\t\t\tvalue: `${storedOTP}:0`,\n\t\t\tidentifier,\n\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t})\n\t\t.catch(async () => {\n\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\tidentifier,\n\t\t\t);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\tidentifier,\n\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t});\n\t\t});\n\n\treturn otp;\n}\n\nconst sendVerificationOTPBodySchema = z.object({\n\temail: z.string({}).meta({\n\t\tdescription: \"Email address to send the OTP\",\n\t}),\n\ttype: z.enum(types).meta({\n\t\tdescription: \"Type of the OTP\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/send-verification-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.sendVerificationOTP`\n *\n * **client:**\n * `authClient.emailOtp.sendVerificationOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-send-verification-otp)\n */\nexport const sendVerificationOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/send-verification-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: sendVerificationOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"sendEmailVerificationOTP\",\n\t\t\t\t\tdescription: \"Send a verification OTP to an email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tif (!opts?.sendVerificationOTP) {\n\t\t\t\tctx.context.logger.error(\"send email verification is not implemented\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"send email verification is not implemented\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst email = ctx.body.email.toLowerCase();\n\t\t\tconst isValidEmail = z.email().safeParse(email);\n\t\t\tif (!isValidEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\n\t\t\t// Enforce using the correct endpoint for change email OTP\n\t\t\tif (ctx.body.type === \"change-email\") {\n\t\t\t\tctx.context.logger.error(\n\t\t\t\t\t\"Use the /email-otp/request-email-change endpoint to send OTP for changing email\",\n\t\t\t\t);\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Invalid OTP type\",\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst identifier = toOTPIdentifier(ctx.body.type, email);\n\t\t\tconst otp = await resolveOTP(ctx, opts, email, ctx.body.type);\n\n\t\t\tconst shouldSendOTP = ctx.body.type === \"sign-in\" && !opts.disableSignUp;\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user && !shouldSendOTP) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tidentifier,\n\t\t\t\t);\n\t\t\t\treturn ctx.json({ success: true });\n\t\t\t}\n\n\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\topts.sendVerificationOTP({ email, otp, type: ctx.body.type }, ctx),\n\t\t\t);\n\t\t\treturn ctx.json({ success: true });\n\t\t},\n\t);\n\nconst createVerificationOTPBodySchema = z.object({\n\temail: z.string({}).meta({\n\t\tdescription: \"Email address to send the OTP\",\n\t}),\n\ttype: z.enum(types).meta({\n\t\trequired: true,\n\t\tdescription: \"Type of the OTP\",\n\t}),\n});\n\nexport const createVerificationOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: createVerificationOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"createEmailVerificationOTP\",\n\t\t\t\t\tdescription: \"Create a verification OTP for an email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email.toLowerCase();\n\t\t\tconst otp =\n\t\t\t\topts.generateOTP({ email, type: ctx.body.type }, ctx) ||\n\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\tidentifier: toOTPIdentifier(ctx.body.type, email),\n\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t});\n\t\t\treturn otp;\n\t\t},\n\t);\n\nconst getVerificationOTPBodySchema = z.object({\n\temail: z.string({}).meta({\n\t\tdescription: \"Email address the OTP was sent to\",\n\t}),\n\ttype: z.enum(types).meta({\n\t\trequired: true,\n\t\tdescription: \"Type of the OTP\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * GET `/email-otp/get-verification-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.getVerificationOTP`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-get-verification-otp)\n */\nexport const getVerificationOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t{\n\t\t\tmethod: \"GET\",\n\t\t\tquery: getVerificationOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"getEmailVerificationOTP\",\n\t\t\t\t\tdescription: \"Get a verification OTP for an email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t\"200\": {\n\t\t\t\t\t\t\tdescription: \"OTP retrieved successfully or not found/expired\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\totp: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"The stored OTP, or null if not found or expired\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"otp\"],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.query.email.toLowerCase();\n\t\t\tconst verificationValue =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\ttoOTPIdentifier(ctx.query.type, email),\n\t\t\t\t);\n\t\t\tif (!verificationValue || verificationValue.expiresAt < new Date()) {\n\t\t\t\treturn ctx.json({\n\t\t\t\t\totp: null,\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (\n\t\t\t\topts.storeOTP === \"hashed\" ||\n\t\t\t\t(typeof opts.storeOTP === \"object\" && \"hash\" in opts.storeOTP)\n\t\t\t) {\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"OTP is hashed, cannot return the plain text OTP\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst [storedOtp, _attempts] = splitAtLastColon(verificationValue.value);\n\t\t\tlet otp = storedOtp;\n\t\t\tif (opts.storeOTP === \"encrypted\") {\n\t\t\t\totp = await symmetricDecrypt({\n\t\t\t\t\tkey: ctx.context.secretConfig,\n\t\t\t\t\tdata: storedOtp,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (typeof opts.storeOTP === \"object\" && \"decrypt\" in opts.storeOTP) {\n\t\t\t\totp = await opts.storeOTP.decrypt(storedOtp);\n\t\t\t}\n\n\t\t\treturn ctx.json({\n\t\t\t\totp,\n\t\t\t});\n\t\t},\n\t);\n\nconst checkVerificationOTPBodySchema = z.object({\n\temail: z.string().meta({\n\t\tdescription: \"Email address the OTP was sent to\",\n\t}),\n\ttype: z.enum(types).meta({\n\t\trequired: true,\n\t\tdescription: \"Type of the OTP\",\n\t}),\n\totp: z.string().meta({\n\t\trequired: true,\n\t\tdescription: \"OTP to verify\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * GET `/email-otp/check-verification-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.checkVerificationOTP`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-check-verification-otp)\n */\nexport const checkVerificationOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/check-verification-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: checkVerificationOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"verifyEmailWithOTP\",\n\t\t\t\t\tdescription: \"Verify an email with an OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email.toLowerCase();\n\t\t\tconst isValidEmail = z.email().safeParse(email);\n\t\t\tif (!isValidEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.USER_NOT_FOUND);\n\t\t\t}\n\t\t\tconst identifier = toOTPIdentifier(ctx.body.type, email);\n\t\t\tconst verificationValue =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(identifier);\n\t\t\tif (!verificationValue) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t}\n\t\t\tif (verificationValue.expiresAt < new Date()) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tidentifier,\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.OTP_EXPIRED);\n\t\t\t}\n\n\t\t\tconst [otpValue, attempts] = splitAtLastColon(verificationValue.value);\n\t\t\tconst allowedAttempts = opts?.allowedAttempts || 3;\n\t\t\tif (attempts && parseInt(attempts) >= allowedAttempts) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tidentifier,\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"FORBIDDEN\", ERROR_CODES.TOO_MANY_ATTEMPTS);\n\t\t\t}\n\t\t\tconst verified = await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp);\n\t\t\tif (!verified) {\n\t\t\t\tawait ctx.context.internalAdapter.updateVerificationByIdentifier(\n\t\t\t\t\tidentifier,\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: `${otpValue}:${parseInt(attempts || \"0\") + 1}`,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t}\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst verifyEmailOTPBodySchema = z.object({\n\temail: z.string({}).meta({\n\t\tdescription: \"Email address to verify\",\n\t}),\n\totp: z.string().meta({\n\t\trequired: true,\n\t\tdescription: \"OTP to verify\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/verify-email`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.verifyEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.verifyEmail`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-verify-email)\n */\nexport const verifyEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/verify-email\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: verifyEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\tdescription: \"Verify email with OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tstatus: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates if the verification was successful\",\n\t\t\t\t\t\t\t\t\t\t\t\tenum: [true],\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\ttoken: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tnullable: true,\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Session token if autoSignInAfterVerification is enabled, otherwise null\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/User\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"status\", \"token\", \"user\"],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email.toLowerCase();\n\t\t\tconst isValidEmail = z.email().safeParse(email);\n\t\t\tif (!isValidEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\n\t\t\t// Use atomic verification to prevent race conditions\n\t\t\tawait atomicVerifyOTP(\n\t\t\t\tctx,\n\t\t\t\topts,\n\t\t\t\ttoOTPIdentifier(\"email-verification\", email),\n\t\t\t\tctx.body.otp,\n\t\t\t);\n\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\t/**\n\t\t\t\t * safe to leak the existence of a user, given the user has already the OTP from the\n\t\t\t\t * email\n\t\t\t\t */\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.USER_NOT_FOUND);\n\t\t\t}\n\t\t\tif (ctx.context.options.emailVerification?.beforeEmailVerification) {\n\t\t\t\tawait ctx.context.options.emailVerification.beforeEmailVerification(\n\t\t\t\t\tuser.user,\n\t\t\t\t\tctx.request,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst updatedUser = await ctx.context.internalAdapter.updateUser(\n\t\t\t\tuser.user.id,\n\t\t\t\t{\n\t\t\t\t\temail,\n\t\t\t\t\temailVerified: true,\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tawait ctx.context.options.emailVerification?.afterEmailVerification?.(\n\t\t\t\tupdatedUser,\n\t\t\t\tctx.request,\n\t\t\t);\n\n\t\t\tif (ctx.context.options.emailVerification?.autoSignInAfterVerification) {\n\t\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\tupdatedUser.id,\n\t\t\t\t);\n\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\tsession,\n\t\t\t\t\tuser: updatedUser,\n\t\t\t\t});\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tstatus: true,\n\t\t\t\t\ttoken: session.token,\n\t\t\t\t\tuser: parseUserOutput(ctx.context.options, updatedUser),\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst currentSession = await getSessionFromCtx(ctx);\n\t\t\tif (currentSession && updatedUser.emailVerified) {\n\t\t\t\tconst dontRememberMeCookie = await ctx.getSignedCookie(\n\t\t\t\t\tctx.context.authCookies.dontRememberToken.name,\n\t\t\t\t\tctx.context.secret,\n\t\t\t\t);\n\t\t\t\tawait setCookieCache(\n\t\t\t\t\tctx,\n\t\t\t\t\t{\n\t\t\t\t\t\tsession: currentSession.session,\n\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t...currentSession.user,\n\t\t\t\t\t\t\temailVerified: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t!!dontRememberMeCookie,\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn ctx.json({\n\t\t\t\tstatus: true,\n\t\t\t\ttoken: null,\n\t\t\t\tuser: parseUserOutput(ctx.context.options, updatedUser),\n\t\t\t});\n\t\t},\n\t);\n\nconst signInEmailOTPBodySchema = z\n\t.object({\n\t\temail: z.string({}).meta({\n\t\t\tdescription: \"Email address to sign in\",\n\t\t}),\n\t\totp: z.string().meta({\n\t\t\trequired: true,\n\t\t\tdescription: \"OTP sent to the email\",\n\t\t}),\n\t\tname: z\n\t\t\t.string()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t'User display name. Only used if the user is registering for the first time. Eg: \"my-name\"',\n\t\t\t})\n\t\t\t.optional(),\n\t\timage: z\n\t\t\t.string()\n\t\t\t.meta({\n\t\t\t\tdescription:\n\t\t\t\t\t\"User profile image URL. Only used if the user is registering for the first time.\",\n\t\t\t})\n\t\t\t.optional(),\n\t})\n\t.and(z.record(z.string(), z.any()));\n\n/**\n * ### Endpoint\n *\n * POST `/sign-in/email-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.signInEmailOTP`\n *\n * **client:**\n * `authClient.signIn.emailOtp`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-sign-in-email-otp)\n */\nexport const signInEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/sign-in/email-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: signInEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"signInWithEmailOTP\",\n\t\t\t\t\tdescription: \"Sign in with email and OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\ttoken: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"string\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Session token for the authenticated session\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tuser: {\n\t\t\t\t\t\t\t\t\t\t\t\t$ref: \"#/components/schemas/User\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\trequired: [\"token\", \"user\"],\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst { email: rawEmail, otp, name, image, ...rest } = ctx.body;\n\t\t\tconst email = rawEmail.toLowerCase();\n\n\t\t\t// Use atomic verification to prevent race conditions\n\t\t\tawait atomicVerifyOTP(ctx, opts, toOTPIdentifier(\"sign-in\", email), otp);\n\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tif (opts.disableSignUp) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t\t}\n\t\t\t\tconst additionalFields = parseUserInput(\n\t\t\t\t\tctx.context.options,\n\t\t\t\t\trest,\n\t\t\t\t\t\"create\",\n\t\t\t\t);\n\t\t\t\tconst newUser = await ctx.context.internalAdapter.createUser({\n\t\t\t\t\t...additionalFields,\n\t\t\t\t\temail,\n\t\t\t\t\temailVerified: true,\n\t\t\t\t\tname: name || \"\",\n\t\t\t\t\timage,\n\t\t\t\t});\n\t\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\t\tnewUser.id,\n\t\t\t\t);\n\t\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\t\tsession,\n\t\t\t\t\tuser: newUser,\n\t\t\t\t});\n\t\t\t\treturn ctx.json({\n\t\t\t\t\ttoken: session.token,\n\t\t\t\t\tuser: parseUserOutput(ctx.context.options, newUser),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (!user.user.emailVerified) {\n\t\t\t\tawait ctx.context.internalAdapter.updateUser(user.user.id, {\n\t\t\t\t\temailVerified: true,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst session = await ctx.context.internalAdapter.createSession(\n\t\t\t\tuser.user.id,\n\t\t\t);\n\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\tsession,\n\t\t\t\tuser: user.user,\n\t\t\t});\n\t\t\treturn ctx.json({\n\t\t\t\ttoken: session.token,\n\t\t\t\tuser: parseUserOutput(ctx.context.options, user.user),\n\t\t\t});\n\t\t},\n\t);\n\nconst requestPasswordResetEmailOTPBodySchema = z.object({\n\temail: z.string().meta({\n\t\tdescription: \"Email address to send the OTP\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/request-password-reset`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.requestPasswordResetEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.requestPasswordReset`\n *\n * @see [Read our docs to learn more.](https://www.better-auth.com/docs/plugins/email-otp#reset-password-with-otp)\n */\nexport const requestPasswordResetEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/request-password-reset\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: requestPasswordResetEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"requestPasswordResetWithEmailOTP\",\n\t\t\t\t\tdescription: \"Request password reset with email and OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates if the OTP was sent successfully\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email;\n\t\t\tconst identifier = toOTPIdentifier(\"forget-password\", email);\n\t\t\tconst otp = await resolveOTP(ctx, opts, email, \"forget-password\");\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tidentifier,\n\t\t\t\t);\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\topts.sendVerificationOTP(\n\t\t\t\t\t{\n\t\t\t\t\t\temail,\n\t\t\t\t\t\totp,\n\t\t\t\t\t\ttype: \"forget-password\",\n\t\t\t\t\t},\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst forgetPasswordEmailOTPBodySchema = z.object({\n\temail: z.string().meta({\n\t\tdescription: \"Email address to send the OTP\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/forget-password/email-otp`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.forgetPasswordEmailOTP`\n *\n * **client:**\n * `authClient.forgetPassword.emailOtp`\n *\n * @deprecated Use `/email-otp/request-password-reset` instead.\n * @see [Read our docs to learn more.](https://www.better-auth.com/docs/plugins/email-otp#reset-password-with-otp)\n */\nexport const forgetPasswordEmailOTP = (opts: RequiredEmailOTPOptions) => {\n\tconst warnDeprecation = deprecate(\n\t\t() => {},\n\t\t'The \"/forget-password/email-otp\" endpoint is deprecated. ' +\n\t\t\t'Please use \"/email-otp/request-password-reset\" instead. ' +\n\t\t\t\"This endpoint will be removed in the next major version.\",\n\t);\n\n\treturn createAuthEndpoint(\n\t\t\"/forget-password/email-otp\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: forgetPasswordEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"forgetPasswordWithEmailOTP\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Deprecated: Use /email-otp/request-password-reset instead.\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t\tdescription:\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"Indicates if the OTP was sent successfully\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\twarnDeprecation();\n\t\t\tconst email = ctx.body.email;\n\t\t\tconst identifier = toOTPIdentifier(\"forget-password\", email);\n\t\t\tconst otp = await resolveOTP(ctx, opts, email, \"forget-password\");\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!user) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tidentifier,\n\t\t\t\t);\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t});\n\t\t\t}\n\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\topts.sendVerificationOTP(\n\t\t\t\t\t{\n\t\t\t\t\t\temail,\n\t\t\t\t\t\totp,\n\t\t\t\t\t\ttype: \"forget-password\",\n\t\t\t\t\t},\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n};\n\nconst resetPasswordEmailOTPBodySchema = z.object({\n\temail: z.string().meta({\n\t\tdescription: \"Email address to reset the password\",\n\t}),\n\totp: z.string().meta({\n\t\tdescription: \"OTP sent to the email\",\n\t}),\n\tpassword: z.string().meta({\n\t\tdescription: \"New password\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/reset-password`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.resetPasswordEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.resetPassword`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-reset-password)\n */\nexport const resetPasswordEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/reset-password\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: resetPasswordEmailOTPBodySchema,\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"resetPasswordWithEmailOTP\",\n\t\t\t\t\tdescription: \"Reset password with email and OTP\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tconst email = ctx.body.email;\n\n\t\t\t// Use atomic verification to prevent race conditions\n\t\t\tawait atomicVerifyOTP(\n\t\t\t\tctx,\n\t\t\t\topts,\n\t\t\t\ttoOTPIdentifier(\"forget-password\", email),\n\t\t\t\tctx.body.otp,\n\t\t\t);\n\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(email, {\n\t\t\t\tincludeAccounts: true,\n\t\t\t});\n\t\t\tif (!user) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.USER_NOT_FOUND);\n\t\t\t}\n\t\t\tconst minPasswordLength = ctx.context.password.config.minPasswordLength;\n\t\t\tif (ctx.body.password.length < minPasswordLength) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.PASSWORD_TOO_SHORT);\n\t\t\t}\n\t\t\tconst maxPasswordLength = ctx.context.password.config.maxPasswordLength;\n\t\t\tif (ctx.body.password.length > maxPasswordLength) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.PASSWORD_TOO_LONG);\n\t\t\t}\n\t\t\tconst passwordHash = await ctx.context.password.hash(ctx.body.password);\n\t\t\tconst account = user.accounts?.find(\n\t\t\t\t(account) => account.providerId === \"credential\",\n\t\t\t);\n\t\t\tif (!account) {\n\t\t\t\tawait ctx.context.internalAdapter.createAccount({\n\t\t\t\t\tuserId: user.user.id,\n\t\t\t\t\tproviderId: \"credential\",\n\t\t\t\t\taccountId: user.user.id,\n\t\t\t\t\tpassword: passwordHash,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tawait ctx.context.internalAdapter.updatePassword(\n\t\t\t\t\tuser.user.id,\n\t\t\t\t\tpasswordHash,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (ctx.context.options.emailAndPassword?.onPasswordReset) {\n\t\t\t\tawait ctx.context.options.emailAndPassword.onPasswordReset(\n\t\t\t\t\t{\n\t\t\t\t\t\tuser: user.user,\n\t\t\t\t\t},\n\t\t\t\t\tctx.request,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (!user.user.emailVerified) {\n\t\t\t\tawait ctx.context.internalAdapter.updateUser(user.user.id, {\n\t\t\t\t\temailVerified: true,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteSessions(user.user.id);\n\t\t\t}\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst requestEmailChangeEmailOTPBodySchema = z.object({\n\tnewEmail: z.string().meta({\n\t\tdescription: \"New email address to send the OTP\",\n\t}),\n\totp: z.string().optional().meta({\n\t\tdescription:\n\t\t\t\"OTP sent to the current email. This is required if changeEmail.verifyCurrentEmail option is set to true\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/request-email-change`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.requestEmailChangeEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.requestEmailChange`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#change-email-with-otp)\n */\nexport const requestEmailChangeEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/request-email-change\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: requestEmailChangeEmailOTPBodySchema,\n\t\t\tuse: [sensitiveSessionMiddleware],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"requestEmailChangeWithEmailOTP\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Request email change with verification OTP sent to the new email\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tif (!opts.changeEmail?.enabled) {\n\t\t\t\tctx.context.logger.error(\"Change email with OTP is disabled.\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Change email with OTP is disabled\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst email = ctx.context.session.user.email.toLowerCase();\n\t\t\tconst newEmail = ctx.body.newEmail.toLowerCase();\n\t\t\tconst isValidEmail = z.email().safeParse(newEmail);\n\t\t\tif (!isValidEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\t\t\tif (newEmail === email) {\n\t\t\t\tctx.context.logger.error(\"Email is the same\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Email is the same\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (opts.changeEmail?.verifyCurrentEmail) {\n\t\t\t\tif (!ctx.body.otp) {\n\t\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\t\tmessage: \"OTP is required to verify current email\",\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst currentEmailVerificationValue =\n\t\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\t\ttoOTPIdentifier(\"email-verification\", email),\n\t\t\t\t\t);\n\t\t\t\tif (!currentEmailVerificationValue) {\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t\t}\n\t\t\t\tconst currentEmailIdentifier = toOTPIdentifier(\n\t\t\t\t\t\"email-verification\",\n\t\t\t\t\temail,\n\t\t\t\t);\n\t\t\t\tif (currentEmailVerificationValue.expiresAt < new Date()) {\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t\tcurrentEmailIdentifier,\n\t\t\t\t\t);\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.OTP_EXPIRED);\n\t\t\t\t}\n\n\t\t\t\tconst [otpValue, attempts] = splitAtLastColon(\n\t\t\t\t\tcurrentEmailVerificationValue.value,\n\t\t\t\t);\n\t\t\t\tconst allowedAttempts = opts?.allowedAttempts || 3;\n\t\t\t\tif (attempts && parseInt(attempts) >= allowedAttempts) {\n\t\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\t\tcurrentEmailIdentifier,\n\t\t\t\t\t);\n\t\t\t\t\tthrow APIError.from(\"FORBIDDEN\", ERROR_CODES.TOO_MANY_ATTEMPTS);\n\t\t\t\t}\n\n\t\t\t\tconst verified = await verifyStoredOTP(\n\t\t\t\t\tctx,\n\t\t\t\t\topts,\n\t\t\t\t\totpValue,\n\t\t\t\t\tctx.body.otp,\n\t\t\t\t);\n\t\t\t\tif (!verified) {\n\t\t\t\t\tawait ctx.context.internalAdapter.updateVerificationByIdentifier(\n\t\t\t\t\t\tcurrentEmailIdentifier,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvalue: `${otpValue}:${parseInt(attempts || \"0\") + 1}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t\t}\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tcurrentEmailIdentifier,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tif (ctx.body.otp) {\n\t\t\t\t\tctx.context.logger.warn(\n\t\t\t\t\t\t\"OTP provided but not required for verifying current email. \" +\n\t\t\t\t\t\t\t\"If you want to require OTP verification for current email, \" +\n\t\t\t\t\t\t\t\"please set the changeEmail.verifyCurrentEmail option to true in the configuration\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst otp =\n\t\t\t\topts.generateOTP({ email: newEmail, type: \"change-email\" }, ctx) ||\n\t\t\t\tdefaultOTPGenerator(opts);\n\t\t\tconst storedOTP = await storeOTP(ctx, opts, otp);\n\t\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\t\tvalue: `${storedOTP}:0`,\n\t\t\t\tidentifier: toOTPIdentifier(\"change-email\", `${email}-${newEmail}`),\n\t\t\t\texpiresAt: getDate(opts.expiresIn, \"sec\"),\n\t\t\t});\n\n\t\t\tconst user = await ctx.context.internalAdapter.findUserByEmail(newEmail);\n\t\t\tif (user) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\ttoOTPIdentifier(\"change-email\", `${email}-${newEmail}`),\n\t\t\t\t);\n\t\t\t\treturn ctx.json({\n\t\t\t\t\tsuccess: true,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait ctx.context.runInBackgroundOrAwait(\n\t\t\t\topts.sendVerificationOTP(\n\t\t\t\t\t{\n\t\t\t\t\t\temail: newEmail,\n\t\t\t\t\t\totp,\n\t\t\t\t\t\ttype: \"change-email\",\n\t\t\t\t\t},\n\t\t\t\t\tctx,\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst changeEmailEmailOTPBodySchema = z.object({\n\tnewEmail: z.string().meta({\n\t\tdescription: \"New email address to verify and change to\",\n\t}),\n\totp: z.string().meta({\n\t\tdescription: \"OTP sent to the new email\",\n\t}),\n});\n\n/**\n * ### Endpoint\n *\n * POST `/email-otp/change-email`\n *\n * ### API Methods\n *\n * **server:**\n * `auth.api.changeEmailEmailOTP`\n *\n * **client:**\n * `authClient.emailOtp.changeEmail`\n *\n * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#change-email-with-otp)\n */\nexport const changeEmailEmailOTP = (opts: RequiredEmailOTPOptions) =>\n\tcreateAuthEndpoint(\n\t\t\"/email-otp/change-email\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: changeEmailEmailOTPBodySchema,\n\t\t\tuse: [sensitiveSessionMiddleware],\n\t\t\tmetadata: {\n\t\t\t\topenapi: {\n\t\t\t\t\toperationId: \"changeEmailWithEmailOTP\",\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"Verify new email with OTP and change the email if verification is successful\",\n\t\t\t\t\tresponses: {\n\t\t\t\t\t\t200: {\n\t\t\t\t\t\t\tdescription: \"Success\",\n\t\t\t\t\t\t\tcontent: {\n\t\t\t\t\t\t\t\t\"application/json\": {\n\t\t\t\t\t\t\t\t\tschema: {\n\t\t\t\t\t\t\t\t\t\ttype: \"object\",\n\t\t\t\t\t\t\t\t\t\tproperties: {\n\t\t\t\t\t\t\t\t\t\t\tsuccess: {\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tasync (ctx) => {\n\t\t\tif (!opts.changeEmail?.enabled) {\n\t\t\t\tctx.context.logger.error(\"Change email with OTP is disabled.\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Change email with OTP is disabled\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst session = ctx.context.session;\n\n\t\t\tconst email = session.user.email.toLowerCase();\n\t\t\tconst newEmail = ctx.body.newEmail.toLowerCase();\n\t\t\tconst isValidNewEmail = z.email().safeParse(newEmail);\n\t\t\tif (!isValidNewEmail.success) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.INVALID_EMAIL);\n\t\t\t}\n\t\t\tif (newEmail === email) {\n\t\t\t\tctx.context.logger.error(\"Email is the same\");\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Email is the same\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst verificationValue =\n\t\t\t\tawait ctx.context.internalAdapter.findVerificationValue(\n\t\t\t\t\ttoOTPIdentifier(\"change-email\", `${email}-${newEmail}`),\n\t\t\t\t);\n\t\t\tif (!verificationValue) {\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t}\n\t\t\tconst changeEmailIdentifier = toOTPIdentifier(\n\t\t\t\t\"change-email\",\n\t\t\t\t`${email}-${newEmail}`,\n\t\t\t);\n\t\t\tif (verificationValue.expiresAt < new Date()) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tchangeEmailIdentifier,\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.OTP_EXPIRED);\n\t\t\t}\n\n\t\t\tconst [otpValue, attempts] = splitAtLastColon(verificationValue.value);\n\t\t\tconst allowedAttempts = opts?.allowedAttempts || 3;\n\t\t\tif (attempts && parseInt(attempts) >= allowedAttempts) {\n\t\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\t\tchangeEmailIdentifier,\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"FORBIDDEN\", ERROR_CODES.TOO_MANY_ATTEMPTS);\n\t\t\t}\n\n\t\t\tconst verified = await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp);\n\t\t\tif (!verified) {\n\t\t\t\tawait ctx.context.internalAdapter.updateVerificationByIdentifier(\n\t\t\t\t\tchangeEmailIdentifier,\n\t\t\t\t\t{\n\t\t\t\t\t\tvalue: `${otpValue}:${parseInt(attempts || \"0\") + 1}`,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t\t\t}\n\t\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\t\tchangeEmailIdentifier,\n\t\t\t);\n\n\t\t\tconst currentUser =\n\t\t\t\tawait ctx.context.internalAdapter.findUserByEmail(email);\n\t\t\tif (!currentUser) {\n\t\t\t\t/**\n\t\t\t\t * safe to leak the existence of a user as a valid OTP has been provided\n\t\t\t\t */\n\t\t\t\tthrow APIError.from(\"BAD_REQUEST\", BASE_ERROR_CODES.USER_NOT_FOUND);\n\t\t\t}\n\n\t\t\tconst existingUserWithNewEmail =\n\t\t\t\tawait ctx.context.internalAdapter.findUserByEmail(newEmail);\n\t\t\tif (existingUserWithNewEmail) {\n\t\t\t\t/**\n\t\t\t\t * safe to leak the existence of a user as a valid OTP has been provided\n\t\t\t\t */\n\t\t\t\tthrow APIError.fromStatus(\"BAD_REQUEST\", {\n\t\t\t\t\tmessage: \"Email already in use\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (ctx.context.options.emailVerification?.beforeEmailVerification) {\n\t\t\t\tawait ctx.context.options.emailVerification.beforeEmailVerification(\n\t\t\t\t\tcurrentUser.user,\n\t\t\t\t\tctx.request,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst updatedUser = await ctx.context.internalAdapter.updateUser(\n\t\t\t\tcurrentUser.user.id,\n\t\t\t\t{\n\t\t\t\t\temail: newEmail,\n\t\t\t\t\temailVerified: true,\n\t\t\t\t},\n\t\t\t);\n\t\t\tif (ctx.context.options.emailVerification?.afterEmailVerification) {\n\t\t\t\tawait ctx.context.options.emailVerification.afterEmailVerification(\n\t\t\t\t\tupdatedUser,\n\t\t\t\t\tctx.request,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait setSessionCookie(ctx, {\n\t\t\t\tsession: session.session,\n\t\t\t\tuser: {\n\t\t\t\t\t...session.user,\n\t\t\t\t\temail: newEmail,\n\t\t\t\t\temailVerified: true,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn ctx.json({\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t},\n\t);\n\nconst defaultOTPGenerator = (options: EmailOTPOptions) =>\n\tgenerateRandomString(options.otpLength ?? 6, \"0-9\");\n\n/**\n * Atomically verifies OTP with race condition protection.\n * Deletes token before verification to prevent concurrent reuse.\n * Re-creates token with incremented attempts on failure.\n */\nasync function atomicVerifyOTP(\n\tctx: GenericEndpointContext,\n\topts: RequiredEmailOTPOptions,\n\tidentifier: string,\n\tprovidedOTP: string,\n): Promise<void> {\n\tconst verificationValue =\n\t\tawait ctx.context.internalAdapter.findVerificationValue(identifier);\n\n\tif (!verificationValue) {\n\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t}\n\n\tif (verificationValue.expiresAt < new Date()) {\n\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\tidentifier,\n\t\t);\n\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.OTP_EXPIRED);\n\t}\n\n\tconst [otpValue, attempts] = splitAtLastColon(verificationValue.value);\n\tconst allowedAttempts = opts?.allowedAttempts || 3;\n\n\tif (attempts && parseInt(attempts) >= allowedAttempts) {\n\t\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(\n\t\t\tidentifier,\n\t\t);\n\t\tthrow APIError.from(\"FORBIDDEN\", ERROR_CODES.TOO_MANY_ATTEMPTS);\n\t}\n\n\t// Atomically delete token before verification to prevent race condition\n\tawait ctx.context.internalAdapter.deleteVerificationByIdentifier(identifier);\n\n\tconst verified = await verifyStoredOTP(ctx, opts, otpValue, providedOTP);\n\n\tif (!verified) {\n\t\t// Re-create with incremented attempts\n\t\tawait ctx.context.internalAdapter.createVerificationValue({\n\t\t\tvalue: `${otpValue}:${parseInt(attempts || \"0\") + 1}`,\n\t\t\tidentifier,\n\t\t\texpiresAt: verificationValue.expiresAt,\n\t\t});\n\t\tthrow APIError.from(\"BAD_REQUEST\", ERROR_CODES.INVALID_OTP);\n\t}\n}\n"],"mappings":"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"}
@@ -62,6 +62,18 @@ interface EmailOTPOptions {
62
62
  encrypt: (otp: string) => Promise<string>;
63
63
  decrypt: (otp: string) => Promise<string>;
64
64
  }) | undefined;
65
+ /**
66
+ * Strategy for handling OTP when the user requests a new OTP
67
+ * while an existing one is still valid.
68
+ *
69
+ * - `"rotate"`: Always generates a new OTP (default behavior).
70
+ * - `"reuse"`: Resends the same OTP and extends its expiry.
71
+ * Only works when the OTP is recoverable (plain, encrypted, or custom encrypt/decrypt).
72
+ * Falls back to `"rotate"` when OTP is hashed.
73
+ *
74
+ * @default "rotate"
75
+ */
76
+ resendStrategy?: "rotate" | "reuse" | undefined;
65
77
  /**
66
78
  * Change email configuration for the change email with OTP flow
67
79
  *
@@ -2,6 +2,9 @@ import { base64Url } from "@better-auth/utils/base64";
2
2
  import { createHash } from "@better-auth/utils/hash";
3
3
 
4
4
  //#region src/plugins/email-otp/utils.ts
5
+ function toOTPIdentifier(type, email) {
6
+ return `${type}-otp-${email}`;
7
+ }
5
8
  const defaultKeyHasher = async (otp) => {
6
9
  const hash = await createHash("SHA-256").digest(new TextEncoder().encode(otp));
7
10
  return base64Url.encode(new Uint8Array(hash), { padding: false });
@@ -13,5 +16,5 @@ function splitAtLastColon(input) {
13
16
  }
14
17
 
15
18
  //#endregion
16
- export { defaultKeyHasher, splitAtLastColon };
19
+ export { defaultKeyHasher, splitAtLastColon, toOTPIdentifier };
17
20
  //# sourceMappingURL=utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../../../src/plugins/email-otp/utils.ts"],"sourcesContent":["import { base64Url } from \"@better-auth/utils/base64\";\nimport { createHash } from \"@better-auth/utils/hash\";\nexport const defaultKeyHasher = async (otp: string) => {\n\tconst hash = await createHash(\"SHA-256\").digest(\n\t\tnew TextEncoder().encode(otp),\n\t);\n\tconst hashed = base64Url.encode(new Uint8Array(hash), {\n\t\tpadding: false,\n\t});\n\treturn hashed;\n};\n\nexport function splitAtLastColon(input: string): [string, string] {\n\tconst idx = input.lastIndexOf(\":\");\n\tif (idx === -1) {\n\t\treturn [input, \"\"];\n\t}\n\treturn [input.slice(0, idx), input.slice(idx + 1)];\n}\n"],"mappings":";;;;AAEA,MAAa,mBAAmB,OAAO,QAAgB;CACtD,MAAM,OAAO,MAAM,WAAW,UAAU,CAAC,OACxC,IAAI,aAAa,CAAC,OAAO,IAAI,CAC7B;AAID,QAHe,UAAU,OAAO,IAAI,WAAW,KAAK,EAAE,EACrD,SAAS,OACT,CAAC;;AAIH,SAAgB,iBAAiB,OAAiC;CACjE,MAAM,MAAM,MAAM,YAAY,IAAI;AAClC,KAAI,QAAQ,GACX,QAAO,CAAC,OAAO,GAAG;AAEnB,QAAO,CAAC,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,MAAM,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"utils.mjs","names":[],"sources":["../../../src/plugins/email-otp/utils.ts"],"sourcesContent":["import { base64Url } from \"@better-auth/utils/base64\";\nimport { createHash } from \"@better-auth/utils/hash\";\n\nexport function toOTPIdentifier(\n\ttype: \"email-verification\" | \"sign-in\" | \"forget-password\" | \"change-email\",\n\temail: string,\n) {\n\treturn `${type}-otp-${email}`;\n}\n\nexport const defaultKeyHasher = async (otp: string) => {\n\tconst hash = await createHash(\"SHA-256\").digest(\n\t\tnew TextEncoder().encode(otp),\n\t);\n\tconst hashed = base64Url.encode(new Uint8Array(hash), {\n\t\tpadding: false,\n\t});\n\treturn hashed;\n};\n\nexport function splitAtLastColon(input: string): [string, string] {\n\tconst idx = input.lastIndexOf(\":\");\n\tif (idx === -1) {\n\t\treturn [input, \"\"];\n\t}\n\treturn [input.slice(0, idx), input.slice(idx + 1)];\n}\n"],"mappings":";;;;AAGA,SAAgB,gBACf,MACA,OACC;AACD,QAAO,GAAG,KAAK,OAAO;;AAGvB,MAAa,mBAAmB,OAAO,QAAgB;CACtD,MAAM,OAAO,MAAM,WAAW,UAAU,CAAC,OACxC,IAAI,aAAa,CAAC,OAAO,IAAI,CAC7B;AAID,QAHe,UAAU,OAAO,IAAI,WAAW,KAAK,EAAE,EACrD,SAAS,OACT,CAAC;;AAIH,SAAgB,iBAAiB,OAAiC;CACjE,MAAM,MAAM,MAAM,YAAY,IAAI;AAClC,KAAI,QAAQ,GACX,QAAO,CAAC,OAAO,GAAG;AAEnB,QAAO,CAAC,MAAM,MAAM,GAAG,IAAI,EAAE,MAAM,MAAM,MAAM,EAAE,CAAC"}
@@ -8,7 +8,6 @@ import { MicrosoftEntraIdOptions, microsoftEntraId } from "./providers/microsoft
8
8
  import { OktaOptions, okta } from "./providers/okta.mjs";
9
9
  import { PatreonOptions, patreon } from "./providers/patreon.mjs";
10
10
  import { SlackOptions, slack } from "./providers/slack.mjs";
11
- import "./providers/index.mjs";
12
11
  import { BaseOAuthProviderOptions, genericOAuth } from "./index.mjs";
13
12
  import { GENERIC_OAUTH_ERROR_CODES } from "./error-codes.mjs";
14
13
  import * as _better_auth_core_utils_error_codes0 from "@better-auth/core/utils/error-codes";
@@ -8,7 +8,6 @@ import { MicrosoftEntraIdOptions, microsoftEntraId } from "./providers/microsoft
8
8
  import { OktaOptions, okta } from "./providers/okta.mjs";
9
9
  import { PatreonOptions, patreon } from "./providers/patreon.mjs";
10
10
  import { SlackOptions, slack } from "./providers/slack.mjs";
11
- import "./providers/index.mjs";
12
11
  import { AuthContext } from "@better-auth/core";
13
12
  import * as _better_auth_core_oauth20 from "@better-auth/core/oauth2";
14
13
  import { OAuthProvider } from "@better-auth/core/oauth2";