shellphone.app/app/auth/mutations/forgot-password.ts

41 lines
1.3 KiB
TypeScript
Raw Normal View History

import { resolver, generateToken, hash256 } from "blitz";
2021-07-31 14:33:18 +00:00
2021-09-25 14:05:39 +00:00
import db, { User } from "../../../db";
import { forgotPasswordMailer } from "../../../mailers/forgot-password-mailer";
import { ForgotPassword } from "../validations";
2021-07-31 14:33:18 +00:00
const RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS = 4;
2021-07-31 14:33:18 +00:00
export default resolver.pipe(resolver.zod(ForgotPassword), async ({ email }) => {
const user = await db.user.findFirst({ where: { email: email.toLowerCase() } });
2021-07-31 14:33:18 +00:00
2021-09-25 14:05:39 +00:00
// 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);
2021-07-31 14:33:18 +00:00
2021-09-25 14:05:39 +00:00
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 forgotPasswordMailer({ to: user.email, token }).send();
}