From 9d30930f96d70c502a4159f02ea8479c39a85a4c Mon Sep 17 00:00:00 2001 From: m5r Date: Sat, 25 Sep 2021 22:05:39 +0800 Subject: [PATCH] style reset password form --- app/auth/mutations/forgot-password.ts | 52 +++++++++---------- app/auth/pages/reset-password.tsx | 74 +++++++++++++-------------- 2 files changed, 61 insertions(+), 65 deletions(-) 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 ( -
-

Set a New Password

- +
{ + try { + await resetPasswordMutation(values); + } catch (error: any) { + if (error.name === "ResetPasswordError") { + return { + [FORM_ERROR]: error.message, + }; + } else { + return { + [FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.", + }; + } + } + }} + > {isSuccess ? ( -
-

Password Reset Successfully

-

- Go to the homepage -

-
+

+ Go to the homepage +

) : ( - { - try { - await resetPasswordMutation(values); - } catch (error: any) { - if (error.name === "ResetPasswordError") { - return { - [FORM_ERROR]: error.message, - }; - } else { - return { - [FORM_ERROR]: "Sorry, we had an unexpected error. Please try again.", - }; - } - } - }} - > + <> - + )} -
+ ); };