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.
- package/dist/adapters/index.d.mts +25 -1
- package/dist/adapters/index.mjs +9 -1
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/api/index.d.mts +36 -10
- package/dist/api/index.mjs +19 -4
- package/dist/api/index.mjs.map +1 -1
- package/dist/api/middlewares/origin-check.mjs +17 -8
- package/dist/api/middlewares/origin-check.mjs.map +1 -1
- package/dist/api/routes/account.d.mts +1 -1
- package/dist/api/routes/email-verification.d.mts +0 -1
- package/dist/api/routes/password.d.mts +1 -0
- package/dist/api/routes/password.mjs +2 -1
- package/dist/api/routes/password.mjs.map +1 -1
- package/dist/api/routes/session.d.mts +0 -1
- package/dist/api/routes/sign-in.d.mts +16 -2
- package/dist/api/routes/sign-in.mjs +10 -2
- package/dist/api/routes/sign-in.mjs.map +1 -1
- package/dist/api/routes/sign-up.d.mts +0 -1
- package/dist/api/routes/sign-up.mjs +3 -2
- package/dist/api/routes/sign-up.mjs.map +1 -1
- package/dist/api/routes/update-session.d.mts +0 -1
- package/dist/api/routes/update-user.d.mts +0 -1
- package/dist/api/to-auth-endpoints.mjs +49 -12
- package/dist/api/to-auth-endpoints.mjs.map +1 -1
- package/dist/auth/full.d.mts +0 -1
- package/dist/auth/minimal.d.mts +0 -1
- package/dist/client/index.d.mts +3 -4
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/path-to-object.d.mts +9 -2
- package/dist/client/query.mjs +3 -2
- package/dist/client/query.mjs.map +1 -1
- package/dist/client/session-refresh.d.mts +11 -3
- package/dist/client/session-refresh.mjs +13 -8
- package/dist/client/session-refresh.mjs.map +1 -1
- package/dist/client/types.d.mts +0 -1
- package/dist/context/create-context.mjs +4 -1
- package/dist/context/create-context.mjs.map +1 -1
- package/dist/context/helpers.mjs +10 -4
- package/dist/context/helpers.mjs.map +1 -1
- package/dist/cookies/index.d.mts +0 -1
- package/dist/cookies/session-store.d.mts +0 -2
- package/dist/db/get-migration.mjs +3 -2
- package/dist/db/get-migration.mjs.map +1 -1
- package/dist/db/index.d.mts +2 -2
- package/dist/db/internal-adapter.d.mts +2 -1
- package/dist/db/internal-adapter.mjs +1 -1
- package/dist/db/internal-adapter.mjs.map +1 -1
- package/dist/db/schema.d.mts +0 -1
- package/dist/db/with-hooks.d.mts +6 -2
- package/dist/db/with-hooks.mjs +72 -31
- package/dist/db/with-hooks.mjs.map +1 -1
- package/dist/index.d.mts +0 -2
- package/dist/integrations/node.d.mts +0 -1
- package/dist/oauth2/link-account.d.mts +0 -1
- package/dist/plugins/admin/access/statement.d.mts +0 -2
- package/dist/plugins/admin/admin.d.mts +0 -1
- package/dist/plugins/admin/client.d.mts +0 -2
- package/dist/plugins/admin/types.d.mts +0 -2
- package/dist/plugins/anonymous/types.d.mts +0 -1
- package/dist/plugins/email-otp/index.mjs +2 -1
- package/dist/plugins/email-otp/index.mjs.map +1 -1
- package/dist/plugins/email-otp/otp-token.mjs +31 -2
- package/dist/plugins/email-otp/otp-token.mjs.map +1 -1
- package/dist/plugins/email-otp/routes.mjs +60 -59
- package/dist/plugins/email-otp/routes.mjs.map +1 -1
- package/dist/plugins/email-otp/types.d.mts +12 -0
- package/dist/plugins/email-otp/utils.mjs +4 -1
- package/dist/plugins/email-otp/utils.mjs.map +1 -1
- package/dist/plugins/generic-oauth/client.d.mts +0 -1
- package/dist/plugins/generic-oauth/index.d.mts +0 -1
- package/dist/plugins/index.d.mts +0 -3
- package/dist/plugins/jwt/types.d.mts +0 -1
- package/dist/plugins/magic-link/index.d.mts +2 -0
- package/dist/plugins/magic-link/index.mjs +5 -3
- package/dist/plugins/magic-link/index.mjs.map +1 -1
- package/dist/plugins/mcp/index.d.mts +0 -1
- package/dist/plugins/oidc-provider/authorize.mjs +13 -4
- package/dist/plugins/oidc-provider/authorize.mjs.map +1 -1
- package/dist/plugins/oidc-provider/error.mjs +12 -2
- package/dist/plugins/oidc-provider/error.mjs.map +1 -1
- package/dist/plugins/oidc-provider/index.d.mts +0 -1
- package/dist/plugins/oidc-provider/types.d.mts +0 -1
- package/dist/plugins/one-time-token/index.d.mts +0 -1
- package/dist/plugins/organization/access/statement.d.mts +0 -2
- package/dist/plugins/organization/adapter.d.mts +0 -2
- package/dist/plugins/organization/adapter.mjs +2 -2
- package/dist/plugins/organization/adapter.mjs.map +1 -1
- package/dist/plugins/organization/client.d.mts +0 -5
- package/dist/plugins/organization/organization.d.mts +0 -2
- package/dist/plugins/organization/permission.d.mts +0 -1
- package/dist/plugins/organization/routes/crud-access-control.d.mts +0 -2
- package/dist/plugins/organization/routes/crud-invites.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-invites.mjs +1 -1
- package/dist/plugins/organization/routes/crud-invites.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-members.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-members.mjs +1 -1
- package/dist/plugins/organization/routes/crud-members.mjs.map +1 -1
- package/dist/plugins/organization/routes/crud-org.d.mts +0 -3
- package/dist/plugins/organization/routes/crud-team.d.mts +2 -3
- package/dist/plugins/organization/routes/crud-team.mjs +18 -14
- package/dist/plugins/organization/routes/crud-team.mjs.map +1 -1
- package/dist/plugins/organization/schema.d.mts +0 -1
- package/dist/plugins/organization/types.d.mts +0 -2
- package/dist/plugins/phone-number/types.d.mts +0 -1
- package/dist/plugins/siwe/index.d.mts +0 -1
- package/dist/plugins/test-utils/types.d.mts +0 -2
- package/dist/plugins/two-factor/client.d.mts +7 -0
- package/dist/plugins/two-factor/client.mjs +5 -1
- package/dist/plugins/two-factor/client.mjs.map +1 -1
- package/dist/plugins/two-factor/index.mjs +7 -1
- package/dist/plugins/two-factor/index.mjs.map +1 -1
- package/dist/plugins/two-factor/otp/index.d.mts +2 -2
- package/dist/plugins/two-factor/otp/index.mjs.map +1 -1
- package/dist/plugins/two-factor/types.d.mts +7 -1
- package/dist/test-utils/test-instance.d.mts +108 -21
- package/dist/types/index.d.mts +0 -1
- 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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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:
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
|
454
|
-
|
|
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(
|
|
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
|
|
514
|
-
|
|
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(
|
|
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,
|
|
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(
|
|
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 =
|
|
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:
|
|
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(
|
|
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(
|
|
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 =
|
|
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":";;;;;;;;;;;;;;;;AAkBA,MAAM,QAAQ;CACb;CACA;CACA;CACA;CACA;AAWD,MAAM,gCAAgC,EAAE,OAAO;CAC9C,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EACxB,aAAa,iCACb,CAAC;CACF,MAAM,EAAE,KAAK,MAAM,CAAC,KAAK,EACxB,aAAa,mBACb,CAAC;CACF,CAAC;;;;;;;;;;;;;;;;AAiBF,MAAa,uBAAuB,SACnC,mBACC,oCACA;CACC,QAAQ;CACR,MAAM;CACN,UAAU,EACT,SAAS;EACR,aAAa;EACb,aAAa;EACb,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY,EACX,SAAS,EACR,MAAM,WACN,EACD;IACD,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;AACd,KAAI,CAAC,MAAM,qBAAqB;AAC/B,MAAI,QAAQ,OAAO,MAAM,6CAA6C;AACtE,QAAMA,WAAS,WAAW,eAAe,EACxC,SAAS,8CACT,CAAC;;CAEH,MAAM,QAAQ,IAAI,KAAK,MAAM,aAAa;AAE1C,KAAI,CADiB,EAAE,OAAO,CAAC,UAAU,MAAM,CAC7B,QACjB,OAAMA,WAAS,KAAK,eAAe,iBAAiB,cAAc;AAInE,KAAI,IAAI,KAAK,SAAS,gBAAgB;AACrC,MAAI,QAAQ,OAAO,MAClB,kFACA;AACD,QAAMA,WAAS,WAAW,eAAe,EACxC,SAAS,oBACT,CAAC;;CAEH,MAAM,MACL,KAAK,YAAY;EAAE;EAAO,MAAM,IAAI,KAAK;EAAM,EAAE,IAAI,IACrD,oBAAoB,KAAK;CAE1B,MAAM,YAAY,MAAM,SAAS,KAAK,MAAM,IAAI;AAEhD,OAAM,IAAI,QAAQ,gBAChB,wBAAwB;EACxB,OAAO,GAAG,UAAU;EACpB,YAAY,GAAG,IAAI,KAAK,KAAK,OAAO;EACpC,WAAW,QAAQ,KAAK,WAAW,MAAM;EACzC,CAAC,CACD,MAAM,OAAO,UAAU;AAEvB,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,GAAG,IAAI,KAAK,KAAK,OAAO,QACxB;AAED,QAAM,IAAI,QAAQ,gBAAgB,wBAAwB;GACzD,OAAO,GAAG,UAAU;GACpB,YAAY,GAAG,IAAI,KAAK,KAAK,OAAO;GACpC,WAAW,QAAQ,KAAK,WAAW,MAAM;GACzC,CAAC;GACD;AAEH,KAAI,CADS,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,MAAM,CAEpE,KAAI,IAAI,KAAK,SAAS,aAAa,CAAC,KAAK,eAAe,QAEjD;AACN,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,GAAG,IAAI,KAAK,KAAK,OAAO,QACxB;AACD,SAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;;AAIJ,OAAM,IAAI,QAAQ,uBACjB,KAAK,oBACJ;EACC;EACA;EACA,MAAM,IAAI,KAAK;EACf,EACD,IACA,CACD;AACD,QAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;EAEH;AAEF,MAAM,kCAAkC,EAAE,OAAO;CAChD,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EACxB,aAAa,iCACb,CAAC;CACF,MAAM,EAAE,KAAK,MAAM,CAAC,KAAK;EACxB,UAAU;EACV,aAAa;EACb,CAAC;CACF,CAAC;AAEF,MAAa,yBAAyB,SACrC,mBACC;CACC,QAAQ;CACR,MAAM;CACN,UAAU,EACT,SAAS;EACR,aAAa;EACb,aAAa;EACb,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ,EACP,MAAM,UACN,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;CACd,MAAM,QAAQ,IAAI,KAAK,MAAM,aAAa;CAC1C,MAAM,MACL,KAAK,YAAY;EAAE;EAAO,MAAM,IAAI,KAAK;EAAM,EAAE,IAAI,IACrD,oBAAoB,KAAK;CAC1B,MAAM,YAAY,MAAM,SAAS,KAAK,MAAM,IAAI;AAChD,OAAM,IAAI,QAAQ,gBAAgB,wBAAwB;EACzD,OAAO,GAAG,UAAU;EACpB,YAAY,GAAG,IAAI,KAAK,KAAK,OAAO;EACpC,WAAW,QAAQ,KAAK,WAAW,MAAM;EACzC,CAAC;AACF,QAAO;EAER;AAEF,MAAM,+BAA+B,EAAE,OAAO;CAC7C,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EACxB,aAAa,qCACb,CAAC;CACF,MAAM,EAAE,KAAK,MAAM,CAAC,KAAK;EACxB,UAAU;EACV,aAAa;EACb,CAAC;CACF,CAAC;;;;;;;;;;;;;AAcF,MAAa,sBAAsB,SAClC,mBACC;CACC,QAAQ;CACR,OAAO;CACP,UAAU,EACT,SAAS;EACR,aAAa;EACb,aAAa;EACb,WAAW,EACV,OAAO;GACN,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY,EACX,KAAK;KACJ,MAAM;KACN,UAAU;KACV,aACC;KACD,EACD;IACD,UAAU,CAAC,MAAM;IACjB,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;CACd,MAAM,QAAQ,IAAI,MAAM,MAAM,aAAa;CAC3C,MAAM,oBACL,MAAM,IAAI,QAAQ,gBAAgB,sBACjC,GAAG,IAAI,MAAM,KAAK,OAAO,QACzB;AACF,KAAI,CAAC,qBAAqB,kBAAkB,4BAAY,IAAI,MAAM,CACjE,QAAO,IAAI,KAAK,EACf,KAAK,MACL,CAAC;AAEH,KACC,KAAK,aAAa,YACjB,OAAO,KAAK,aAAa,YAAY,UAAU,KAAK,SAErD,OAAMA,WAAS,WAAW,eAAe,EACxC,SAAS,mDACT,CAAC;CAGH,MAAM,CAAC,WAAW,aAAa,iBAAiB,kBAAkB,MAAM;CACxE,IAAI,MAAM;AACV,KAAI,KAAK,aAAa,YACrB,OAAM,MAAM,iBAAiB;EAC5B,KAAK,IAAI,QAAQ;EACjB,MAAM;EACN,CAAC;AAGH,KAAI,OAAO,KAAK,aAAa,YAAY,aAAa,KAAK,SAC1D,OAAM,MAAM,KAAK,SAAS,QAAQ,UAAU;AAG7C,QAAO,IAAI,KAAK,EACf,KACA,CAAC;EAEH;AAEF,MAAM,iCAAiC,EAAE,OAAO;CAC/C,OAAO,EAAE,QAAQ,CAAC,KAAK,EACtB,aAAa,qCACb,CAAC;CACF,MAAM,EAAE,KAAK,MAAM,CAAC,KAAK;EACxB,UAAU;EACV,aAAa;EACb,CAAC;CACF,KAAK,EAAE,QAAQ,CAAC,KAAK;EACpB,UAAU;EACV,aAAa;EACb,CAAC;CACF,CAAC;;;;;;;;;;;;;AAcF,MAAa,wBAAwB,SACpC,mBACC,qCACA;CACC,QAAQ;CACR,MAAM;CACN,UAAU,EACT,SAAS;EACR,aAAa;EACb,aAAa;EACb,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY,EACX,SAAS,EACR,MAAM,WACN,EACD;IACD,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;CACd,MAAM,QAAQ,IAAI,KAAK,MAAM,aAAa;AAE1C,KAAI,CADiB,EAAE,OAAO,CAAC,UAAU,MAAM,CAC7B,QACjB,OAAMA,WAAS,KAAK,eAAe,iBAAiB,cAAc;AAGnE,KAAI,CADS,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,MAAM,CAEpE,OAAMA,WAAS,KAAK,eAAe,iBAAiB,eAAe;CAEpE,MAAM,oBACL,MAAM,IAAI,QAAQ,gBAAgB,sBACjC,GAAG,IAAI,KAAK,KAAK,OAAO,QACxB;AACF,KAAI,CAAC,kBACJ,OAAMA,WAAS,KAAK,eAAeC,sBAAY,YAAY;CAE5D,MAAM,gBAAgB,GAAG,IAAI,KAAK,KAAK,OAAO;AAC9C,KAAI,kBAAkB,4BAAY,IAAI,MAAM,EAAE;AAC7C,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,cACA;AACD,QAAMD,WAAS,KAAK,eAAeC,sBAAY,YAAY;;CAG5D,MAAM,CAAC,UAAU,YAAY,iBAAiB,kBAAkB,MAAM;CACtE,MAAM,kBAAkB,MAAM,mBAAmB;AACjD,KAAI,YAAY,SAAS,SAAS,IAAI,iBAAiB;AACtD,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,cACA;AACD,QAAMD,WAAS,KAAK,aAAaC,sBAAY,kBAAkB;;AAGhE,KAAI,CADa,MAAM,gBAAgB,KAAK,MAAM,UAAU,IAAI,KAAK,IAAI,EAC1D;AACd,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,eACA,EACC,OAAO,GAAG,SAAS,GAAG,SAAS,YAAY,IAAI,GAAG,KAClD,CACD;AACD,QAAMD,WAAS,KAAK,eAAeC,sBAAY,YAAY;;AAE5D,QAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;EAEH;AAEF,MAAM,2BAA2B,EAAE,OAAO;CACzC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EACxB,aAAa,2BACb,CAAC;CACF,KAAK,EAAE,QAAQ,CAAC,KAAK;EACpB,UAAU;EACV,aAAa;EACb,CAAC;CACF,CAAC;;;;;;;;;;;;;;;;AAiBF,MAAa,kBAAkB,SAC9B,mBACC,2BACA;CACC,QAAQ;CACR,MAAM;CACN,UAAU,EACT,SAAS;EACR,aAAa;EACb,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY;KACX,QAAQ;MACP,MAAM;MACN,aACC;MACD,MAAM,CAAC,KAAK;MACZ;KACD,OAAO;MACN,MAAM;MACN,UAAU;MACV,aACC;MACD;KACD,MAAM,EACL,MAAM,6BACN;KACD;IACD,UAAU;KAAC;KAAU;KAAS;KAAO;IACrC,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;CACd,MAAM,QAAQ,IAAI,KAAK,MAAM,aAAa;AAE1C,KAAI,CADiB,EAAE,OAAO,CAAC,UAAU,MAAM,CAC7B,QACjB,OAAMD,WAAS,KAAK,eAAe,iBAAiB,cAAc;AAInE,OAAM,gBACL,KACA,MACA,0BAA0B,SAC1B,IAAI,KAAK,IACT;CAED,MAAM,OAAO,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,MAAM;AACrE,KAAI,CAAC;;;;;AAKJ,OAAMA,WAAS,KAAK,eAAe,iBAAiB,eAAe;AAEpE,KAAI,IAAI,QAAQ,QAAQ,mBAAmB,wBAC1C,OAAM,IAAI,QAAQ,QAAQ,kBAAkB,wBAC3C,KAAK,MACL,IAAI,QACJ;CAEF,MAAM,cAAc,MAAM,IAAI,QAAQ,gBAAgB,WACrD,KAAK,KAAK,IACV;EACC;EACA,eAAe;EACf,CACD;AAED,OAAM,IAAI,QAAQ,QAAQ,mBAAmB,yBAC5C,aACA,IAAI,QACJ;AAED,KAAI,IAAI,QAAQ,QAAQ,mBAAmB,6BAA6B;EACvE,MAAM,UAAU,MAAM,IAAI,QAAQ,gBAAgB,cACjD,YAAY,GACZ;AACD,QAAM,iBAAiB,KAAK;GAC3B;GACA,MAAM;GACN,CAAC;AACF,SAAO,IAAI,KAAK;GACf,QAAQ;GACR,OAAO,QAAQ;GACf,MAAM,gBAAgB,IAAI,QAAQ,SAAS,YAAY;GACvD,CAAC;;CAEH,MAAM,iBAAiB,MAAM,kBAAkB,IAAI;AACnD,KAAI,kBAAkB,YAAY,eAAe;EAChD,MAAM,uBAAuB,MAAM,IAAI,gBACtC,IAAI,QAAQ,YAAY,kBAAkB,MAC1C,IAAI,QAAQ,OACZ;AACD,QAAM,eACL,KACA;GACC,SAAS,eAAe;GACxB,MAAM;IACL,GAAG,eAAe;IAClB,eAAe;IACf;GACD,EACD,CAAC,CAAC,qBACF;;AAEF,QAAO,IAAI,KAAK;EACf,QAAQ;EACR,OAAO;EACP,MAAM,gBAAgB,IAAI,QAAQ,SAAS,YAAY;EACvD,CAAC;EAEH;AAEF,MAAM,2BAA2B,EAC/B,OAAO;CACP,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,KAAK,EACxB,aAAa,4BACb,CAAC;CACF,KAAK,EAAE,QAAQ,CAAC,KAAK;EACpB,UAAU;EACV,aAAa;EACb,CAAC;CACF,MAAM,EACJ,QAAQ,CACR,KAAK,EACL,aACC,+FACD,CAAC,CACD,UAAU;CACZ,OAAO,EACL,QAAQ,CACR,KAAK,EACL,aACC,oFACD,CAAC,CACD,UAAU;CACZ,CAAC,CACD,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,KAAK,CAAC,CAAC;;;;;;;;;;;;;;;;AAiBpC,MAAa,kBAAkB,SAC9B,mBACC,sBACA;CACC,QAAQ;CACR,MAAM;CACN,UAAU,EACT,SAAS;EACR,aAAa;EACb,aAAa;EACb,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY;KACX,OAAO;MACN,MAAM;MACN,aACC;MACD;KACD,MAAM,EACL,MAAM,6BACN;KACD;IACD,UAAU,CAAC,SAAS,OAAO;IAC3B,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;CACd,MAAM,EAAE,OAAO,UAAU,KAAK,MAAM,OAAO,GAAG,SAAS,IAAI;CAC3D,MAAM,QAAQ,SAAS,aAAa;AAGpC,OAAM,gBAAgB,KAAK,MAAM,eAAe,SAAS,IAAI;CAE7D,MAAM,OAAO,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,MAAM;AACrE,KAAI,CAAC,MAAM;AACV,MAAI,KAAK,cACR,OAAMA,WAAS,KAAK,eAAeC,sBAAY,YAAY;EAE5D,MAAM,mBAAmB,eACxB,IAAI,QAAQ,SACZ,MACA,SACA;EACD,MAAM,UAAU,MAAM,IAAI,QAAQ,gBAAgB,WAAW;GAC5D,GAAG;GACH;GACA,eAAe;GACf,MAAM,QAAQ;GACd;GACA,CAAC;EACF,MAAM,UAAU,MAAM,IAAI,QAAQ,gBAAgB,cACjD,QAAQ,GACR;AACD,QAAM,iBAAiB,KAAK;GAC3B;GACA,MAAM;GACN,CAAC;AACF,SAAO,IAAI,KAAK;GACf,OAAO,QAAQ;GACf,MAAM,gBAAgB,IAAI,QAAQ,SAAS,QAAQ;GACnD,CAAC;;AAGH,KAAI,CAAC,KAAK,KAAK,cACd,OAAM,IAAI,QAAQ,gBAAgB,WAAW,KAAK,KAAK,IAAI,EAC1D,eAAe,MACf,CAAC;CAGH,MAAM,UAAU,MAAM,IAAI,QAAQ,gBAAgB,cACjD,KAAK,KAAK,GACV;AACD,OAAM,iBAAiB,KAAK;EAC3B;EACA,MAAM,KAAK;EACX,CAAC;AACF,QAAO,IAAI,KAAK;EACf,OAAO,QAAQ;EACf,MAAM,gBAAgB,IAAI,QAAQ,SAAS,KAAK,KAAK;EACrD,CAAC;EAEH;AAEF,MAAM,yCAAyC,EAAE,OAAO,EACvD,OAAO,EAAE,QAAQ,CAAC,KAAK,EACtB,aAAa,iCACb,CAAC,EACF,CAAC;;;;;;;;;;;;;;;;AAiBF,MAAa,gCAAgC,SAC5C,mBACC,qCACA;CACC,QAAQ;CACR,MAAM;CACN,UAAU,EACT,SAAS;EACR,aAAa;EACb,aAAa;EACb,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY,EACX,SAAS;KACR,MAAM;KACN,aACC;KACD,EACD;IACD,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;CACd,MAAM,QAAQ,IAAI,KAAK;CACvB,MAAM,MACL,KAAK,YAAY;EAAE;EAAO,MAAM;EAAmB,EAAE,IAAI,IACzD,oBAAoB,KAAK;CAC1B,MAAM,YAAY,MAAM,SAAS,KAAK,MAAM,IAAI;AAChD,OAAM,IAAI,QAAQ,gBAAgB,wBAAwB;EACzD,OAAO,GAAG,UAAU;EACpB,YAAY,uBAAuB;EACnC,WAAW,QAAQ,KAAK,WAAW,MAAM;EACzC,CAAC;AAEF,KAAI,CADS,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,MAAM,EAC1D;AACV,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,uBAAuB,QACvB;AACD,SAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;;AAEH,OAAM,IAAI,QAAQ,uBACjB,KAAK,oBACJ;EACC;EACA;EACA,MAAM;EACN,EACD,IACA,CACD;AACD,QAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;EAEH;AAEF,MAAM,mCAAmC,EAAE,OAAO,EACjD,OAAO,EAAE,QAAQ,CAAC,KAAK,EACtB,aAAa,iCACb,CAAC,EACF,CAAC;;;;;;;;;;;;;;;;;AAkBF,MAAa,0BAA0B,SAAkC;CACxE,MAAM,kBAAkB,gBACjB,IACN,gLAGA;AAED,QAAO,mBACN,8BACA;EACC,QAAQ;EACR,MAAM;EACN,UAAU,EACT,SAAS;GACR,aAAa;GACb,aACC;GACD,WAAW,EACV,KAAK;IACJ,aAAa;IACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;KACP,MAAM;KACN,YAAY,EACX,SAAS;MACR,MAAM;MACN,aACC;MACD,EACD;KACD,EACD,EACD;IACD,EACD;GACD,EACD;EACD,EACD,OAAO,QAAQ;AACd,mBAAiB;EACjB,MAAM,QAAQ,IAAI,KAAK;EACvB,MAAM,MACL,KAAK,YAAY;GAAE;GAAO,MAAM;GAAmB,EAAE,IAAI,IACzD,oBAAoB,KAAK;EAC1B,MAAM,YAAY,MAAM,SAAS,KAAK,MAAM,IAAI;AAChD,QAAM,IAAI,QAAQ,gBAAgB,wBAAwB;GACzD,OAAO,GAAG,UAAU;GACpB,YAAY,uBAAuB;GACnC,WAAW,QAAQ,KAAK,WAAW,MAAM;GACzC,CAAC;AAEF,MAAI,CADS,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,MAAM,EAC1D;AACV,SAAM,IAAI,QAAQ,gBAAgB,+BACjC,uBAAuB,QACvB;AACD,UAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;;AAEH,QAAM,IAAI,QAAQ,uBACjB,KAAK,oBACJ;GACC;GACA;GACA,MAAM;GACN,EACD,IACA,CACD;AACD,SAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;GAEH;;AAGF,MAAM,kCAAkC,EAAE,OAAO;CAChD,OAAO,EAAE,QAAQ,CAAC,KAAK,EACtB,aAAa,uCACb,CAAC;CACF,KAAK,EAAE,QAAQ,CAAC,KAAK,EACpB,aAAa,yBACb,CAAC;CACF,UAAU,EAAE,QAAQ,CAAC,KAAK,EACzB,aAAa,gBACb,CAAC;CACF,CAAC;;;;;;;;;;;;;;;;AAiBF,MAAa,yBAAyB,SACrC,mBACC,6BACA;CACC,QAAQ;CACR,MAAM;CACN,UAAU,EACT,SAAS;EACR,aAAa;EACb,aAAa;EACb,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY,EACX,SAAS,EACR,MAAM,WACN,EACD;IACD,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;CACd,MAAM,QAAQ,IAAI,KAAK;AAGvB,OAAM,gBACL,KACA,MACA,uBAAuB,SACvB,IAAI,KAAK,IACT;CAED,MAAM,OAAO,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,OAAO,EACrE,iBAAiB,MACjB,CAAC;AACF,KAAI,CAAC,KACJ,OAAMD,WAAS,KAAK,eAAe,iBAAiB,eAAe;CAEpE,MAAM,oBAAoB,IAAI,QAAQ,SAAS,OAAO;AACtD,KAAI,IAAI,KAAK,SAAS,SAAS,kBAC9B,OAAMA,WAAS,KAAK,eAAe,iBAAiB,mBAAmB;CAExE,MAAM,oBAAoB,IAAI,QAAQ,SAAS,OAAO;AACtD,KAAI,IAAI,KAAK,SAAS,SAAS,kBAC9B,OAAMA,WAAS,KAAK,eAAe,iBAAiB,kBAAkB;CAEvE,MAAM,eAAe,MAAM,IAAI,QAAQ,SAAS,KAAK,IAAI,KAAK,SAAS;AAIvE,KAAI,CAHY,KAAK,UAAU,MAC7B,YAAY,QAAQ,eAAe,aACpC,CAEA,OAAM,IAAI,QAAQ,gBAAgB,cAAc;EAC/C,QAAQ,KAAK,KAAK;EAClB,YAAY;EACZ,WAAW,KAAK,KAAK;EACrB,UAAU;EACV,CAAC;KAEF,OAAM,IAAI,QAAQ,gBAAgB,eACjC,KAAK,KAAK,IACV,aACA;AAGF,KAAI,IAAI,QAAQ,QAAQ,kBAAkB,gBACzC,OAAM,IAAI,QAAQ,QAAQ,iBAAiB,gBAC1C,EACC,MAAM,KAAK,MACX,EACD,IAAI,QACJ;AAGF,KAAI,CAAC,KAAK,KAAK,cACd,OAAM,IAAI,QAAQ,gBAAgB,WAAW,KAAK,KAAK,IAAI,EAC1D,eAAe,MACf,CAAC;AAGH,KAAI,IAAI,QAAQ,QAAQ,kBAAkB,8BACzC,OAAM,IAAI,QAAQ,gBAAgB,eAAe,KAAK,KAAK,GAAG;AAE/D,QAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;EAEH;AAEF,MAAM,uCAAuC,EAAE,OAAO;CACrD,UAAU,EAAE,QAAQ,CAAC,KAAK,EACzB,aAAa,qCACb,CAAC;CACF,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,KAAK,EAC/B,aACC,2GACD,CAAC;CACF,CAAC;;;;;;;;;;;;;;;;AAiBF,MAAa,8BAA8B,SAC1C,mBACC,mCACA;CACC,QAAQ;CACR,MAAM;CACN,KAAK,CAAC,2BAA2B;CACjC,UAAU,EACT,SAAS;EACR,aAAa;EACb,aACC;EACD,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY,EACX,SAAS,EACR,MAAM,WACN,EACD;IACD,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;AACd,KAAI,CAAC,KAAK,aAAa,SAAS;AAC/B,MAAI,QAAQ,OAAO,MAAM,qCAAqC;AAC9D,QAAMA,WAAS,WAAW,eAAe,EACxC,SAAS,qCACT,CAAC;;CAGH,MAAM,QAAQ,IAAI,QAAQ,QAAQ,KAAK,MAAM,aAAa;CAC1D,MAAM,WAAW,IAAI,KAAK,SAAS,aAAa;AAEhD,KAAI,CADiB,EAAE,OAAO,CAAC,UAAU,SAAS,CAChC,QACjB,OAAMA,WAAS,KAAK,eAAe,iBAAiB,cAAc;AAEnE,KAAI,aAAa,OAAO;AACvB,MAAI,QAAQ,OAAO,MAAM,oBAAoB;AAC7C,QAAMA,WAAS,WAAW,eAAe,EACxC,SAAS,qBACT,CAAC;;AAGH,KAAI,KAAK,aAAa,oBAAoB;AACzC,MAAI,CAAC,IAAI,KAAK,IACb,OAAMA,WAAS,WAAW,eAAe,EACxC,SAAS,2CACT,CAAC;EAGH,MAAM,gCACL,MAAM,IAAI,QAAQ,gBAAgB,sBACjC,0BAA0B,QAC1B;AACF,MAAI,CAAC,8BACJ,OAAMA,WAAS,KAAK,eAAeC,sBAAY,YAAY;EAE5D,MAAM,yBAAyB,0BAA0B;AACzD,MAAI,8BAA8B,4BAAY,IAAI,MAAM,EAAE;AACzD,SAAM,IAAI,QAAQ,gBAAgB,+BACjC,uBACA;AACD,SAAMD,WAAS,KAAK,eAAeC,sBAAY,YAAY;;EAG5D,MAAM,CAAC,UAAU,YAAY,iBAC5B,8BAA8B,MAC9B;EACD,MAAM,kBAAkB,MAAM,mBAAmB;AACjD,MAAI,YAAY,SAAS,SAAS,IAAI,iBAAiB;AACtD,SAAM,IAAI,QAAQ,gBAAgB,+BACjC,uBACA;AACD,SAAMD,WAAS,KAAK,aAAaC,sBAAY,kBAAkB;;AAShE,MAAI,CANa,MAAM,gBACtB,KACA,MACA,UACA,IAAI,KAAK,IACT,EACc;AACd,SAAM,IAAI,QAAQ,gBAAgB,+BACjC,wBACA,EACC,OAAO,GAAG,SAAS,GAAG,SAAS,YAAY,IAAI,GAAG,KAClD,CACD;AACD,SAAMD,WAAS,KAAK,eAAeC,sBAAY,YAAY;;AAE5D,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,uBACA;YAEG,IAAI,KAAK,IACZ,KAAI,QAAQ,OAAO,KAClB,0MAGA;CAIH,MAAM,MACL,KAAK,YAAY;EAAE,OAAO;EAAU,MAAM;EAAgB,EAAE,IAAI,IAChE,oBAAoB,KAAK;CAC1B,MAAM,YAAY,MAAM,SAAS,KAAK,MAAM,IAAI;AAChD,OAAM,IAAI,QAAQ,gBAAgB,wBAAwB;EACzD,OAAO,GAAG,UAAU;EACpB,YAAY,oBAAoB,MAAM,GAAG;EACzC,WAAW,QAAQ,KAAK,WAAW,MAAM;EACzC,CAAC;AAGF,KADa,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,SAAS,EAC9D;AACT,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,oBAAoB,MAAM,GAAG,WAC7B;AACD,SAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;;AAGH,OAAM,IAAI,QAAQ,uBACjB,KAAK,oBACJ;EACC,OAAO;EACP;EACA,MAAM;EACN,EACD,IACA,CACD;AACD,QAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;EAEH;AAEF,MAAM,gCAAgC,EAAE,OAAO;CAC9C,UAAU,EAAE,QAAQ,CAAC,KAAK,EACzB,aAAa,6CACb,CAAC;CACF,KAAK,EAAE,QAAQ,CAAC,KAAK,EACpB,aAAa,6BACb,CAAC;CACF,CAAC;;;;;;;;;;;;;;;;AAiBF,MAAa,uBAAuB,SACnC,mBACC,2BACA;CACC,QAAQ;CACR,MAAM;CACN,KAAK,CAAC,2BAA2B;CACjC,UAAU,EACT,SAAS;EACR,aAAa;EACb,aACC;EACD,WAAW,EACV,KAAK;GACJ,aAAa;GACb,SAAS,EACR,oBAAoB,EACnB,QAAQ;IACP,MAAM;IACN,YAAY,EACX,SAAS,EACR,MAAM,WACN,EACD;IACD,EACD,EACD;GACD,EACD;EACD,EACD;CACD,EACD,OAAO,QAAQ;AACd,KAAI,CAAC,KAAK,aAAa,SAAS;AAC/B,MAAI,QAAQ,OAAO,MAAM,qCAAqC;AAC9D,QAAMD,WAAS,WAAW,eAAe,EACxC,SAAS,qCACT,CAAC;;CAGH,MAAM,UAAU,IAAI,QAAQ;CAE5B,MAAM,QAAQ,QAAQ,KAAK,MAAM,aAAa;CAC9C,MAAM,WAAW,IAAI,KAAK,SAAS,aAAa;AAEhD,KAAI,CADoB,EAAE,OAAO,CAAC,UAAU,SAAS,CAChC,QACpB,OAAMA,WAAS,KAAK,eAAe,iBAAiB,cAAc;AAEnE,KAAI,aAAa,OAAO;AACvB,MAAI,QAAQ,OAAO,MAAM,oBAAoB;AAC7C,QAAMA,WAAS,WAAW,eAAe,EACxC,SAAS,qBACT,CAAC;;CAGH,MAAM,oBACL,MAAM,IAAI,QAAQ,gBAAgB,sBACjC,oBAAoB,MAAM,GAAG,WAC7B;AACF,KAAI,CAAC,kBACJ,OAAMA,WAAS,KAAK,eAAeC,sBAAY,YAAY;CAE5D,MAAM,wBAAwB,oBAAoB,MAAM,GAAG;AAC3D,KAAI,kBAAkB,4BAAY,IAAI,MAAM,EAAE;AAC7C,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,sBACA;AACD,QAAMD,WAAS,KAAK,eAAeC,sBAAY,YAAY;;CAG5D,MAAM,CAAC,UAAU,YAAY,iBAAiB,kBAAkB,MAAM;CACtE,MAAM,kBAAkB,MAAM,mBAAmB;AACjD,KAAI,YAAY,SAAS,SAAS,IAAI,iBAAiB;AACtD,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,sBACA;AACD,QAAMD,WAAS,KAAK,aAAaC,sBAAY,kBAAkB;;AAIhE,KAAI,CADa,MAAM,gBAAgB,KAAK,MAAM,UAAU,IAAI,KAAK,IAAI,EAC1D;AACd,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,uBACA,EACC,OAAO,GAAG,SAAS,GAAG,SAAS,YAAY,IAAI,GAAG,KAClD,CACD;AACD,QAAMD,WAAS,KAAK,eAAeC,sBAAY,YAAY;;AAE5D,OAAM,IAAI,QAAQ,gBAAgB,+BACjC,sBACA;CAED,MAAM,cACL,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,MAAM;AACzD,KAAI,CAAC;;;;AAIJ,OAAMD,WAAS,KAAK,eAAe,iBAAiB,eAAe;AAKpE,KADC,MAAM,IAAI,QAAQ,gBAAgB,gBAAgB,SAAS;;;;AAK3D,OAAMA,WAAS,WAAW,eAAe,EACxC,SAAS,wBACT,CAAC;AAGH,KAAI,IAAI,QAAQ,QAAQ,mBAAmB,wBAC1C,OAAM,IAAI,QAAQ,QAAQ,kBAAkB,wBAC3C,YAAY,MACZ,IAAI,QACJ;CAEF,MAAM,cAAc,MAAM,IAAI,QAAQ,gBAAgB,WACrD,YAAY,KAAK,IACjB;EACC,OAAO;EACP,eAAe;EACf,CACD;AACD,KAAI,IAAI,QAAQ,QAAQ,mBAAmB,uBAC1C,OAAM,IAAI,QAAQ,QAAQ,kBAAkB,uBAC3C,aACA,IAAI,QACJ;AAEF,OAAM,iBAAiB,KAAK;EAC3B,SAAS,QAAQ;EACjB,MAAM;GACL,GAAG,QAAQ;GACX,OAAO;GACP,eAAe;GACf;EACD,CAAC;AAEF,QAAO,IAAI,KAAK,EACf,SAAS,MACT,CAAC;EAEH;AAEF,MAAM,uBAAuB,YAC5B,qBAAqB,QAAQ,aAAa,GAAG,MAAM;;;;;;AAOpD,eAAe,gBACd,KACA,MACA,YACA,aACgB;CAChB,MAAM,oBACL,MAAM,IAAI,QAAQ,gBAAgB,sBAAsB,WAAW;AAEpE,KAAI,CAAC,kBACJ,OAAMA,WAAS,KAAK,eAAeC,sBAAY,YAAY;AAG5D,KAAI,kBAAkB,4BAAY,IAAI,MAAM,EAAE;AAC7C,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,WACA;AACD,QAAMD,WAAS,KAAK,eAAeC,sBAAY,YAAY;;CAG5D,MAAM,CAAC,UAAU,YAAY,iBAAiB,kBAAkB,MAAM;CACtE,MAAM,kBAAkB,MAAM,mBAAmB;AAEjD,KAAI,YAAY,SAAS,SAAS,IAAI,iBAAiB;AACtD,QAAM,IAAI,QAAQ,gBAAgB,+BACjC,WACA;AACD,QAAMD,WAAS,KAAK,aAAaC,sBAAY,kBAAkB;;AAIhE,OAAM,IAAI,QAAQ,gBAAgB,+BAA+B,WAAW;AAI5E,KAAI,CAFa,MAAM,gBAAgB,KAAK,MAAM,UAAU,YAAY,EAEzD;AAEd,QAAM,IAAI,QAAQ,gBAAgB,wBAAwB;GACzD,OAAO,GAAG,SAAS,GAAG,SAAS,YAAY,IAAI,GAAG;GAClD;GACA,WAAW,kBAAkB;GAC7B,CAAC;AACF,QAAMD,WAAS,KAAK,eAAeC,sBAAY,YAAY"}
|
|
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":";;;;
|
|
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";
|