From 447bf0cb76c543c0e1c598dac22eb0da3d0fe7ea Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 12:23:52 +0000 Subject: [PATCH 01/24] Add password reset to prisma schema --- .../migration.sql | 15 +++++++++ packages/prisma/schema.prisma | 31 ++++++++++++------- 2 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 packages/prisma/migrations/20230605122017_password_reset/migration.sql diff --git a/packages/prisma/migrations/20230605122017_password_reset/migration.sql b/packages/prisma/migrations/20230605122017_password_reset/migration.sql new file mode 100644 index 000000000..782a60880 --- /dev/null +++ b/packages/prisma/migrations/20230605122017_password_reset/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "PasswordResetToken" ( + "id" SERIAL NOT NULL, + "token" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" INTEGER NOT NULL, + + CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "PasswordResetToken_token_key" ON "PasswordResetToken"("token"); + +-- AddForeignKey +ALTER TABLE "PasswordResetToken" ADD CONSTRAINT "PasswordResetToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 33fae736d..c702f244f 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -13,17 +13,18 @@ enum IdentityProvider { } model User { - id Int @id @default(autoincrement()) - name String? - email String @unique - emailVerified DateTime? - password String? - source String? - identityProvider IdentityProvider @default(DOCUMENSO) - accounts Account[] - sessions Session[] - Document Document[] - Subscription Subscription[] + id Int @id @default(autoincrement()) + name String? + email String @unique + emailVerified DateTime? + password String? + source String? + identityProvider IdentityProvider @default(DOCUMENSO) + accounts Account[] + sessions Session[] + Document Document[] + Subscription Subscription[] + PasswordResetToken PasswordResetToken[] } enum SubscriptionStatus { @@ -158,3 +159,11 @@ model Signature { Recipient Recipient @relation(fields: [recipientId], references: [id], onDelete: Cascade) Field Field @relation(fields: [fieldId], references: [id], onDelete: Restrict) } + +model PasswordResetToken { + id Int @id @default(autoincrement()) + token String @unique + createdAt DateTime @default(now()) + userId Int + User User @relation(fields: [userId], references: [id]) +} From 002b22b1a88d52823ebea215ae074a6d6b9b8ff8 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 12:50:11 +0000 Subject: [PATCH 02/24] Add forgot password page --- apps/web/components/forgot-password.tsx | 77 +++++++++++++++++++++++++ apps/web/components/login.tsx | 6 +- apps/web/pages/forgot-password.tsx | 34 +++++++++++ 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 apps/web/components/forgot-password.tsx create mode 100644 apps/web/pages/forgot-password.tsx diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx new file mode 100644 index 000000000..30a7eb036 --- /dev/null +++ b/apps/web/components/forgot-password.tsx @@ -0,0 +1,77 @@ +import Link from "next/link"; +import { Button } from "@documenso/ui"; +import Logo from "./logo"; +import { ArrowLeftIcon } from "@heroicons/react/24/outline"; +import { FormProvider, useForm } from "react-hook-form"; + +interface IResetPassword { + email: string; +} + +export default function ForgotPassword(props: any) { + const methods = useForm(); + const { register, formState, resetField } = methods; + + const onSubmit = async (values: IResetPassword) => { + resetField("email"); + + console.log(values); + }; + + return ( + <> +
+
+
+ +

+ Forgot Password? +

+

+ No worries, we'll send you reset instructions. +

+
+ +
+ +
+
+ + +
+
+ +
+ +
+
+ +
+ + Back to log in +
+ +
+
+
+
+
+ + ); +} diff --git a/apps/web/components/login.tsx b/apps/web/components/login.tsx index 4f086a8e1..f78513d87 100644 --- a/apps/web/components/login.tsx +++ b/apps/web/components/login.tsx @@ -111,9 +111,11 @@ export default function Login(props: any) {
- + Forgot your password? - +
diff --git a/apps/web/pages/forgot-password.tsx b/apps/web/pages/forgot-password.tsx new file mode 100644 index 000000000..ffcf6e470 --- /dev/null +++ b/apps/web/pages/forgot-password.tsx @@ -0,0 +1,34 @@ +import Head from "next/head"; +import { getUserFromToken } from "@documenso/lib/server"; +import ForgotPassword from "../components/forgot-password"; + +export default function ForgotPasswordPage(props: any) { + return ( + <> + + Reset Password | Documenso + + + + ); +} + +export async function getServerSideProps(context: any) { + const user = await getUserFromToken(context.req, context.res); + if (user) + return { + redirect: { + source: "/login", + destination: "/dashboard", + permanent: false, + }, + }; + + const ALLOW_SIGNUP = process.env.NEXT_PUBLIC_ALLOW_SIGNUP === "true"; + + return { + props: { + ALLOW_SIGNUP, + }, + }; +} From 8293b5019530489044a0e53ff513fb32ba161b15 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 13:05:25 +0000 Subject: [PATCH 03/24] Create reset password token for user --- apps/web/components/forgot-password.tsx | 16 ++++++++ apps/web/pages/api/auth/forgot-password.ts | 43 ++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 apps/web/pages/api/auth/forgot-password.ts diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx index 30a7eb036..503c7bbd1 100644 --- a/apps/web/components/forgot-password.tsx +++ b/apps/web/components/forgot-password.tsx @@ -3,6 +3,7 @@ import { Button } from "@documenso/ui"; import Logo from "./logo"; import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { FormProvider, useForm } from "react-hook-form"; +import { toast } from "react-hot-toast"; interface IResetPassword { email: string; @@ -13,6 +14,21 @@ export default function ForgotPassword(props: any) { const { register, formState, resetField } = methods; const onSubmit = async (values: IResetPassword) => { + await toast.promise( + fetch(`/api/auth/forgot-password`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(values), + }), + { + loading: "Sending...", + success: `Reset link sent. `, + error: "Could not send reset link :/", + } + ); + resetField("email"); console.log(values); diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts new file mode 100644 index 000000000..1a861362f --- /dev/null +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -0,0 +1,43 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { defaultHandler, defaultResponder } from "@documenso/lib/server"; +import prisma from "@documenso/prisma"; +import crypto from "crypto"; + +async function postHandler(req: NextApiRequest, res: NextApiResponse) { + const { email } = req.body; + const cleanEmail = email.toLowerCase(); + + if (!cleanEmail || !cleanEmail.includes("@")) { + res.status(422).json({ message: "Invalid email" }); + return; + } + + const user = await prisma.user.findFirst({ + where: { + email: cleanEmail, + }, + }); + + if (!user) { + return res.status(400).json({ message: "No user found with this email." }); + } + + const token = crypto.randomBytes(64).toString("hex"); + + const passwordResetToken = await prisma.passwordResetToken.create({ + data: { + token, + userId: user.id, + }, + }); + + console.log(passwordResetToken); + + // TODO: Send token to user via email + + res.status(201).end(); +} + +export default defaultHandler({ + POST: Promise.resolve({ default: defaultResponder(postHandler) }), +}); From 66b529a841f2dda36cfc110b5730cea06a7c183d Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 13:44:22 +0000 Subject: [PATCH 04/24] feat: send reset password email --- apps/web/pages/api/auth/forgot-password.ts | 5 +-- packages/lib/mail/baseTemplate.ts | 3 +- packages/lib/mail/index.ts | 2 + packages/lib/mail/resetPasswordTemplate.ts | 46 ++++++++++++++++++++++ packages/lib/mail/sendMail.ts | 1 - packages/lib/mail/sendResetPassword.ts | 17 ++++++++ 6 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 packages/lib/mail/resetPasswordTemplate.ts create mode 100644 packages/lib/mail/sendResetPassword.ts diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index 1a861362f..221be3d1c 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -1,4 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; +import { sendResetPassword } from "@documenso/lib/mail"; import { defaultHandler, defaultResponder } from "@documenso/lib/server"; import prisma from "@documenso/prisma"; import crypto from "crypto"; @@ -31,9 +32,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }, }); - console.log(passwordResetToken); - - // TODO: Send token to user via email + await sendResetPassword(user, passwordResetToken.token); res.status(201).end(); } diff --git a/packages/lib/mail/baseTemplate.ts b/packages/lib/mail/baseTemplate.ts index 6741a87b5..6e18c7114 100644 --- a/packages/lib/mail/baseTemplate.ts +++ b/packages/lib/mail/baseTemplate.ts @@ -1,10 +1,9 @@ import { NEXT_PUBLIC_WEBAPP_URL } from "../constants"; -import { Document as PrismaDocument } from "@prisma/client"; export const baseEmailTemplate = (message: string, content: string) => { const html = `
-
+
Documenso Logo ${message} ${content} diff --git a/packages/lib/mail/index.ts b/packages/lib/mail/index.ts index 6d49cdb6b..8388608c6 100644 --- a/packages/lib/mail/index.ts +++ b/packages/lib/mail/index.ts @@ -2,3 +2,5 @@ export { signingRequestTemplate } from "./signingRequestTemplate"; export { signingCompleteTemplate } from "./signingCompleteTemplate"; export { sendSigningRequest as sendSigningRequest } from "./sendSigningRequest"; export { sendSigningDoneMail } from "./sendSigningDoneMail"; +export { resetPasswordTemplate } from "./resetPasswordTemplate"; +export { sendResetPassword } from "./sendResetPassword"; diff --git a/packages/lib/mail/resetPasswordTemplate.ts b/packages/lib/mail/resetPasswordTemplate.ts new file mode 100644 index 000000000..b86b404fd --- /dev/null +++ b/packages/lib/mail/resetPasswordTemplate.ts @@ -0,0 +1,46 @@ +import { NEXT_PUBLIC_WEBAPP_URL } from "../constants"; + +export const resetPasswordTemplate = (ctaLink: string, ctaLabel: string) => { + const customContent = ` +

Forgot your password?

+

+ That's okay, it happens! Click the button below to reset your password. +

+ +

+ + ${ctaLabel} + +

+

+ Want to send you own signing links? Hosted Documenso is here!. +

`; + + const html = ` +
+
+ Documenso Logo + ${customContent} +
+
+ `; + + const footer = ` +
+
+ Need help? +
+ Contact us at hi@documenso.com +
+
+
+ Easy and beautiful document signing by Documenso. +
+
`; + + return html + footer; +}; + +export default resetPasswordTemplate; diff --git a/packages/lib/mail/sendMail.ts b/packages/lib/mail/sendMail.ts index 101981f12..fd7c6fb61 100644 --- a/packages/lib/mail/sendMail.ts +++ b/packages/lib/mail/sendMail.ts @@ -1,4 +1,3 @@ -import { ReadStream } from "fs"; import nodemailer from "nodemailer"; import nodemailerSendgrid from "nodemailer-sendgrid"; diff --git a/packages/lib/mail/sendResetPassword.ts b/packages/lib/mail/sendResetPassword.ts new file mode 100644 index 000000000..fd918b470 --- /dev/null +++ b/packages/lib/mail/sendResetPassword.ts @@ -0,0 +1,17 @@ +import { resetPasswordTemplate } from "@documenso/lib/mail"; +import { NEXT_PUBLIC_WEBAPP_URL } from "../constants"; +import { sendMail } from "./sendMail"; +import { User } from "@prisma/client"; + +export const sendResetPassword = async (user: User, token: string) => { + await sendMail( + user.email, + "Forgot password?", + resetPasswordTemplate( + `${NEXT_PUBLIC_WEBAPP_URL}/api/auth/reset/${token}`, + "Reset Your Password" + ) + ).catch((err) => { + throw err; + }); +}; From 8dc9c9d72dfcc8c9833885312554fa524612df34 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 14:17:45 +0000 Subject: [PATCH 05/24] Add reset password page --- apps/web/components/forgot-password.tsx | 6 +- apps/web/components/reset-password.tsx | 93 +++++++++++++++++++++++ apps/web/pages/api/auth/reset-password.ts | 42 ++++++++++ apps/web/pages/auth/reset.tsx | 34 +++++++++ apps/web/pages/forgot-password.tsx | 2 +- 5 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 apps/web/components/reset-password.tsx create mode 100644 apps/web/pages/api/auth/reset-password.ts create mode 100644 apps/web/pages/auth/reset.tsx diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx index 503c7bbd1..7a7ec0356 100644 --- a/apps/web/components/forgot-password.tsx +++ b/apps/web/components/forgot-password.tsx @@ -5,15 +5,15 @@ import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { FormProvider, useForm } from "react-hook-form"; import { toast } from "react-hot-toast"; -interface IResetPassword { +interface IForgotPassword { email: string; } export default function ForgotPassword(props: any) { - const methods = useForm(); + const methods = useForm(); const { register, formState, resetField } = methods; - const onSubmit = async (values: IResetPassword) => { + const onSubmit = async (values: IForgotPassword) => { await toast.promise( fetch(`/api/auth/forgot-password`, { method: "POST", diff --git a/apps/web/components/reset-password.tsx b/apps/web/components/reset-password.tsx new file mode 100644 index 000000000..88f3cb4d0 --- /dev/null +++ b/apps/web/components/reset-password.tsx @@ -0,0 +1,93 @@ +import Link from "next/link"; +import { Button } from "@documenso/ui"; +import Logo from "./logo"; +import { ArrowLeftIcon } from "@heroicons/react/24/outline"; +import { FormProvider, useForm, useWatch } from "react-hook-form"; +import { toast } from "react-hot-toast"; + +interface IResetPassword { + password: string; + confirmPassword: string; +} + +export default function ResetPassword(props: any) { + const methods = useForm(); + const { register, formState, watch } = methods; + const password = watch("password", ""); + + const onSubmit = async (values: IResetPassword) => { + console.log(values); + }; + + return ( + <> +
+
+
+ +

+ Reset Password +

+

Please chose your new password

+
+ +
+ +
+
+ + +
+
+ + value === password || "The passwords do not match", + })} + id="confirmPassword" + name="confirmPassword" + type="password" + required + className="focus:border-neon focus:ring-neon relative block w-full appearance-none rounded-none rounded-b-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:outline-none sm:text-sm" + placeholder="Confirm new password" + /> +
+
+ +
+ +
+
+ +
+ + Back to log in +
+ +
+
+
+
+
+ + ); +} diff --git a/apps/web/pages/api/auth/reset-password.ts b/apps/web/pages/api/auth/reset-password.ts new file mode 100644 index 000000000..221be3d1c --- /dev/null +++ b/apps/web/pages/api/auth/reset-password.ts @@ -0,0 +1,42 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { sendResetPassword } from "@documenso/lib/mail"; +import { defaultHandler, defaultResponder } from "@documenso/lib/server"; +import prisma from "@documenso/prisma"; +import crypto from "crypto"; + +async function postHandler(req: NextApiRequest, res: NextApiResponse) { + const { email } = req.body; + const cleanEmail = email.toLowerCase(); + + if (!cleanEmail || !cleanEmail.includes("@")) { + res.status(422).json({ message: "Invalid email" }); + return; + } + + const user = await prisma.user.findFirst({ + where: { + email: cleanEmail, + }, + }); + + if (!user) { + return res.status(400).json({ message: "No user found with this email." }); + } + + const token = crypto.randomBytes(64).toString("hex"); + + const passwordResetToken = await prisma.passwordResetToken.create({ + data: { + token, + userId: user.id, + }, + }); + + await sendResetPassword(user, passwordResetToken.token); + + res.status(201).end(); +} + +export default defaultHandler({ + POST: Promise.resolve({ default: defaultResponder(postHandler) }), +}); diff --git a/apps/web/pages/auth/reset.tsx b/apps/web/pages/auth/reset.tsx new file mode 100644 index 000000000..efbb4372b --- /dev/null +++ b/apps/web/pages/auth/reset.tsx @@ -0,0 +1,34 @@ +import Head from "next/head"; +import { getUserFromToken } from "@documenso/lib/server"; +import ResetPassword from "../../components/reset-password"; + +export default function ResetPasswordPage(props: any) { + return ( + <> + + Reset Password | Documenso + + + + ); +} + +export async function getServerSideProps(context: any) { + const user = await getUserFromToken(context.req, context.res); + if (user) + return { + redirect: { + source: "/login", + destination: "/dashboard", + permanent: false, + }, + }; + + const ALLOW_SIGNUP = process.env.NEXT_PUBLIC_ALLOW_SIGNUP === "true"; + + return { + props: { + ALLOW_SIGNUP, + }, + }; +} diff --git a/apps/web/pages/forgot-password.tsx b/apps/web/pages/forgot-password.tsx index ffcf6e470..7ecf29881 100644 --- a/apps/web/pages/forgot-password.tsx +++ b/apps/web/pages/forgot-password.tsx @@ -6,7 +6,7 @@ export default function ForgotPasswordPage(props: any) { return ( <> - Reset Password | Documenso + Forgot Password | Documenso From 6e2b05f8354eecc2a679240a6fd85d838d67c205 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 14:36:20 +0000 Subject: [PATCH 06/24] Change password in database to new reset password --- apps/web/components/reset-password.tsx | 37 ++++++++++++++ apps/web/pages/api/auth/forgot-password.ts | 3 +- apps/web/pages/api/auth/reset-password.ts | 49 ++++++++++++------- .../auth/{reset.tsx => reset/[token].tsx} | 2 +- packages/lib/mail/sendResetPassword.ts | 5 +- 5 files changed, 71 insertions(+), 25 deletions(-) rename apps/web/pages/auth/{reset.tsx => reset/[token].tsx} (91%) diff --git a/apps/web/components/reset-password.tsx b/apps/web/components/reset-password.tsx index 88f3cb4d0..27e214574 100644 --- a/apps/web/components/reset-password.tsx +++ b/apps/web/components/reset-password.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { useRouter } from "next/router"; import { Button } from "@documenso/ui"; import Logo from "./logo"; import { ArrowLeftIcon } from "@heroicons/react/24/outline"; @@ -11,14 +12,50 @@ interface IResetPassword { } export default function ResetPassword(props: any) { + const router = useRouter(); + const { token } = router.query; + const methods = useForm(); const { register, formState, watch } = methods; const password = watch("password", ""); const onSubmit = async (values: IResetPassword) => { + await toast.promise( + fetch(`/api/auth/reset-password`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ password: values.password, token }), + }), + { + loading: "Resetting...", + success: `Reset password successful`, + error: "Could not reset password :/", + } + ); + console.log(values); }; + if (!token) { + return ( +
+
+
+ +

+ Reset Password +

+

+ The token you provided is invalid. Please try again. +

+
+
+
+ ); + } + return ( <>
diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index 221be3d1c..ef2b218d7 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -24,7 +24,6 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { } const token = crypto.randomBytes(64).toString("hex"); - const passwordResetToken = await prisma.passwordResetToken.create({ data: { token, @@ -34,7 +33,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { await sendResetPassword(user, passwordResetToken.token); - res.status(201).end(); + res.status(200).json({ message: "Password reset email sent." }); } export default defaultHandler({ diff --git a/apps/web/pages/api/auth/reset-password.ts b/apps/web/pages/api/auth/reset-password.ts index 221be3d1c..ad6ef7a49 100644 --- a/apps/web/pages/api/auth/reset-password.ts +++ b/apps/web/pages/api/auth/reset-password.ts @@ -1,40 +1,53 @@ import { NextApiRequest, NextApiResponse } from "next"; +import { hashPassword } from "@documenso/lib/auth"; import { sendResetPassword } from "@documenso/lib/mail"; import { defaultHandler, defaultResponder } from "@documenso/lib/server"; import prisma from "@documenso/prisma"; -import crypto from "crypto"; async function postHandler(req: NextApiRequest, res: NextApiResponse) { - const { email } = req.body; - const cleanEmail = email.toLowerCase(); + const { token, password } = req.body; - if (!cleanEmail || !cleanEmail.includes("@")) { - res.status(422).json({ message: "Invalid email" }); + if (!token) { + res.status(422).json({ message: "Invalid token" }); return; } - const user = await prisma.user.findFirst({ + const foundToken = await prisma.passwordResetToken.findUnique({ where: { - email: cleanEmail, + token, + }, + include: { + User: true, }, }); - if (!user) { - return res.status(400).json({ message: "No user found with this email." }); + if (!foundToken) { + return res.status(400).json({ message: "Invalid token." }); } - const token = crypto.randomBytes(64).toString("hex"); + const hashedPassword = await hashPassword(password); - const passwordResetToken = await prisma.passwordResetToken.create({ - data: { - token, - userId: user.id, - }, - }); + const transaction = await prisma.$transaction([ + prisma.user.update({ + where: { + id: foundToken.userId, + }, + data: { + password: hashedPassword, + }, + }), + prisma.passwordResetToken.delete({ + where: { + token, + }, + }), + ]); - await sendResetPassword(user, passwordResetToken.token); + if (!transaction) { + return res.status(500).json({ message: "Error resetting password." }); + } - res.status(201).end(); + res.status(200).json({ message: "Password reset successful." }); } export default defaultHandler({ diff --git a/apps/web/pages/auth/reset.tsx b/apps/web/pages/auth/reset/[token].tsx similarity index 91% rename from apps/web/pages/auth/reset.tsx rename to apps/web/pages/auth/reset/[token].tsx index efbb4372b..fffe7c3c7 100644 --- a/apps/web/pages/auth/reset.tsx +++ b/apps/web/pages/auth/reset/[token].tsx @@ -1,6 +1,6 @@ import Head from "next/head"; import { getUserFromToken } from "@documenso/lib/server"; -import ResetPassword from "../../components/reset-password"; +import ResetPassword from "../../../components/reset-password"; export default function ResetPasswordPage(props: any) { return ( diff --git a/packages/lib/mail/sendResetPassword.ts b/packages/lib/mail/sendResetPassword.ts index fd918b470..32e098a4c 100644 --- a/packages/lib/mail/sendResetPassword.ts +++ b/packages/lib/mail/sendResetPassword.ts @@ -7,10 +7,7 @@ export const sendResetPassword = async (user: User, token: string) => { await sendMail( user.email, "Forgot password?", - resetPasswordTemplate( - `${NEXT_PUBLIC_WEBAPP_URL}/api/auth/reset/${token}`, - "Reset Your Password" - ) + resetPasswordTemplate(`${NEXT_PUBLIC_WEBAPP_URL}/auth/reset/${token}`, "Reset Your Password") ).catch((err) => { throw err; }); From 7c30ee0c3e1af084a877774d3429a0e27143bd97 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 14:47:10 +0000 Subject: [PATCH 07/24] Redirect to /login on password reset --- apps/web/components/reset-password.tsx | 28 ++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/apps/web/components/reset-password.tsx b/apps/web/components/reset-password.tsx index 27e214574..d4745ee24 100644 --- a/apps/web/components/reset-password.tsx +++ b/apps/web/components/reset-password.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import { Button } from "@documenso/ui"; @@ -19,8 +20,10 @@ export default function ResetPassword(props: any) { const { register, formState, watch } = methods; const password = watch("password", ""); + const [resetSuccessful, setResetSuccessful] = useState(false); + const onSubmit = async (values: IResetPassword) => { - await toast.promise( + const response = await toast.promise( fetch(`/api/auth/reset-password`, { method: "POST", headers: { @@ -35,7 +38,12 @@ export default function ResetPassword(props: any) { } ); - console.log(values); + if (response.ok) { + setResetSuccessful(true); + setTimeout(() => { + router.push("/login"); + }, 2000); + } }; if (!token) { @@ -56,6 +64,22 @@ export default function ResetPassword(props: any) { ); } + if (resetSuccessful) { + return ( +
+
+
+ +

+ Reset Password +

+

Your password has been reset.

+
+
+
+ ); + } + return ( <>
From c47e01b2b8d3b3236c456f07bfedef12829a9293 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 14:59:50 +0000 Subject: [PATCH 08/24] Display sucessful password reset request --- apps/web/components/forgot-password.tsx | 39 ++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx index 7a7ec0356..bb55bfb66 100644 --- a/apps/web/components/forgot-password.tsx +++ b/apps/web/components/forgot-password.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import Link from "next/link"; import { Button } from "@documenso/ui"; import Logo from "./logo"; @@ -9,12 +10,14 @@ interface IForgotPassword { email: string; } -export default function ForgotPassword(props: any) { +export default function ForgotPassword() { const methods = useForm(); const { register, formState, resetField } = methods; + const [resetSuccessful, setResetSuccessful] = useState(false); + const onSubmit = async (values: IForgotPassword) => { - await toast.promise( + const response = await toast.promise( fetch(`/api/auth/forgot-password`, { method: "POST", headers: { @@ -29,11 +32,39 @@ export default function ForgotPassword(props: any) { } ); - resetField("email"); + if (response.ok) { + setResetSuccessful(true); + } - console.log(values); + resetField("email"); }; + if (resetSuccessful) { + return ( +
+
+
+ +

+ Reset Password +

+

+ Please check your email for reset instructions. +

+
+
+ +
+ + Back to log in +
+ +
+
+
+ ); + } + return ( <>
From 5d2349086d3a90e47ba0f50dd9c289db7e3b225e Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 15:33:27 +0000 Subject: [PATCH 09/24] Send email on password reset complete --- apps/web/pages/api/auth/forgot-password.ts | 2 +- apps/web/pages/api/auth/reset-password.ts | 4 +- apps/web/pages/auth/reset.tsx | 0 packages/lib/mail/index.ts | 2 + .../lib/mail/resetPasswordSuccessTemplate.ts | 51 +++++++++++++++++++ .../lib/mail/sendResetPasswordSuccessMail.ts | 11 ++++ packages/lib/mail/signingCompleteTemplate.ts | 1 - 7 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 apps/web/pages/auth/reset.tsx create mode 100644 packages/lib/mail/resetPasswordSuccessTemplate.ts create mode 100644 packages/lib/mail/sendResetPasswordSuccessMail.ts diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index ef2b218d7..e30eeed36 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { sendResetPassword } from "@documenso/lib/mail"; +import { sendResetPassword, sendResetPasswordSuccessMail } from "@documenso/lib/mail"; import { defaultHandler, defaultResponder } from "@documenso/lib/server"; import prisma from "@documenso/prisma"; import crypto from "crypto"; diff --git a/apps/web/pages/api/auth/reset-password.ts b/apps/web/pages/api/auth/reset-password.ts index ad6ef7a49..4bf2e5306 100644 --- a/apps/web/pages/api/auth/reset-password.ts +++ b/apps/web/pages/api/auth/reset-password.ts @@ -1,6 +1,6 @@ import { NextApiRequest, NextApiResponse } from "next"; import { hashPassword } from "@documenso/lib/auth"; -import { sendResetPassword } from "@documenso/lib/mail"; +import { sendResetPasswordSuccessMail } from "@documenso/lib/mail"; import { defaultHandler, defaultResponder } from "@documenso/lib/server"; import prisma from "@documenso/prisma"; @@ -47,6 +47,8 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { return res.status(500).json({ message: "Error resetting password." }); } + await sendResetPasswordSuccessMail(foundToken.User); + res.status(200).json({ message: "Password reset successful." }); } diff --git a/apps/web/pages/auth/reset.tsx b/apps/web/pages/auth/reset.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/lib/mail/index.ts b/packages/lib/mail/index.ts index 8388608c6..e4d66dc44 100644 --- a/packages/lib/mail/index.ts +++ b/packages/lib/mail/index.ts @@ -4,3 +4,5 @@ export { sendSigningRequest as sendSigningRequest } from "./sendSigningRequest"; export { sendSigningDoneMail } from "./sendSigningDoneMail"; export { resetPasswordTemplate } from "./resetPasswordTemplate"; export { sendResetPassword } from "./sendResetPassword"; +export { resetPasswordSuccessTemplate } from "./resetPasswordSuccessTemplate"; +export { sendResetPasswordSuccessMail } from "./sendResetPasswordSuccessMail"; diff --git a/packages/lib/mail/resetPasswordSuccessTemplate.ts b/packages/lib/mail/resetPasswordSuccessTemplate.ts new file mode 100644 index 000000000..44afcc119 --- /dev/null +++ b/packages/lib/mail/resetPasswordSuccessTemplate.ts @@ -0,0 +1,51 @@ +import { NEXT_PUBLIC_WEBAPP_URL } from "../constants"; +import { User } from "@prisma/client"; + +export const resetPasswordSuccessTemplate = (user: User) => { + return ` +
+
+ Documenso Logo + +

Password updated!

+ +

+ Hi ${user.name ? user.name : user.email}, +

+ +

+ We've changed your password as you asked. You can now sign in with your new password. +

+ +

+ If you did not ask to change your password we are here to help you secure your account, just contact us. +

+ +

+

+ The Documenso Team +

+

+ +

+ Want to send you own signing links? + Hosted Documenso is here!. +

+
+
+
+
+ Need help? +
+ Contact us at hi@documenso.com +
+
+
+ Easy and beautiful document signing by Documenso. +
+
+`; +}; +export default resetPasswordSuccessTemplate; diff --git a/packages/lib/mail/sendResetPasswordSuccessMail.ts b/packages/lib/mail/sendResetPasswordSuccessMail.ts new file mode 100644 index 000000000..6877700fb --- /dev/null +++ b/packages/lib/mail/sendResetPasswordSuccessMail.ts @@ -0,0 +1,11 @@ +import resetPasswordSuccessTemplate from "./resetPasswordSuccessTemplate"; +import { sendMail } from "./sendMail"; +import { User } from "@prisma/client"; + +export const sendResetPasswordSuccessMail = async (user: User) => { + await sendMail(user.email, "Password Reset Success!", resetPasswordSuccessTemplate(user)).catch( + (err) => { + throw err; + } + ); +}; diff --git a/packages/lib/mail/signingCompleteTemplate.ts b/packages/lib/mail/signingCompleteTemplate.ts index 212e1f8ea..e32162906 100644 --- a/packages/lib/mail/signingCompleteTemplate.ts +++ b/packages/lib/mail/signingCompleteTemplate.ts @@ -1,6 +1,5 @@ import { NEXT_PUBLIC_WEBAPP_URL } from "../constants"; import { baseEmailTemplate } from "./baseTemplate"; -import { Document as PrismaDocument } from "@prisma/client"; export const signingCompleteTemplate = (message: string) => { const customContent = ` From e9cee23c15d509d2562748b1b40b7db445004a46 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 15:52:00 +0000 Subject: [PATCH 10/24] Error handling for invalid users --- apps/web/components/forgot-password.tsx | 19 +++++++++++++++++++ apps/web/pages/api/auth/forgot-password.ts | 21 ++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx index bb55bfb66..40d0af98a 100644 --- a/apps/web/components/forgot-password.tsx +++ b/apps/web/components/forgot-password.tsx @@ -29,9 +29,28 @@ export default function ForgotPassword() { loading: "Sending...", success: `Reset link sent. `, error: "Could not send reset link :/", + }, + { + style: { + minWidth: "200px", + }, } ); + if (!response.ok) { + toast.dismiss(); + + if (response.status == 400 || response.status == 404) { + toast.error("Email address not found."); + } + + if (response.status == 500) { + toast.error("Something went wrong."); + } + + return; + } + if (response.ok) { setResetSuccessful(true); } diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index e30eeed36..058a2d3b6 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -20,16 +20,23 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }); if (!user) { - return res.status(400).json({ message: "No user found with this email." }); + return res.status(404).json({ message: "No user found with this email." }); } const token = crypto.randomBytes(64).toString("hex"); - const passwordResetToken = await prisma.passwordResetToken.create({ - data: { - token, - userId: user.id, - }, - }); + + let passwordResetToken; + + try { + passwordResetToken = await prisma.passwordResetToken.create({ + data: { + token, + userId: user.id, + }, + }); + } catch (error) { + return res.status(500).json({ message: "Error saving token." }); + } await sendResetPassword(user, passwordResetToken.token); From 4136811e32995eab97a5fb785f2190dfad9057c2 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 16:01:01 +0000 Subject: [PATCH 11/24] Avoid consecutive password reset requests --- apps/web/components/forgot-password.tsx | 11 +++++------ apps/web/components/reset-password.tsx | 2 +- apps/web/pages/api/auth/forgot-password.ts | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx index 40d0af98a..2f4250c05 100644 --- a/apps/web/components/forgot-password.tsx +++ b/apps/web/components/forgot-password.tsx @@ -29,21 +29,20 @@ export default function ForgotPassword() { loading: "Sending...", success: `Reset link sent. `, error: "Could not send reset link :/", - }, - { - style: { - minWidth: "200px", - }, } ); if (!response.ok) { toast.dismiss(); - if (response.status == 400 || response.status == 404) { + if (response.status == 404) { toast.error("Email address not found."); } + if (response.status == 400) { + toast.error("Password reset requested."); + } + if (response.status == 500) { toast.error("Something went wrong."); } diff --git a/apps/web/components/reset-password.tsx b/apps/web/components/reset-password.tsx index d4745ee24..f55a7628c 100644 --- a/apps/web/components/reset-password.tsx +++ b/apps/web/components/reset-password.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { Button } from "@documenso/ui"; import Logo from "./logo"; import { ArrowLeftIcon } from "@heroicons/react/24/outline"; -import { FormProvider, useForm, useWatch } from "react-hook-form"; +import { FormProvider, useForm } from "react-hook-form"; import { toast } from "react-hot-toast"; interface IResetPassword { diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index 058a2d3b6..42d904fb7 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -23,10 +23,24 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { return res.status(404).json({ message: "No user found with this email." }); } + const existingToken = await prisma.passwordResetToken.findFirst({ + where: { + userId: user.id, + createdAt: { + gte: new Date(Date.now() - 1000 * 60 * 60), + }, + }, + }); + + if (existingToken) { + return res + .status(400) + .json({ message: "A password reset has already been requested. Please check your email." }); + } + const token = crypto.randomBytes(64).toString("hex"); let passwordResetToken; - try { passwordResetToken = await prisma.passwordResetToken.create({ data: { From 2b9a2ff250626a1ddd76a77a132d483ed0899328 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 16:36:16 +0000 Subject: [PATCH 12/24] Avoid user from setting the same old password --- apps/web/components/reset-password.tsx | 18 ++++++++++++++++++ apps/web/pages/api/auth/forgot-password.ts | 2 +- apps/web/pages/api/auth/reset-password.ts | 12 ++++++++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/web/components/reset-password.tsx b/apps/web/components/reset-password.tsx index f55a7628c..7cc38ce42 100644 --- a/apps/web/components/reset-password.tsx +++ b/apps/web/components/reset-password.tsx @@ -38,6 +38,24 @@ export default function ResetPassword(props: any) { } ); + if (!response.ok) { + toast.dismiss(); + + if (response.status == 404) { + toast.error("Invalid Token"); + } + + if (response.status == 400) { + toast.error("New password must be different"); + } + + if (response.status == 500) { + toast.error("Something went wrong."); + } + + return; + } + if (response.ok) { setResetSuccessful(true); setTimeout(() => { diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index 42d904fb7..7edab5aca 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { sendResetPassword, sendResetPasswordSuccessMail } from "@documenso/lib/mail"; +import { sendResetPassword } from "@documenso/lib/mail"; import { defaultHandler, defaultResponder } from "@documenso/lib/server"; import prisma from "@documenso/prisma"; import crypto from "crypto"; diff --git a/apps/web/pages/api/auth/reset-password.ts b/apps/web/pages/api/auth/reset-password.ts index 4bf2e5306..c98de4809 100644 --- a/apps/web/pages/api/auth/reset-password.ts +++ b/apps/web/pages/api/auth/reset-password.ts @@ -1,5 +1,5 @@ import { NextApiRequest, NextApiResponse } from "next"; -import { hashPassword } from "@documenso/lib/auth"; +import { hashPassword, verifyPassword } from "@documenso/lib/auth"; import { sendResetPasswordSuccessMail } from "@documenso/lib/mail"; import { defaultHandler, defaultResponder } from "@documenso/lib/server"; import prisma from "@documenso/prisma"; @@ -22,7 +22,15 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }); if (!foundToken) { - return res.status(400).json({ message: "Invalid token." }); + return res.status(404).json({ message: "Invalid token." }); + } + + const isSamePassword = await verifyPassword(password, foundToken.User.password!); + + if (isSamePassword) { + return res + .status(400) + .json({ message: "New password must be different from the current password." }); } const hashedPassword = await hashPassword(password); From 3a0648c85d5e51a291393bfda9d63a8ea6d7fa95 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 16:54:12 +0000 Subject: [PATCH 13/24] Expire token after 1 hour --- apps/web/components/forgot-password.tsx | 2 +- apps/web/components/reset-password.tsx | 16 ++-------------- apps/web/pages/api/auth/forgot-password.ts | 11 ++++++----- apps/web/pages/api/auth/reset-password.ts | 10 +++++++--- .../migration.sql | 8 ++++++++ packages/prisma/schema.prisma | 1 + 6 files changed, 25 insertions(+), 23 deletions(-) create mode 100644 packages/prisma/migrations/20230605164015_expire_password_reset_token/migration.sql diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx index 2f4250c05..cd7e4a99c 100644 --- a/apps/web/components/forgot-password.tsx +++ b/apps/web/components/forgot-password.tsx @@ -27,7 +27,7 @@ export default function ForgotPassword() { }), { loading: "Sending...", - success: `Reset link sent. `, + success: "Reset link sent.", error: "Could not send reset link :/", } ); diff --git a/apps/web/components/reset-password.tsx b/apps/web/components/reset-password.tsx index 7cc38ce42..3bf4dcbcf 100644 --- a/apps/web/components/reset-password.tsx +++ b/apps/web/components/reset-password.tsx @@ -40,20 +40,8 @@ export default function ResetPassword(props: any) { if (!response.ok) { toast.dismiss(); - - if (response.status == 404) { - toast.error("Invalid Token"); - } - - if (response.status == 400) { - toast.error("New password must be different"); - } - - if (response.status == 500) { - toast.error("Something went wrong."); - } - - return; + const error = await response.json(); + toast.error(error.message); } if (response.ok) { diff --git a/apps/web/pages/api/auth/forgot-password.ts b/apps/web/pages/api/auth/forgot-password.ts index 7edab5aca..1c77c6df6 100644 --- a/apps/web/pages/api/auth/forgot-password.ts +++ b/apps/web/pages/api/auth/forgot-password.ts @@ -20,7 +20,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }); if (!user) { - return res.status(404).json({ message: "No user found with this email." }); + return res.status(404).json({ message: "No user found with this email" }); } const existingToken = await prisma.passwordResetToken.findFirst({ @@ -33,23 +33,24 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { }); if (existingToken) { - return res - .status(400) - .json({ message: "A password reset has already been requested. Please check your email." }); + return res.status(400).json({ message: "Password reset requested." }); } const token = crypto.randomBytes(64).toString("hex"); + const expiry = new Date(); + expiry.setHours(expiry.getHours() + 1); // Set expiry to one hour from now let passwordResetToken; try { passwordResetToken = await prisma.passwordResetToken.create({ data: { token, + expiry, userId: user.id, }, }); } catch (error) { - return res.status(500).json({ message: "Error saving token." }); + return res.status(500).json({ message: "Something went wrong" }); } await sendResetPassword(user, passwordResetToken.token); diff --git a/apps/web/pages/api/auth/reset-password.ts b/apps/web/pages/api/auth/reset-password.ts index c98de4809..89ed6108c 100644 --- a/apps/web/pages/api/auth/reset-password.ts +++ b/apps/web/pages/api/auth/reset-password.ts @@ -25,12 +25,16 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { return res.status(404).json({ message: "Invalid token." }); } + const now = new Date(); + + if (now > foundToken.expiry) { + return res.status(400).json({ message: "Token has expired" }); + } + const isSamePassword = await verifyPassword(password, foundToken.User.password!); if (isSamePassword) { - return res - .status(400) - .json({ message: "New password must be different from the current password." }); + return res.status(400).json({ message: "New password must be different" }); } const hashedPassword = await hashPassword(password); diff --git a/packages/prisma/migrations/20230605164015_expire_password_reset_token/migration.sql b/packages/prisma/migrations/20230605164015_expire_password_reset_token/migration.sql new file mode 100644 index 000000000..a3a70e575 --- /dev/null +++ b/packages/prisma/migrations/20230605164015_expire_password_reset_token/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `expiry` to the `PasswordResetToken` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "PasswordResetToken" ADD COLUMN "expiry" TIMESTAMP(3) NOT NULL; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index c702f244f..fc8463c9b 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -164,6 +164,7 @@ model PasswordResetToken { id Int @id @default(autoincrement()) token String @unique createdAt DateTime @default(now()) + expiry DateTime userId Int User User @relation(fields: [userId], references: [id]) } From 79bd410687c286b95b4b970df2832bd498431a5d Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Mon, 5 Jun 2023 17:15:41 +0000 Subject: [PATCH 14/24] Remove tokens on successful password reset --- apps/web/pages/api/auth/reset-password.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/pages/api/auth/reset-password.ts b/apps/web/pages/api/auth/reset-password.ts index 89ed6108c..bc42e2339 100644 --- a/apps/web/pages/api/auth/reset-password.ts +++ b/apps/web/pages/api/auth/reset-password.ts @@ -48,9 +48,9 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) { password: hashedPassword, }, }), - prisma.passwordResetToken.delete({ + prisma.passwordResetToken.deleteMany({ where: { - token, + userId: foundToken.userId, }, }), ]); From 7184c47ac4ba50638b3e9f18ad58f22400dc1689 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 7 Jun 2023 10:10:56 +0000 Subject: [PATCH 15/24] Rename component interfaces --- apps/web/components/forgot-password.tsx | 6 +++--- apps/web/components/reset-password.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx index cd7e4a99c..52dd96ce3 100644 --- a/apps/web/components/forgot-password.tsx +++ b/apps/web/components/forgot-password.tsx @@ -6,17 +6,17 @@ import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { FormProvider, useForm } from "react-hook-form"; import { toast } from "react-hot-toast"; -interface IForgotPassword { +interface ForgotPasswordForm { email: string; } export default function ForgotPassword() { - const methods = useForm(); + const methods = useForm(); const { register, formState, resetField } = methods; const [resetSuccessful, setResetSuccessful] = useState(false); - const onSubmit = async (values: IForgotPassword) => { + const onSubmit = async (values: ForgotPasswordForm) => { const response = await toast.promise( fetch(`/api/auth/forgot-password`, { method: "POST", diff --git a/apps/web/components/reset-password.tsx b/apps/web/components/reset-password.tsx index 3bf4dcbcf..16276ed4d 100644 --- a/apps/web/components/reset-password.tsx +++ b/apps/web/components/reset-password.tsx @@ -7,7 +7,7 @@ import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { FormProvider, useForm } from "react-hook-form"; import { toast } from "react-hot-toast"; -interface IResetPassword { +interface ForgotPasswordForm { password: string; confirmPassword: string; } @@ -16,13 +16,13 @@ export default function ResetPassword(props: any) { const router = useRouter(); const { token } = router.query; - const methods = useForm(); + const methods = useForm(); const { register, formState, watch } = methods; const password = watch("password", ""); const [resetSuccessful, setResetSuccessful] = useState(false); - const onSubmit = async (values: IResetPassword) => { + const onSubmit = async (values: ForgotPasswordForm) => { const response = await toast.promise( fetch(`/api/auth/reset-password`, { method: "POST", From f08836216ecb107ccc252eaafcb2ba6fb6ffc1f2 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Wed, 7 Jun 2023 10:12:05 +0000 Subject: [PATCH 16/24] Remove unused input fields --- apps/web/components/forgot-password.tsx | 1 - apps/web/components/reset-password.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/apps/web/components/forgot-password.tsx b/apps/web/components/forgot-password.tsx index 52dd96ce3..a0eee29f4 100644 --- a/apps/web/components/forgot-password.tsx +++ b/apps/web/components/forgot-password.tsx @@ -98,7 +98,6 @@ export default function ForgotPassword() {
-
-