better-auth
Version:
The most comprehensive authentication framework for TypeScript.
630 lines (628 loc) • 24.1 kB
JavaScript
import { getDate } from "../../utils/date.mjs";
import { parseUserOutput } from "../../db/schema.mjs";
import { generateRandomString } from "../../crypto/random.mjs";
import { symmetricDecrypt } from "../../crypto/index.mjs";
import { setCookieCache, setSessionCookie } from "../../cookies/index.mjs";
import { getSessionFromCtx } from "../../api/routes/session.mjs";
import { APIError } from "../../api/index.mjs";
import { splitAtLastColon } from "./utils.mjs";
import { storeOTP, verifyStoredOTP } from "./otp-token.mjs";
import { BASE_ERROR_CODES } from "@better-auth/core/error";
import { defineErrorCodes, deprecate } from "@better-auth/core/utils";
import * as z from "zod";
import { createAuthEndpoint } from "@better-auth/core/api";
//#region src/plugins/email-otp/routes.ts
const types = [
"email-verification",
"sign-in",
"forget-password"
];
const ERROR_CODES = defineErrorCodes({
OTP_EXPIRED: "OTP expired",
INVALID_OTP: "Invalid OTP",
TOO_MANY_ATTEMPTS: "Too many attempts"
});
const sendVerificationOTPBodySchema = z.object({
email: z.string({}).meta({ description: "Email address to send the OTP" }),
type: z.enum(types).meta({ description: "Type of the OTP" })
});
/**
* ### Endpoint
*
* POST `/email-otp/send-verification-otp`
*
* ### API Methods
*
* **server:**
* `auth.api.sendVerificationOTP`
*
* **client:**
* `authClient.emailOtp.sendVerificationOtp`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-send-verification-otp)
*/
const sendVerificationOTP = (opts) => createAuthEndpoint("/email-otp/send-verification-otp", {
method: "POST",
body: sendVerificationOTPBodySchema,
metadata: { openapi: {
operationId: "sendEmailVerificationOTP",
description: "Send a verification OTP to an email",
responses: { 200: {
description: "Success",
content: { "application/json": { schema: {
type: "object",
properties: { success: { type: "boolean" } }
} } }
} }
} }
}, async (ctx) => {
if (!opts?.sendVerificationOTP) {
ctx.context.logger.error("send email verification is not implemented");
throw new APIError("BAD_REQUEST", { message: "send email verification is not implemented" });
}
const email = ctx.body.email.toLowerCase();
if (!z.email().safeParse(email).success) throw ctx.error("BAD_REQUEST", { message: BASE_ERROR_CODES.INVALID_EMAIL });
const otp = opts.generateOTP({
email,
type: ctx.body.type
}, ctx) || defaultOTPGenerator(opts);
const storedOTP = await storeOTP(ctx, opts, otp);
await ctx.context.internalAdapter.createVerificationValue({
value: `${storedOTP}:0`,
identifier: `${ctx.body.type}-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
}).catch(async (error) => {
await ctx.context.internalAdapter.deleteVerificationByIdentifier(`${ctx.body.type}-otp-${email}`);
await ctx.context.internalAdapter.createVerificationValue({
value: `${storedOTP}:0`,
identifier: `${ctx.body.type}-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
});
});
if (!await ctx.context.internalAdapter.findUserByEmail(email)) if (ctx.body.type === "sign-in" && !opts.disableSignUp) {} else {
await ctx.context.internalAdapter.deleteVerificationByIdentifier(`${ctx.body.type}-otp-${email}`);
return ctx.json({ success: true });
}
await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
email,
otp,
type: ctx.body.type
}, ctx));
return ctx.json({ success: true });
});
const createVerificationOTPBodySchema = z.object({
email: z.string({}).meta({ description: "Email address to send the OTP" }),
type: z.enum(types).meta({
required: true,
description: "Type of the OTP"
})
});
const createVerificationOTP = (opts) => createAuthEndpoint({
method: "POST",
body: createVerificationOTPBodySchema,
metadata: { openapi: {
operationId: "createEmailVerificationOTP",
description: "Create a verification OTP for an email",
responses: { 200: {
description: "Success",
content: { "application/json": { schema: { type: "string" } } }
} }
} }
}, async (ctx) => {
const email = ctx.body.email.toLowerCase();
const otp = opts.generateOTP({
email,
type: ctx.body.type
}, ctx) || defaultOTPGenerator(opts);
const storedOTP = await storeOTP(ctx, opts, otp);
await ctx.context.internalAdapter.createVerificationValue({
value: `${storedOTP}:0`,
identifier: `${ctx.body.type}-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
});
return otp;
});
const getVerificationOTPBodySchema = z.object({
email: z.string({}).meta({ description: "Email address the OTP was sent to" }),
type: z.enum(types).meta({
required: true,
description: "Type of the OTP"
})
});
/**
* ### Endpoint
*
* GET `/email-otp/get-verification-otp`
*
* ### API Methods
*
* **server:**
* `auth.api.getVerificationOTP`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-get-verification-otp)
*/
const getVerificationOTP = (opts) => createAuthEndpoint({
method: "GET",
query: getVerificationOTPBodySchema,
metadata: { openapi: {
operationId: "getEmailVerificationOTP",
description: "Get a verification OTP for an email",
responses: { "200": {
description: "OTP retrieved successfully or not found/expired",
content: { "application/json": { schema: {
type: "object",
properties: { otp: {
type: "string",
nullable: true,
description: "The stored OTP, or null if not found or expired"
} },
required: ["otp"]
} } }
} }
} }
}, async (ctx) => {
const email = ctx.query.email.toLowerCase();
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(`${ctx.query.type}-otp-${email}`);
if (!verificationValue || verificationValue.expiresAt < /* @__PURE__ */ new Date()) return ctx.json({ otp: null });
if (opts.storeOTP === "hashed" || typeof opts.storeOTP === "object" && "hash" in opts.storeOTP) throw new APIError("BAD_REQUEST", { message: "OTP is hashed, cannot return the plain text OTP" });
const [storedOtp, _attempts] = splitAtLastColon(verificationValue.value);
let otp = storedOtp;
if (opts.storeOTP === "encrypted") otp = await symmetricDecrypt({
key: ctx.context.secret,
data: storedOtp
});
if (typeof opts.storeOTP === "object" && "decrypt" in opts.storeOTP) otp = await opts.storeOTP.decrypt(storedOtp);
return ctx.json({ otp });
});
const checkVerificationOTPBodySchema = z.object({
email: z.string().meta({ description: "Email address the OTP was sent to" }),
type: z.enum(types).meta({
required: true,
description: "Type of the OTP"
}),
otp: z.string().meta({
required: true,
description: "OTP to verify"
})
});
/**
* ### Endpoint
*
* GET `/email-otp/check-verification-otp`
*
* ### API Methods
*
* **server:**
* `auth.api.checkVerificationOTP`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-check-verification-otp)
*/
const checkVerificationOTP = (opts) => createAuthEndpoint("/email-otp/check-verification-otp", {
method: "POST",
body: checkVerificationOTPBodySchema,
metadata: { openapi: {
operationId: "verifyEmailWithOTP",
description: "Verify an email with an OTP",
responses: { 200: {
description: "Success",
content: { "application/json": { schema: {
type: "object",
properties: { success: { type: "boolean" } }
} } }
} }
} }
}, async (ctx) => {
const email = ctx.body.email.toLowerCase();
if (!z.email().safeParse(email).success) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.INVALID_EMAIL });
if (!await ctx.context.internalAdapter.findUserByEmail(email)) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.USER_NOT_FOUND });
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(`${ctx.body.type}-otp-${email}`);
if (!verificationValue) throw new APIError("BAD_REQUEST", { message: ERROR_CODES.INVALID_OTP });
if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
throw new APIError("BAD_REQUEST", { message: ERROR_CODES.OTP_EXPIRED });
}
const [otpValue, attempts] = splitAtLastColon(verificationValue.value);
const allowedAttempts = opts?.allowedAttempts || 3;
if (attempts && parseInt(attempts) >= allowedAttempts) {
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
throw new APIError("FORBIDDEN", { message: ERROR_CODES.TOO_MANY_ATTEMPTS });
}
if (!await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp)) {
await ctx.context.internalAdapter.updateVerificationValue(verificationValue.id, { value: `${otpValue}:${parseInt(attempts || "0") + 1}` });
throw new APIError("BAD_REQUEST", { message: ERROR_CODES.INVALID_OTP });
}
return ctx.json({ success: true });
});
const verifyEmailOTPBodySchema = z.object({
email: z.string({}).meta({ description: "Email address to verify" }),
otp: z.string().meta({
required: true,
description: "OTP to verify"
})
});
/**
* ### Endpoint
*
* POST `/email-otp/verify-email`
*
* ### API Methods
*
* **server:**
* `auth.api.verifyEmailOTP`
*
* **client:**
* `authClient.emailOtp.verifyEmail`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-verify-email)
*/
const verifyEmailOTP = (opts) => createAuthEndpoint("/email-otp/verify-email", {
method: "POST",
body: verifyEmailOTPBodySchema,
metadata: { openapi: {
description: "Verify email with OTP",
responses: { 200: {
description: "Success",
content: { "application/json": { schema: {
type: "object",
properties: {
status: {
type: "boolean",
description: "Indicates if the verification was successful",
enum: [true]
},
token: {
type: "string",
nullable: true,
description: "Session token if autoSignInAfterVerification is enabled, otherwise null"
},
user: { $ref: "#/components/schemas/User" }
},
required: [
"status",
"token",
"user"
]
} } }
} }
} }
}, async (ctx) => {
const email = ctx.body.email.toLowerCase();
if (!z.email().safeParse(email).success) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.INVALID_EMAIL });
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(`email-verification-otp-${email}`);
if (!verificationValue) throw new APIError("BAD_REQUEST", { message: ERROR_CODES.INVALID_OTP });
if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("BAD_REQUEST", { message: ERROR_CODES.OTP_EXPIRED });
const [otpValue, attempts] = splitAtLastColon(verificationValue.value);
const allowedAttempts = opts?.allowedAttempts || 3;
if (attempts && parseInt(attempts) >= allowedAttempts) {
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
throw new APIError("FORBIDDEN", { message: ERROR_CODES.TOO_MANY_ATTEMPTS });
}
if (!await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp)) {
await ctx.context.internalAdapter.updateVerificationValue(verificationValue.id, { value: `${otpValue}:${parseInt(attempts || "0") + 1}` });
throw new APIError("BAD_REQUEST", { message: ERROR_CODES.INVALID_OTP });
}
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user)
/**
* safe to leak the existence of a user, given the user has already the OTP from the
* email
*/
throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.USER_NOT_FOUND });
if (ctx.context.options.emailVerification?.beforeEmailVerification) await ctx.context.options.emailVerification.beforeEmailVerification(user.user, ctx.request);
const updatedUser = await ctx.context.internalAdapter.updateUser(user.user.id, {
email,
emailVerified: true
});
if (ctx.context.options.emailVerification?.onEmailVerification) await ctx.context.options.emailVerification.onEmailVerification(updatedUser, ctx.request);
await ctx.context.options.emailVerification?.afterEmailVerification?.(updatedUser, ctx.request);
if (ctx.context.options.emailVerification?.autoSignInAfterVerification) {
const session = await ctx.context.internalAdapter.createSession(updatedUser.id);
await setSessionCookie(ctx, {
session,
user: updatedUser
});
return ctx.json({
status: true,
token: session.token,
user: parseUserOutput(ctx.context.options, updatedUser)
});
}
const currentSession = await getSessionFromCtx(ctx);
if (currentSession && updatedUser.emailVerified) {
const dontRememberMeCookie = await ctx.getSignedCookie(ctx.context.authCookies.dontRememberToken.name, ctx.context.secret);
await setCookieCache(ctx, {
session: currentSession.session,
user: {
...currentSession.user,
emailVerified: true
}
}, !!dontRememberMeCookie);
}
return ctx.json({
status: true,
token: null,
user: parseUserOutput(ctx.context.options, updatedUser)
});
});
const signInEmailOTPBodySchema = z.object({
email: z.string({}).meta({ description: "Email address to sign in" }),
otp: z.string().meta({
required: true,
description: "OTP sent to the email"
})
});
/**
* ### Endpoint
*
* POST `/sign-in/email-otp`
*
* ### API Methods
*
* **server:**
* `auth.api.signInEmailOTP`
*
* **client:**
* `authClient.signIn.emailOtp`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-sign-in-email-otp)
*/
const signInEmailOTP = (opts) => createAuthEndpoint("/sign-in/email-otp", {
method: "POST",
body: signInEmailOTPBodySchema,
metadata: { openapi: {
operationId: "signInWithEmailOTP",
description: "Sign in with email and OTP",
responses: { 200: {
description: "Success",
content: { "application/json": { schema: {
type: "object",
properties: {
token: {
type: "string",
description: "Session token for the authenticated session"
},
user: { $ref: "#/components/schemas/User" }
},
required: ["token", "user"]
} } }
} }
} }
}, async (ctx) => {
const email = ctx.body.email.toLowerCase();
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(`sign-in-otp-${email}`);
if (!verificationValue) throw new APIError("BAD_REQUEST", { message: ERROR_CODES.INVALID_OTP });
if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) throw new APIError("BAD_REQUEST", { message: ERROR_CODES.OTP_EXPIRED });
const [otpValue, attempts] = splitAtLastColon(verificationValue.value);
const allowedAttempts = opts?.allowedAttempts || 3;
if (attempts && parseInt(attempts) >= allowedAttempts) {
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
throw new APIError("FORBIDDEN", { message: ERROR_CODES.TOO_MANY_ATTEMPTS });
}
if (!await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp)) {
await ctx.context.internalAdapter.updateVerificationValue(verificationValue.id, { value: `${otpValue}:${parseInt(attempts || "0") + 1}` });
throw new APIError("BAD_REQUEST", { message: ERROR_CODES.INVALID_OTP });
}
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
if (opts.disableSignUp) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.USER_NOT_FOUND });
const newUser = await ctx.context.internalAdapter.createUser({
email,
emailVerified: true,
name: ""
});
const session$1 = await ctx.context.internalAdapter.createSession(newUser.id);
await setSessionCookie(ctx, {
session: session$1,
user: newUser
});
return ctx.json({
token: session$1.token,
user: parseUserOutput(ctx.context.options, newUser)
});
}
if (!user.user.emailVerified) await ctx.context.internalAdapter.updateUser(user.user.id, { emailVerified: true });
const session = await ctx.context.internalAdapter.createSession(user.user.id);
await setSessionCookie(ctx, {
session,
user: user.user
});
return ctx.json({
token: session.token,
user: parseUserOutput(ctx.context.options, user.user)
});
});
const requestPasswordResetEmailOTPBodySchema = z.object({ email: z.string().meta({ description: "Email address to send the OTP" }) });
/**
* ### Endpoint
*
* POST `/email-otp/request-password-reset`
*
* ### API Methods
*
* **server:**
* `auth.api.requestPasswordResetEmailOTP`
*
* **client:**
* `authClient.emailOtp.requestPasswordReset`
*
* @see [Read our docs to learn more.](https://www.better-auth.com/docs/plugins/email-otp#reset-password-with-otp)
*/
const requestPasswordResetEmailOTP = (opts) => createAuthEndpoint("/email-otp/request-password-reset", {
method: "POST",
body: requestPasswordResetEmailOTPBodySchema,
metadata: { openapi: {
operationId: "requestPasswordResetWithEmailOTP",
description: "Request password reset with email and OTP",
responses: { 200: {
description: "Success",
content: { "application/json": { schema: {
type: "object",
properties: { success: {
type: "boolean",
description: "Indicates if the OTP was sent successfully"
} }
} } }
} }
} }
}, async (ctx) => {
const email = ctx.body.email;
const otp = opts.generateOTP({
email,
type: "forget-password"
}, ctx) || defaultOTPGenerator(opts);
const storedOTP = await storeOTP(ctx, opts, otp);
await ctx.context.internalAdapter.createVerificationValue({
value: `${storedOTP}:0`,
identifier: `forget-password-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
});
if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
await ctx.context.internalAdapter.deleteVerificationByIdentifier(`forget-password-otp-${email}`);
return ctx.json({ success: true });
}
await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
email,
otp,
type: "forget-password"
}, ctx));
return ctx.json({ success: true });
});
const forgetPasswordEmailOTPBodySchema = z.object({ email: z.string().meta({ description: "Email address to send the OTP" }) });
/**
* ### Endpoint
*
* POST `/forget-password/email-otp`
*
* ### API Methods
*
* **server:**
* `auth.api.forgetPasswordEmailOTP`
*
* **client:**
* `authClient.forgetPassword.emailOtp`
*
* @deprecated Use `/email-otp/request-password-reset` instead.
* @see [Read our docs to learn more.](https://www.better-auth.com/docs/plugins/email-otp#reset-password-with-otp)
*/
const forgetPasswordEmailOTP = (opts) => {
const warnDeprecation = deprecate(() => {}, "The \"/forget-password/email-otp\" endpoint is deprecated. Please use \"/email-otp/request-password-reset\" instead. This endpoint will be removed in the next major version.");
return createAuthEndpoint("/forget-password/email-otp", {
method: "POST",
body: forgetPasswordEmailOTPBodySchema,
metadata: { openapi: {
operationId: "forgetPasswordWithEmailOTP",
description: "Deprecated: Use /email-otp/request-password-reset instead.",
responses: { 200: {
description: "Success",
content: { "application/json": { schema: {
type: "object",
properties: { success: {
type: "boolean",
description: "Indicates if the OTP was sent successfully"
} }
} } }
} }
} }
}, async (ctx) => {
warnDeprecation();
const email = ctx.body.email;
const otp = opts.generateOTP({
email,
type: "forget-password"
}, ctx) || defaultOTPGenerator(opts);
const storedOTP = await storeOTP(ctx, opts, otp);
await ctx.context.internalAdapter.createVerificationValue({
value: `${storedOTP}:0`,
identifier: `forget-password-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
});
if (!await ctx.context.internalAdapter.findUserByEmail(email)) {
await ctx.context.internalAdapter.deleteVerificationByIdentifier(`forget-password-otp-${email}`);
return ctx.json({ success: true });
}
await ctx.context.runInBackgroundOrAwait(opts.sendVerificationOTP({
email,
otp,
type: "forget-password"
}, ctx));
return ctx.json({ success: true });
});
};
const resetPasswordEmailOTPBodySchema = z.object({
email: z.string().meta({ description: "Email address to reset the password" }),
otp: z.string().meta({ description: "OTP sent to the email" }),
password: z.string().meta({ description: "New password" })
});
/**
* ### Endpoint
*
* POST `/email-otp/reset-password`
*
* ### API Methods
*
* **server:**
* `auth.api.resetPasswordEmailOTP`
*
* **client:**
* `authClient.emailOtp.resetPassword`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-email-otp-reset-password)
*/
const resetPasswordEmailOTP = (opts) => createAuthEndpoint("/email-otp/reset-password", {
method: "POST",
body: resetPasswordEmailOTPBodySchema,
metadata: { openapi: {
operationId: "resetPasswordWithEmailOTP",
description: "Reset password with email and OTP",
responses: { 200: {
description: "Success",
content: { "application/json": { schema: {
type: "object",
properties: { success: { type: "boolean" } }
} } }
} }
} }
}, async (ctx) => {
const email = ctx.body.email;
const verificationValue = await ctx.context.internalAdapter.findVerificationValue(`forget-password-otp-${email}`);
if (!verificationValue) throw new APIError("BAD_REQUEST", { message: ERROR_CODES.INVALID_OTP });
if (verificationValue.expiresAt < /* @__PURE__ */ new Date()) {
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
throw new APIError("BAD_REQUEST", { message: ERROR_CODES.OTP_EXPIRED });
}
const [otpValue, attempts] = splitAtLastColon(verificationValue.value);
const allowedAttempts = opts?.allowedAttempts || 3;
if (attempts && parseInt(attempts) >= allowedAttempts) {
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
throw new APIError("FORBIDDEN", { message: ERROR_CODES.TOO_MANY_ATTEMPTS });
}
if (!await verifyStoredOTP(ctx, opts, otpValue, ctx.body.otp)) {
await ctx.context.internalAdapter.updateVerificationValue(verificationValue.id, { value: `${otpValue}:${parseInt(attempts || "0") + 1}` });
throw new APIError("BAD_REQUEST", { message: ERROR_CODES.INVALID_OTP });
}
await ctx.context.internalAdapter.deleteVerificationValue(verificationValue.id);
const user = await ctx.context.internalAdapter.findUserByEmail(email, { includeAccounts: true });
if (!user) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.USER_NOT_FOUND });
const minPasswordLength = ctx.context.password.config.minPasswordLength;
if (ctx.body.password.length < minPasswordLength) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.PASSWORD_TOO_SHORT });
const maxPasswordLength = ctx.context.password.config.maxPasswordLength;
if (ctx.body.password.length > maxPasswordLength) throw new APIError("BAD_REQUEST", { message: BASE_ERROR_CODES.PASSWORD_TOO_LONG });
const passwordHash = await ctx.context.password.hash(ctx.body.password);
if (!user.accounts?.find((account) => account.providerId === "credential")) await ctx.context.internalAdapter.createAccount({
userId: user.user.id,
providerId: "credential",
accountId: user.user.id,
password: passwordHash
});
else await ctx.context.internalAdapter.updatePassword(user.user.id, passwordHash);
if (ctx.context.options.emailAndPassword?.onPasswordReset) await ctx.context.options.emailAndPassword.onPasswordReset({ user: user.user }, ctx.request);
if (!user.user.emailVerified) await ctx.context.internalAdapter.updateUser(user.user.id, { emailVerified: true });
if (ctx.context.options.emailAndPassword?.revokeSessionsOnPasswordReset) await ctx.context.internalAdapter.deleteSessions(user.user.id);
return ctx.json({ success: true });
});
const defaultOTPGenerator = (options) => generateRandomString(options.otpLength ?? 6, "0-9");
//#endregion
export { ERROR_CODES, checkVerificationOTP, createVerificationOTP, forgetPasswordEmailOTP, getVerificationOTP, requestPasswordResetEmailOTP, resetPasswordEmailOTP, sendVerificationOTP, signInEmailOTP, verifyEmailOTP };
//# sourceMappingURL=routes.mjs.map