-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Expand file tree
/
Copy pathroute.ts
More file actions
129 lines (115 loc) · 3.44 KB
/
Copy pathroute.ts
File metadata and controls
129 lines (115 loc) · 3.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { DubApiError, handleAndReturnErrorResponse } from "@/lib/api/errors";
import { parseRequestBody } from "@/lib/api/utils";
import { getIP } from "@/lib/api/utils/get-ip";
import { hashPassword, validatePassword } from "@/lib/auth/password";
import { prisma } from "@/lib/prisma";
import { assertRateLimit } from "@/lib/upstash/assert-rate-limit";
import { RATELIMIT_POLICIES } from "@/lib/upstash/ratelimit-policies";
import { resetPasswordSchema } from "@/lib/zod/schemas/auth";
import { sendEmail } from "@dub/email";
import PasswordUpdated from "@dub/email/templates/password-updated";
import { waitUntil } from "@vercel/functions";
import { NextRequest, NextResponse } from "next/server";
// POST /api/auth/reset-password - reset password using the reset token
export async function POST(req: NextRequest) {
try {
await assertRateLimit({
policy: RATELIMIT_POLICIES.passwordReset,
identifier: await getIP(),
});
const { token, password } = resetPasswordSchema.parse(
await parseRequestBody(req),
);
// Find the token
const tokenFound = await prisma.passwordResetToken.findFirst({
where: {
token,
expires: {
gte: new Date(),
},
},
select: {
identifier: true,
},
});
if (!tokenFound) {
throw new DubApiError({
code: "not_found",
message:
"Password reset token not found or expired. Please request a new one.",
});
}
const { identifier } = tokenFound;
const user = await prisma.user.findUniqueOrThrow({
where: {
email: identifier,
},
select: {
id: true,
emailVerified: true,
passwordHash: true,
},
});
// Check if the new password is the same as the current password
if (user.passwordHash) {
const isSamePassword = await validatePassword({
password,
passwordHash: user.passwordHash,
});
if (isSamePassword) {
throw new DubApiError({
code: "unprocessable_entity",
message:
"Your new password cannot be the same as your current password.",
});
}
}
const newPasswordHash = await hashPassword(password);
await prisma.$transaction([
// Delete the token
prisma.passwordResetToken.deleteMany({
where: {
token,
},
}),
// Update the user's password
prisma.user.update({
where: {
email: identifier,
},
data: {
passwordHash: newPasswordHash,
lockedAt: null, // Unlock the account after a successful password reset
...(!user.emailVerified && {
emailVerified: new Date(),
emailVerifiedBa: true,
}),
},
}),
// Dual write: keep Better Auth credential Account.password in sync
prisma.account.updateMany({
where: {
userId: user.id,
providerId: "credential",
},
data: {
password: newPasswordHash,
},
}),
]);
// Send the email to inform the user that their password has been reset
waitUntil(
sendEmail({
subject: "Your Dub account password has been reset",
to: identifier,
react: PasswordUpdated({
email: identifier,
verb: "reset",
}),
}),
);
return NextResponse.json({ ok: true });
} catch (error) {
return handleAndReturnErrorResponse(error);
}
}