style reset password form

This commit is contained in:
m5r 2021-09-25 22:05:39 +08:00
parent d8acd6c37c
commit 9d30930f96
2 changed files with 61 additions and 65 deletions

View File

@ -1,26 +1,32 @@
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 } },
@ -30,13 +36,5 @@ export default resolver.pipe(resolver.zod(ForgotPassword), async ({ email }) =>
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;
});

View File

@ -2,30 +2,22 @@ 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 (
<div>
<h1>Set a New Password</h1>
{isSuccess ? (
<div>
<h2>Password Reset Successfully</h2>
<p>
Go to the <Link href={Routes.LandingPage()}>homepage</Link>
</p>
</div>
) : (
<Form
submitText="Reset Password"
texts={{
title: isSuccess ? "Password reset successfully" : "Set a new password",
subtitle: "",
submit: "Reset password",
}}
schema={ResetPassword}
initialValues={{
password: "",
@ -48,11 +40,17 @@ const ResetPasswordPage: BlitzPage = () => {
}
}}
>
{isSuccess ? (
<p>
Go to the <Link href={Routes.LandingPage()}>homepage</Link>
</p>
) : (
<>
<LabeledTextField name="password" label="New Password" type="password" />
<LabeledTextField name="passwordConfirmation" label="Confirm New Password" type="password" />
</Form>
</>
)}
</div>
</Form>
);
};