diff --git a/app/auth/mutations/forgot-password.ts b/app/auth/mutations/forgot-password.ts index e67d5e9..2c807c8 100644 --- a/app/auth/mutations/forgot-password.ts +++ b/app/auth/mutations/forgot-password.ts @@ -1,42 +1,40 @@ import { resolver, generateToken, hash256 } from "blitz"; -import db from "../../../db"; +import db, { User } from "../../../db"; import { forgotPasswordMailer } from "../../../mailers/forgot-password-mailer"; import { ForgotPassword } from "../validations"; const RESET_PASSWORD_TOKEN_EXPIRATION_IN_HOURS = 4; export default resolver.pipe(resolver.zod(ForgotPassword), async ({ email }) => { - // 1. Get the user const user = await db.user.findFirst({ where: { email: email.toLowerCase() } }); - // 2. Generate the token and expiration date. + // 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); - // 3. If user with this email was found - if (user) { - // 4. Delete any existing password reset tokens - await db.token.deleteMany({ where: { type: "RESET_PASSWORD", userId: user.id } }); - // 5. Save this new token in the database. - await db.token.create({ - data: { - user: { connect: { id: user.id } }, - type: "RESET_PASSWORD", - expiresAt, - hashedToken, - sentTo: user.email, - }, - }); - // 6. Send the email - await forgotPasswordMailer({ to: user.email, token }).send(); - } else { - // 7. If no user found wait the same time so attackers can't tell the difference - await new Promise((resolve) => setTimeout(resolve, 750)); - } - - // 8. Return the same result whether a password reset email was sent or not - return; -}); + 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(); +} diff --git a/app/auth/pages/reset-password.tsx b/app/auth/pages/reset-password.tsx index d5cb9d6..390d323 100644 --- a/app/auth/pages/reset-password.tsx +++ b/app/auth/pages/reset-password.tsx @@ -2,57 +2,55 @@ import type { BlitzPage, GetServerSideProps } from "blitz"; import { useRouterQuery, Link, useMutation, Routes } from "blitz"; import BaseLayout from "../../core/layouts/base-layout"; -import { LabeledTextField } from "../../core/components/labeled-text-field"; -import { Form, FORM_ERROR } from "../../core/components/form"; +import { AuthForm as Form, FORM_ERROR } from "../components/auth-form"; +import { LabeledTextField } from "../components/labeled-text-field"; import { ResetPassword } from "../validations"; import resetPassword from "../../auth/mutations/reset-password"; const ResetPasswordPage: BlitzPage = () => { const query = useRouterQuery(); - console.log("client query", query); const [resetPasswordMutation, { isSuccess }] = useMutation(resetPassword); return ( -