47 lines
1.3 KiB
TypeScript
47 lines
1.3 KiB
TypeScript
import { resolver, generateToken, hash256 } from "blitz";
|
|
|
|
import db, { User } from "../../../db";
|
|
import { forgotPasswordMailer } from "../../../mailers/forgot-password-mailer";
|
|
import { ForgotPassword } from "../validations";
|
|
|
|
const RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS = 24;
|
|
|
|
export default resolver.pipe(resolver.zod(ForgotPassword), async ({ email }) => {
|
|
const user = await db.user.findFirst({ where: { email: email.toLowerCase() } });
|
|
|
|
// always wait the same amount of time so attackers can't tell the difference whether a user is found
|
|
await Promise.all([updatePassword(user), new Promise((resolve) => setTimeout(resolve, 750))]);
|
|
|
|
// return the same result whether a password reset email was sent or not
|
|
return;
|
|
});
|
|
|
|
async function updatePassword(user: User | null) {
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
const token = generateToken();
|
|
const hashedToken = hash256(token);
|
|
const expiresAt = new Date();
|
|
expiresAt.setHours(expiresAt.getHours() + RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS);
|
|
|
|
await db.token.deleteMany({ where: { type: "RESET_PASSWORD", userId: user.id } });
|
|
await db.token.create({
|
|
data: {
|
|
user: { connect: { id: user.id } },
|
|
type: "RESET_PASSWORD",
|
|
expiresAt,
|
|
hashedToken,
|
|
sentTo: user.email,
|
|
},
|
|
});
|
|
await (
|
|
await forgotPasswordMailer({
|
|
to: user.email,
|
|
token,
|
|
userName: user.fullName,
|
|
})
|
|
).send();
|
|
}
|