mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-09 20:27:44 +00:00
fix: email OTP error codes and docs (#845)
This commit is contained in:
@@ -127,12 +127,16 @@ import { createAuthMiddleware } from "better-auth/plugins";
|
|||||||
//...
|
//...
|
||||||
handler: createAuthMiddleware(async (ctx) => {
|
handler: createAuthMiddleware(async (ctx) => {
|
||||||
const { birthday } = ctx.body;
|
const { birthday } = ctx.body;
|
||||||
if(!birthday instanceof Date) throw APIError("BAD_REQUEST", { message: "Birthday must be of type Date." });
|
if(!birthday instanceof Date) {
|
||||||
|
throw new APIError("BAD_REQUEST", { message: "Birthday must be of type Date." });
|
||||||
|
}
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const fiveYearsAgo = new Date(today.setFullYear(today.getFullYear() - 5));
|
const fiveYearsAgo = new Date(today.setFullYear(today.getFullYear() - 5));
|
||||||
|
|
||||||
if(birthday <= fiveYearsAgo) throw APIError("BAD_REQUEST", { message: "User must be above 5 years old." });
|
if(birthday <= fiveYearsAgo) {
|
||||||
|
throw new APIError("BAD_REQUEST", { message: "User must be above 5 years old." });
|
||||||
|
}
|
||||||
|
|
||||||
return { context: ctx };
|
return { context: ctx };
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ title: Email OTP
|
|||||||
description: Email OTP plugin for Better Auth.
|
description: Email OTP plugin for Better Auth.
|
||||||
---
|
---
|
||||||
|
|
||||||
The Email OTP plugin allows user to sign-in and verify their email using a one-time password (OTP) sent to their email address.
|
The Email OTP plugin allows user to sign-in, verify their email, or reset their password using a one-time password (OTP) sent to their email address.
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -50,12 +50,12 @@ The Email OTP plugin allows user to sign-in and verify their email using a one-t
|
|||||||
|
|
||||||
### Send OTP
|
### Send OTP
|
||||||
|
|
||||||
Before signing in or verifying email, you need to send an OTP to the user's email address.
|
First, send an OTP to the user's email address.
|
||||||
|
|
||||||
```ts title="example.ts"
|
```ts title="example.ts"
|
||||||
await authClient.emailOtp.sendVerificationOtp({
|
await authClient.emailOtp.sendVerificationOtp({
|
||||||
email: "user-email@email.com",
|
email: "user-email@email.com",
|
||||||
type: "sign-in" // or "email-verification"
|
type: "sign-in" // or "email-verification", "forget-password"
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -70,8 +70,7 @@ const user = await authClient.signIn.emailOtp({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
If the user is not registered, it'll be automatically registered. If you want to prevent this, you can pass `disableSignUp` as `true` in the options.
|
If the user is not registered, they'll be automatically registered. If you want to prevent this, you can pass `disableSignUp` as `true` in the options.
|
||||||
|
|
||||||
|
|
||||||
### Verify Email
|
### Verify Email
|
||||||
|
|
||||||
@@ -84,12 +83,24 @@ const user = await authClient.emailOtp.verifyEmail({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Reset Password
|
||||||
|
|
||||||
|
To reset the user's password, use the `resetPassword()` method.
|
||||||
|
|
||||||
|
```ts title="example.ts"
|
||||||
|
await authClient.emailOtp.resetPassword({
|
||||||
|
email: "user-email@email.com",
|
||||||
|
otp: "123456",
|
||||||
|
password: "password"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
- `sendVerificationOTP`: A function that sends the OTP to the user's email address. The function receives an object with the following properties:
|
- `sendVerificationOTP`: A function that sends the OTP to the user's email address. The function receives an object with the following properties:
|
||||||
- `email`: The user's email address.
|
- `email`: The user's email address.
|
||||||
- `otp`: The OTP to send.
|
- `otp`: The OTP to send.
|
||||||
- `type`: The type of OTP to send. Can be either "sign-in" or "email-verification".
|
- `type`: The type of OTP to send. Can be "sign-in", "email-verification", or "forget-password".
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
@@ -104,10 +115,12 @@ export const auth = betterAuth({
|
|||||||
otp,
|
otp,
|
||||||
type
|
type
|
||||||
}) {
|
}) {
|
||||||
if(type === "sign-in") {
|
if (type === "sign-in") {
|
||||||
// Send the OTP for sign-in
|
// Send the OTP for sign-in
|
||||||
} else {
|
} else if (type === "email-verification") {
|
||||||
// Send the OTP for email verification
|
// Send the OTP for email verification
|
||||||
|
} else {
|
||||||
|
// Send the OTP for password reset
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -115,8 +128,8 @@ export const auth = betterAuth({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
- `otpLength`: The length of the OTP. Defaults to 6.
|
- `otpLength`: The length of the OTP. Defaults to `6`.
|
||||||
- `otpExpiry`: The expiry time of the OTP in seconds. Defaults to 300 seconds.
|
- `otpExpiry`: The expiry time of the OTP in seconds. Defaults to `300` seconds.
|
||||||
|
|
||||||
```ts title="auth.ts"
|
```ts title="auth.ts"
|
||||||
import { betterAuth } from "better-auth"
|
import { betterAuth } from "better-auth"
|
||||||
@@ -131,6 +144,6 @@ export const auth = betterAuth({
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
- `sendVerificationOnSignUp`: A boolean value that determines whether to send the OTP when a user signs up. Defaults to false.
|
- `sendVerificationOnSignUp`: A boolean value that determines whether to send the OTP when a user signs up. Defaults to `false`.
|
||||||
|
|
||||||
- `disableSignUp`: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to false.
|
- `disableSignUp`: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to `false`.
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import { INVALID, z } from "zod";
|
import { z } from "zod";
|
||||||
import { APIError, createAuthEndpoint } from "../../api";
|
import { APIError, createAuthEndpoint } from "../../api";
|
||||||
import type { BetterAuthPlugin, User } from "../../types";
|
import type { BetterAuthPlugin, User } from "../../types";
|
||||||
import { alphabet, generateRandomString } from "../../crypto";
|
import { alphabet, generateRandomString } from "../../crypto";
|
||||||
import { getDate } from "../../utils/date";
|
import { getDate } from "../../utils/date";
|
||||||
import { setSessionCookie } from "../../cookies";
|
import { setSessionCookie } from "../../cookies";
|
||||||
import { getEndpointResponse } from "../../utils/plugin-helper";
|
|
||||||
|
|
||||||
interface EmailOTPOptions {
|
interface EmailOTPOptions {
|
||||||
/**
|
/**
|
||||||
@@ -273,30 +272,30 @@ export const emailOTP = (options: EmailOTPOptions) => {
|
|||||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||||
if (!emailRegex.test(email)) {
|
if (!emailRegex.test(email)) {
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: "Invalid email",
|
message: ERROR_CODES.INVALID_EMAIL,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const verificationValue =
|
const verificationValue =
|
||||||
await ctx.context.internalAdapter.findVerificationValue(
|
await ctx.context.internalAdapter.findVerificationValue(
|
||||||
`email-verification-otp-${email}`,
|
`email-verification-otp-${email}`,
|
||||||
);
|
);
|
||||||
if (!verificationValue || verificationValue.expiresAt < new Date()) {
|
if (!verificationValue) {
|
||||||
if (verificationValue) {
|
|
||||||
await ctx.context.internalAdapter.deleteVerificationValue(
|
|
||||||
verificationValue.id,
|
|
||||||
);
|
|
||||||
throw new APIError("BAD_REQUEST", {
|
|
||||||
message: "OTP expired",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: "Invalid OTP",
|
message: ERROR_CODES.INVALID_OTP,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (verificationValue.expiresAt < new Date()) {
|
||||||
|
await ctx.context.internalAdapter.deleteVerificationValue(
|
||||||
|
verificationValue.id,
|
||||||
|
);
|
||||||
|
throw new APIError("BAD_REQUEST", {
|
||||||
|
message: ERROR_CODES.OTP_EXPIRED,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const otp = ctx.body.otp;
|
const otp = ctx.body.otp;
|
||||||
if (verificationValue.value !== otp) {
|
if (verificationValue.value !== otp) {
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: "Invalid OTP",
|
message: ERROR_CODES.INVALID_OTP,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await ctx.context.internalAdapter.deleteVerificationValue(
|
await ctx.context.internalAdapter.deleteVerificationValue(
|
||||||
@@ -305,7 +304,7 @@ export const emailOTP = (options: EmailOTPOptions) => {
|
|||||||
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: "User not found",
|
message: ERROR_CODES.USER_NOT_FOUND,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const updatedUser = await ctx.context.internalAdapter.updateUser(
|
const updatedUser = await ctx.context.internalAdapter.updateUser(
|
||||||
@@ -370,20 +369,23 @@ export const emailOTP = (options: EmailOTPOptions) => {
|
|||||||
await ctx.context.internalAdapter.findVerificationValue(
|
await ctx.context.internalAdapter.findVerificationValue(
|
||||||
`sign-in-otp-${email}`,
|
`sign-in-otp-${email}`,
|
||||||
);
|
);
|
||||||
if (!verificationValue || verificationValue.expiresAt < new Date()) {
|
if (!verificationValue) {
|
||||||
if (verificationValue) {
|
|
||||||
await ctx.context.internalAdapter.deleteVerificationValue(
|
|
||||||
verificationValue.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: "Invalid OTP",
|
message: ERROR_CODES.INVALID_OTP,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (verificationValue.expiresAt < new Date()) {
|
||||||
|
await ctx.context.internalAdapter.deleteVerificationValue(
|
||||||
|
verificationValue.id,
|
||||||
|
);
|
||||||
|
throw new APIError("BAD_REQUEST", {
|
||||||
|
message: ERROR_CODES.OTP_EXPIRED,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const otp = ctx.body.otp;
|
const otp = ctx.body.otp;
|
||||||
if (verificationValue.value !== otp) {
|
if (verificationValue.value !== otp) {
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: "Invalid OTP",
|
message: ERROR_CODES.INVALID_OTP,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await ctx.context.internalAdapter.deleteVerificationValue(
|
await ctx.context.internalAdapter.deleteVerificationValue(
|
||||||
@@ -393,7 +395,7 @@ export const emailOTP = (options: EmailOTPOptions) => {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
if (opts.disableSignUp) {
|
if (opts.disableSignUp) {
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: "User not found",
|
message: ERROR_CODES.USER_NOT_FOUND,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const newUser = await ctx.context.internalAdapter.createUser({
|
const newUser = await ctx.context.internalAdapter.createUser({
|
||||||
@@ -472,7 +474,7 @@ export const emailOTP = (options: EmailOTPOptions) => {
|
|||||||
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
const user = await ctx.context.internalAdapter.findUserByEmail(email);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: "User not found",
|
message: ERROR_CODES.USER_NOT_FOUND,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const otp = generateRandomString(opts.otpLength, alphabet("0-9"));
|
const otp = generateRandomString(opts.otpLength, alphabet("0-9"));
|
||||||
@@ -544,12 +546,15 @@ export const emailOTP = (options: EmailOTPOptions) => {
|
|||||||
await ctx.context.internalAdapter.findVerificationValue(
|
await ctx.context.internalAdapter.findVerificationValue(
|
||||||
`forget-password-otp-${email}`,
|
`forget-password-otp-${email}`,
|
||||||
);
|
);
|
||||||
if (!verificationValue || verificationValue.expiresAt < new Date()) {
|
if (!verificationValue) {
|
||||||
if (verificationValue) {
|
throw new APIError("BAD_REQUEST", {
|
||||||
await ctx.context.internalAdapter.deleteVerificationValue(
|
message: ERROR_CODES.INVALID_OTP,
|
||||||
verificationValue.id,
|
});
|
||||||
);
|
}
|
||||||
}
|
if (verificationValue.expiresAt < new Date()) {
|
||||||
|
await ctx.context.internalAdapter.deleteVerificationValue(
|
||||||
|
verificationValue.id,
|
||||||
|
);
|
||||||
throw new APIError("BAD_REQUEST", {
|
throw new APIError("BAD_REQUEST", {
|
||||||
message: ERROR_CODES.OTP_EXPIRED,
|
message: ERROR_CODES.OTP_EXPIRED,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user