@zpg6-test-pkgs/better-auth
Version:
The most comprehensive authentication library for TypeScript.
1,125 lines (1,121 loc) • 35.8 kB
JavaScript
import * as z from 'zod/v4';
import { APIError } from 'better-call';
import '../../shared/better-auth.D7aTFyWE.mjs';
import { a as createAuthEndpoint, c as createAuthMiddleware } from '../../shared/better-auth.BfeJWAMn.mjs';
import { s as setSessionCookie } from '../../shared/better-auth.DF-MUmVw.mjs';
import '../../shared/better-auth.n2KFGwjY.mjs';
import '../../shared/better-auth.CMQ3rA-I.mjs';
import '../../shared/better-auth.BjBlybv-.mjs';
import { symmetricDecrypt, symmetricEncrypt } from '../../crypto/index.mjs';
import { g as getDate } from '../../shared/better-auth.CW6D9eSx.mjs';
import { g as getEndpointResponse } from '../../shared/better-auth.DQI8AD7d.mjs';
import { createHash } from '@better-auth/utils/hash';
import { base64Url } from '@better-auth/utils/base64';
import { g as generateRandomString } from '../../shared/better-auth.B4Qoxdgc.mjs';
import '@better-fetch/fetch';
import 'jose';
import '@noble/ciphers/chacha';
import '@noble/ciphers/utils';
import '@noble/ciphers/webcrypto';
import '@noble/hashes/scrypt';
import '@better-auth/utils';
import '@better-auth/utils/hex';
import '@noble/hashes/utils';
import '../../shared/better-auth.CuS_eDdK.mjs';
import '../../shared/better-auth.DdzSJf-n.mjs';
import 'jose/errors';
import '@better-auth/utils/random';
import '../../shared/better-auth.BZZKN1g7.mjs';
import '../../shared/better-auth.BUPPRXfK.mjs';
import '@better-auth/utils/hmac';
import '@better-auth/utils/binary';
import 'defu';
const defaultKeyHasher = async (otp) => {
const hash = await createHash("SHA-256").digest(
new TextEncoder().encode(otp)
);
const hashed = base64Url.encode(new Uint8Array(hash), {
padding: false
});
return hashed;
};
function splitAtLastColon(input) {
const idx = input.lastIndexOf(":");
if (idx === -1) {
return [input, ""];
}
return [input.slice(0, idx), input.slice(idx + 1)];
}
const types = ["email-verification", "sign-in", "forget-password"];
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const emailOTP = (options) => {
const opts = {
expiresIn: 5 * 60,
generateOTP: () => generateRandomString(options.otpLength ?? 6, "0-9"),
storeOTP: "plain",
...options
};
const ERROR_CODES = {
OTP_EXPIRED: "otp expired",
INVALID_OTP: "Invalid OTP",
INVALID_EMAIL: "Invalid email",
USER_NOT_FOUND: "User not found",
TOO_MANY_ATTEMPTS: "Too many attempts"
};
async function storeOTP(ctx, otp) {
if (opts.storeOTP === "encrypted") {
return await symmetricEncrypt({
key: ctx.context.secret,
data: otp
});
}
if (opts.storeOTP === "hashed") {
return await defaultKeyHasher(otp);
}
if (typeof opts.storeOTP === "object" && "hash" in opts.storeOTP) {
return await opts.storeOTP.hash(otp);
}
if (typeof opts.storeOTP === "object" && "encrypt" in opts.storeOTP) {
return await opts.storeOTP.encrypt(otp);
}
return otp;
}
async function verifyStoredOTP(ctx, storedOtp, otp) {
if (opts.storeOTP === "encrypted") {
return await symmetricDecrypt({
key: ctx.context.secret,
data: storedOtp
}) === otp;
}
if (opts.storeOTP === "hashed") {
const hashedOtp = await defaultKeyHasher(otp);
return hashedOtp === storedOtp;
}
if (typeof opts.storeOTP === "object" && "hash" in opts.storeOTP) {
const hashedOtp = await opts.storeOTP.hash(otp);
return hashedOtp === storedOtp;
}
if (typeof opts.storeOTP === "object" && "decrypt" in opts.storeOTP) {
const decryptedOtp = await opts.storeOTP.decrypt(storedOtp);
return decryptedOtp === otp;
}
return otp === storedOtp;
}
const endpoints = {
/**
* ### 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)
*/
sendVerificationOTP: createAuthEndpoint(
"/email-otp/send-verification-otp",
{
method: "POST",
body: z.object({
email: z.string({}).meta({
description: "Email address to send the OTP"
}),
type: z.enum(types).meta({
description: "Type of the OTP"
})
}),
metadata: {
openapi: {
description: "Send verification OTP",
responses: {
200: {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: {
type: "boolean"
}
}
}
}
}
}
}
}
}
},
async (ctx) => {
if (!options?.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;
if (!emailRegex.test(email)) {
throw ctx.error("BAD_REQUEST", {
message: ERROR_CODES.INVALID_EMAIL
});
}
if (opts.disableSignUp) {
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
throw new APIError("BAD_REQUEST", {
message: ERROR_CODES.USER_NOT_FOUND
});
}
} else if (ctx.body.type === "forget-password") {
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
return ctx.json({
success: true
});
}
}
let otp = opts.generateOTP({ email, type: ctx.body.type }, ctx.request);
let storedOTP = await storeOTP(ctx, otp);
await ctx.context.internalAdapter.createVerificationValue(
{
value: `${storedOTP}:0`,
identifier: `${ctx.body.type}-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
},
ctx
).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")
},
ctx
);
});
await options.sendVerificationOTP(
{
email,
otp,
type: ctx.body.type
},
ctx.request
);
return ctx.json({
success: true
});
}
)
};
return {
id: "email-otp",
init(ctx) {
if (!opts.overrideDefaultEmailVerification) {
return;
}
return {
options: {
emailVerification: {
async sendVerificationEmail(data, request) {
await endpoints.sendVerificationOTP({
//@ts-expect-error - we need to pass the context
context: ctx,
request,
body: {
email: data.user.email,
type: "email-verification"
},
ctx
});
}
}
}
};
},
endpoints: {
...endpoints,
createVerificationOTP: createAuthEndpoint(
"/email-otp/create-verification-otp",
{
method: "POST",
body: 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"
})
}),
metadata: {
SERVER_ONLY: true,
openapi: {
description: "Create verification OTP",
responses: {
200: {
description: "Success",
content: {
"application/json": {
schema: {
type: "string"
}
}
}
}
}
}
}
},
async (ctx) => {
const email = ctx.body.email;
const otp = opts.generateOTP(
{ email, type: ctx.body.type },
ctx.request
);
let storedOTP = await storeOTP(ctx, otp);
await ctx.context.internalAdapter.createVerificationValue(
{
value: `${storedOTP}:0`,
identifier: `${ctx.body.type}-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
},
ctx
);
return 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)
*/
getVerificationOTP: createAuthEndpoint(
"/email-otp/get-verification-otp",
{
method: "GET",
query: 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"
})
}),
metadata: {
SERVER_ONLY: true,
openapi: {
description: "Get verification OTP",
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;
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"
});
}
let [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
});
}
),
/**
* ### 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)
*/
checkVerificationOTP: createAuthEndpoint(
"/email-otp/check-verification-otp",
{
method: "POST",
body: 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"
})
}),
metadata: {
openapi: {
description: "Check if a verification OTP is valid",
responses: {
200: {
description: "Success",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: {
type: "boolean"
}
}
}
}
}
}
}
}
}
},
async (ctx) => {
const email = ctx.body.email;
if (!emailRegex.test(email)) {
throw new APIError("BAD_REQUEST", {
message: ERROR_CODES.INVALID_EMAIL
});
}
const user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
throw new APIError("BAD_REQUEST", {
message: 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 = options?.allowedAttempts || 3;
if (attempts && parseInt(attempts) >= allowedAttempts) {
await ctx.context.internalAdapter.deleteVerificationValue(
verificationValue.id
);
throw new APIError("FORBIDDEN", {
message: ERROR_CODES.TOO_MANY_ATTEMPTS
});
}
const verified = await verifyStoredOTP(ctx, otpValue, ctx.body.otp);
if (!verified) {
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
});
}
),
/**
* ### 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)
*/
verifyEmailOTP: createAuthEndpoint(
"/email-otp/verify-email",
{
method: "POST",
body: z.object({
email: z.string({}).meta({
description: "Email address to verify"
}),
otp: z.string().meta({
required: true,
description: "OTP to verify"
})
}),
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;
if (!emailRegex.test(email)) {
throw new APIError("BAD_REQUEST", {
message: 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 = options?.allowedAttempts || 3;
if (attempts && parseInt(attempts) >= allowedAttempts) {
await ctx.context.internalAdapter.deleteVerificationValue(
verificationValue.id
);
throw new APIError("FORBIDDEN", {
message: ERROR_CODES.TOO_MANY_ATTEMPTS
});
}
const verified = await verifyStoredOTP(ctx, otpValue, ctx.body.otp);
if (!verified) {
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) {
throw new APIError("BAD_REQUEST", {
message: ERROR_CODES.USER_NOT_FOUND
});
}
const updatedUser = await ctx.context.internalAdapter.updateUser(
user.user.id,
{
email,
emailVerified: true
},
ctx
);
await ctx.context.options.emailVerification?.onEmailVerification?.(
updatedUser,
ctx.request
);
if (ctx.context.options.emailVerification?.autoSignInAfterVerification) {
const session = await ctx.context.internalAdapter.createSession(
updatedUser.id,
ctx
);
await setSessionCookie(ctx, {
session,
user: updatedUser
});
return ctx.json({
status: true,
token: session.token,
user: {
id: updatedUser.id,
email: updatedUser.email,
emailVerified: updatedUser.emailVerified,
name: updatedUser.name,
image: updatedUser.image,
createdAt: updatedUser.createdAt,
updatedAt: updatedUser.updatedAt
}
});
}
return ctx.json({
status: true,
token: null,
user: {
id: updatedUser.id,
email: updatedUser.email,
emailVerified: updatedUser.emailVerified,
name: updatedUser.name,
image: updatedUser.image,
createdAt: updatedUser.createdAt,
updatedAt: updatedUser.updatedAt
}
});
}
),
/**
* ### 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)
*/
signInEmailOTP: createAuthEndpoint(
"/sign-in/email-otp",
{
method: "POST",
body: z.object({
email: z.string({}).meta({
description: "Email address to sign in"
}),
otp: z.string().meta({
required: true,
description: "OTP sent to the email"
})
}),
metadata: {
openapi: {
description: "Sign in with 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;
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 = options?.allowedAttempts || 3;
if (attempts && parseInt(attempts) >= allowedAttempts) {
await ctx.context.internalAdapter.deleteVerificationValue(
verificationValue.id
);
throw new APIError("FORBIDDEN", {
message: ERROR_CODES.TOO_MANY_ATTEMPTS
});
}
const verified = await verifyStoredOTP(ctx, otpValue, ctx.body.otp);
if (!verified) {
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: ERROR_CODES.USER_NOT_FOUND
});
}
const newUser = await ctx.context.internalAdapter.createUser(
{
email,
emailVerified: true,
name: ""
},
ctx
);
const session2 = await ctx.context.internalAdapter.createSession(
newUser.id,
ctx
);
await setSessionCookie(ctx, {
session: session2,
user: newUser
});
return ctx.json({
token: session2.token,
user: {
id: newUser.id,
email: newUser.email,
emailVerified: newUser.emailVerified,
name: newUser.name,
image: newUser.image,
createdAt: newUser.createdAt,
updatedAt: newUser.updatedAt
}
});
}
if (!user.user.emailVerified) {
await ctx.context.internalAdapter.updateUser(
user.user.id,
{
emailVerified: true
},
ctx
);
}
const session = await ctx.context.internalAdapter.createSession(
user.user.id,
ctx
);
await setSessionCookie(ctx, {
session,
user: user.user
});
return ctx.json({
token: session.token,
user: {
id: user.user.id,
email: user.user.email,
emailVerified: user.user.emailVerified,
name: user.user.name,
image: user.user.image,
createdAt: user.user.createdAt,
updatedAt: user.user.updatedAt
}
});
}
),
/**
* ### Endpoint
*
* POST `/forget-password/email-otp`
*
* ### API Methods
*
* **server:**
* `auth.api.forgetPasswordEmailOTP`
*
* **client:**
* `authClient.forgetPassword.emailOtp`
*
* @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/email-otp#api-method-forget-password-email-otp)
*/
forgetPasswordEmailOTP: createAuthEndpoint(
"/forget-password/email-otp",
{
method: "POST",
body: z.object({
email: z.string().meta({
description: "Email address to send the OTP"
})
}),
metadata: {
openapi: {
description: "Send a password reset OTP to the user",
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 user = await ctx.context.internalAdapter.findUserByEmail(email);
if (!user) {
throw new APIError("BAD_REQUEST", {
message: ERROR_CODES.USER_NOT_FOUND
});
}
const otp = opts.generateOTP(
{ email, type: "forget-password" },
ctx.request
);
let storedOTP = await storeOTP(ctx, otp);
await ctx.context.internalAdapter.createVerificationValue(
{
value: `${storedOTP}:0`,
identifier: `forget-password-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
},
ctx
);
await options.sendVerificationOTP(
{
email,
otp,
type: "forget-password"
},
ctx.request
);
return ctx.json({
success: true
});
}
),
/**
* ### 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)
*/
resetPasswordEmailOTP: createAuthEndpoint(
"/email-otp/reset-password",
{
method: "POST",
body: 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"
})
}),
metadata: {
openapi: {
description: "Reset user password with OTP",
responses: {
200: {
description: "Success",
contnt: {
"application/json": {
schema: {
type: "object",
properties: {
success: {
type: "boolean"
}
}
}
}
}
}
}
}
}
},
async (ctx) => {
const email = ctx.body.email;
const user = await ctx.context.internalAdapter.findUserByEmail(
email,
{
includeAccounts: true
}
);
if (!user) {
throw new APIError("BAD_REQUEST", {
message: ERROR_CODES.USER_NOT_FOUND
});
}
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 = options?.allowedAttempts || 3;
if (attempts && parseInt(attempts) >= allowedAttempts) {
await ctx.context.internalAdapter.deleteVerificationValue(
verificationValue.id
);
throw new APIError("FORBIDDEN", {
message: ERROR_CODES.TOO_MANY_ATTEMPTS
});
}
const verified = await verifyStoredOTP(ctx, otpValue, ctx.body.otp);
if (!verified) {
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 passwordHash = await ctx.context.password.hash(
ctx.body.password
);
const account = user.accounts.find(
(account2) => account2.providerId === "credential"
);
if (!account) {
await ctx.context.internalAdapter.createAccount(
{
userId: user.user.id,
providerId: "credential",
accountId: user.user.id,
password: passwordHash
},
ctx
);
} else {
await ctx.context.internalAdapter.updatePassword(
user.user.id,
passwordHash,
ctx
);
}
if (!user.user.emailVerified) {
await ctx.context.internalAdapter.updateUser(
user.user.id,
{
emailVerified: true
},
ctx
);
}
return ctx.json({
success: true
});
}
)
},
hooks: {
after: [
{
matcher(context) {
return !!(context.path?.startsWith("/sign-up") && opts.sendVerificationOnSignUp);
},
handler: createAuthMiddleware(async (ctx) => {
const response = await getEndpointResponse(ctx);
const email = response?.user.email;
if (email) {
const otp = opts.generateOTP(
{ email, type: ctx.body.type },
ctx.request
);
let storedOTP = await storeOTP(ctx, otp);
await ctx.context.internalAdapter.createVerificationValue(
{
value: `${storedOTP}:0`,
identifier: `email-verification-otp-${email}`,
expiresAt: getDate(opts.expiresIn, "sec")
},
ctx
);
await options.sendVerificationOTP(
{
email,
otp,
type: "email-verification"
},
ctx.request
);
}
})
}
]
},
$ERROR_CODES: ERROR_CODES,
rateLimit: [
{
pathMatcher(path) {
return path === "/email-otp/send-verification-otp";
},
window: 60,
max: 3
},
{
pathMatcher(path) {
return path === "/email-otp/check-verification-otp";
},
window: 60,
max: 3
},
{
pathMatcher(path) {
return path === "/email-otp/verify-email";
},
window: 60,
max: 3
},
{
pathMatcher(path) {
return path === "/sign-in/email-otp";
},
window: 60,
max: 3
}
]
};
};
export { emailOTP };