Skip to content

Commit e91be5a

Browse files
authored
fix: email OTP error codes and docs (#845)
1 parent 36e2ee2 commit e91be5a

File tree

3 files changed

+67
-45
lines changed

3 files changed

+67
-45
lines changed

docs/content/docs/guides/your-first-plugin.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,16 @@ import { createAuthMiddleware } from "better-auth/plugins";
127127
//...
128128
handler: createAuthMiddleware(async (ctx) => {
129129
const { birthday } = ctx.body;
130-
if(!birthday instanceof Date) throw APIError("BAD_REQUEST", { message: "Birthday must be of type Date." });
130+
if(!birthday instanceof Date) {
131+
throw new APIError("BAD_REQUEST", { message: "Birthday must be of type Date." });
132+
}
131133

132134
const today = new Date();
133135
const fiveYearsAgo = new Date(today.setFullYear(today.getFullYear() - 5));
134136

135-
if(birthday <= fiveYearsAgo) throw APIError("BAD_REQUEST", { message: "User must be above 5 years old." });
137+
if(birthday <= fiveYearsAgo) {
138+
throw new APIError("BAD_REQUEST", { message: "User must be above 5 years old." });
139+
}
136140

137141
return { context: ctx };
138142
}),

docs/content/docs/plugins/email-otp.mdx

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Email OTP
33
description: Email OTP plugin for Better Auth.
44
---
55

6-
The Email OTP plugin allows user to sign-in and verify their email using a one-time password (OTP) sent to their email address.
6+
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.
77

88

99
## Installation
@@ -50,12 +50,12 @@ The Email OTP plugin allows user to sign-in and verify their email using a one-t
5050

5151
### Send OTP
5252

53-
Before signing in or verifying email, you need to send an OTP to the user's email address.
53+
First, send an OTP to the user's email address.
5454

5555
```ts title="example.ts"
5656
await authClient.emailOtp.sendVerificationOtp({
5757
email: "user-email@email.com",
58-
type: "sign-in" // or "email-verification"
58+
type: "sign-in" // or "email-verification", "forget-password"
5959
})
6060
```
6161

@@ -70,8 +70,7 @@ const user = await authClient.signIn.emailOtp({
7070
})
7171
```
7272

73-
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.
74-
73+
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.
7574

7675
### Verify Email
7776

@@ -84,12 +83,24 @@ const user = await authClient.emailOtp.verifyEmail({
8483
})
8584
```
8685

86+
### Reset Password
87+
88+
To reset the user's password, use the `resetPassword()` method.
89+
90+
```ts title="example.ts"
91+
await authClient.emailOtp.resetPassword({
92+
email: "user-email@email.com",
93+
otp: "123456",
94+
password: "password"
95+
})
96+
```
97+
8798
## Options
8899

89100
- `sendVerificationOTP`: A function that sends the OTP to the user's email address. The function receives an object with the following properties:
90101
- `email`: The user's email address.
91102
- `otp`: The OTP to send.
92-
- `type`: The type of OTP to send. Can be either "sign-in" or "email-verification".
103+
- `type`: The type of OTP to send. Can be "sign-in", "email-verification", or "forget-password".
93104

94105
### Example
95106

@@ -104,19 +115,21 @@ export const auth = betterAuth({
104115
otp,
105116
type
106117
}) {
107-
if(type === "sign-in") {
118+
if (type === "sign-in") {
108119
// Send the OTP for sign-in
109-
} else {
120+
} else if (type === "email-verification") {
110121
// Send the OTP for email verification
122+
} else {
123+
// Send the OTP for password reset
111124
}
112125
},
113126
})
114127
]
115128
})
116129
```
117130

118-
- `otpLength`: The length of the OTP. Defaults to 6.
119-
- `otpExpiry`: The expiry time of the OTP in seconds. Defaults to 300 seconds.
131+
- `otpLength`: The length of the OTP. Defaults to `6`.
132+
- `otpExpiry`: The expiry time of the OTP in seconds. Defaults to `300` seconds.
120133

121134
```ts title="auth.ts"
122135
import { betterAuth } from "better-auth"
@@ -131,6 +144,6 @@ export const auth = betterAuth({
131144
})
132145
```
133146

134-
- `sendVerificationOnSignUp`: A boolean value that determines whether to send the OTP when a user signs up. Defaults to false.
147+
- `sendVerificationOnSignUp`: A boolean value that determines whether to send the OTP when a user signs up. Defaults to `false`.
135148

136-
- `disableSignUp`: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to false.
149+
- `disableSignUp`: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to `false`.

packages/better-auth/src/plugins/email-otp/index.ts

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { INVALID, z } from "zod";
1+
import { z } from "zod";
22
import { APIError, createAuthEndpoint } from "../../api";
33
import type { BetterAuthPlugin, User } from "../../types";
44
import { alphabet, generateRandomString } from "../../crypto";
55
import { getDate } from "../../utils/date";
66
import { setSessionCookie } from "../../cookies";
7-
import { getEndpointResponse } from "../../utils/plugin-helper";
87

98
interface EmailOTPOptions {
109
/**
@@ -273,30 +272,30 @@ export const emailOTP = (options: EmailOTPOptions) => {
273272
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
274273
if (!emailRegex.test(email)) {
275274
throw new APIError("BAD_REQUEST", {
276-
message: "Invalid email",
275+
message: ERROR_CODES.INVALID_EMAIL,
277276
});
278277
}
279278
const verificationValue =
280279
await ctx.context.internalAdapter.findVerificationValue(
281280
`email-verification-otp-${email}`,
282281
);
283-
if (!verificationValue || verificationValue.expiresAt < new Date()) {
284-
if (verificationValue) {
285-
await ctx.context.internalAdapter.deleteVerificationValue(
286-
verificationValue.id,
287-
);
288-
throw new APIError("BAD_REQUEST", {
289-
message: "OTP expired",
290-
});
291-
}
282+
if (!verificationValue) {
292283
throw new APIError("BAD_REQUEST", {
293-
message: "Invalid OTP",
284+
message: ERROR_CODES.INVALID_OTP,
285+
});
286+
}
287+
if (verificationValue.expiresAt < new Date()) {
288+
await ctx.context.internalAdapter.deleteVerificationValue(
289+
verificationValue.id,
290+
);
291+
throw new APIError("BAD_REQUEST", {
292+
message: ERROR_CODES.OTP_EXPIRED,
294293
});
295294
}
296295
const otp = ctx.body.otp;
297296
if (verificationValue.value !== otp) {
298297
throw new APIError("BAD_REQUEST", {
299-
message: "Invalid OTP",
298+
message: ERROR_CODES.INVALID_OTP,
300299
});
301300
}
302301
await ctx.context.internalAdapter.deleteVerificationValue(
@@ -305,7 +304,7 @@ export const emailOTP = (options: EmailOTPOptions) => {
305304
const user = await ctx.context.internalAdapter.findUserByEmail(email);
306305
if (!user) {
307306
throw new APIError("BAD_REQUEST", {
308-
message: "User not found",
307+
message: ERROR_CODES.USER_NOT_FOUND,
309308
});
310309
}
311310
const updatedUser = await ctx.context.internalAdapter.updateUser(
@@ -370,20 +369,23 @@ export const emailOTP = (options: EmailOTPOptions) => {
370369
await ctx.context.internalAdapter.findVerificationValue(
371370
`sign-in-otp-${email}`,
372371
);
373-
if (!verificationValue || verificationValue.expiresAt < new Date()) {
374-
if (verificationValue) {
375-
await ctx.context.internalAdapter.deleteVerificationValue(
376-
verificationValue.id,
377-
);
378-
}
372+
if (!verificationValue) {
379373
throw new APIError("BAD_REQUEST", {
380-
message: "Invalid OTP",
374+
message: ERROR_CODES.INVALID_OTP,
375+
});
376+
}
377+
if (verificationValue.expiresAt < new Date()) {
378+
await ctx.context.internalAdapter.deleteVerificationValue(
379+
verificationValue.id,
380+
);
381+
throw new APIError("BAD_REQUEST", {
382+
message: ERROR_CODES.OTP_EXPIRED,
381383
});
382384
}
383385
const otp = ctx.body.otp;
384386
if (verificationValue.value !== otp) {
385387
throw new APIError("BAD_REQUEST", {
386-
message: "Invalid OTP",
388+
message: ERROR_CODES.INVALID_OTP,
387389
});
388390
}
389391
await ctx.context.internalAdapter.deleteVerificationValue(
@@ -393,7 +395,7 @@ export const emailOTP = (options: EmailOTPOptions) => {
393395
if (!user) {
394396
if (opts.disableSignUp) {
395397
throw new APIError("BAD_REQUEST", {
396-
message: "User not found",
398+
message: ERROR_CODES.USER_NOT_FOUND,
397399
});
398400
}
399401
const newUser = await ctx.context.internalAdapter.createUser({
@@ -472,7 +474,7 @@ export const emailOTP = (options: EmailOTPOptions) => {
472474
const user = await ctx.context.internalAdapter.findUserByEmail(email);
473475
if (!user) {
474476
throw new APIError("BAD_REQUEST", {
475-
message: "User not found",
477+
message: ERROR_CODES.USER_NOT_FOUND,
476478
});
477479
}
478480
const otp = generateRandomString(opts.otpLength, alphabet("0-9"));
@@ -544,12 +546,15 @@ export const emailOTP = (options: EmailOTPOptions) => {
544546
await ctx.context.internalAdapter.findVerificationValue(
545547
`forget-password-otp-${email}`,
546548
);
547-
if (!verificationValue || verificationValue.expiresAt < new Date()) {
548-
if (verificationValue) {
549-
await ctx.context.internalAdapter.deleteVerificationValue(
550-
verificationValue.id,
551-
);
552-
}
549+
if (!verificationValue) {
550+
throw new APIError("BAD_REQUEST", {
551+
message: ERROR_CODES.INVALID_OTP,
552+
});
553+
}
554+
if (verificationValue.expiresAt < new Date()) {
555+
await ctx.context.internalAdapter.deleteVerificationValue(
556+
verificationValue.id,
557+
);
553558
throw new APIError("BAD_REQUEST", {
554559
message: ERROR_CODES.OTP_EXPIRED,
555560
});

0 commit comments

Comments
 (0)