From 1f3fdd5d0a9718e4d665b02f2664c4c84668d936 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 6 Jun 2023 21:36:13 +0530 Subject: [PATCH] feat: reset password page for self-hosted added (#1221) * feat: reset password page for self-hosted added * chore: change reset password workflow * dev: update email template for reset password * chore: updated restricted workspace slugs list --------- Co-authored-by: pablohashescobar --- .../emails/auth/forgot_password.html | 10 +- .../account/email-password-form.tsx | 116 ++++++++------- .../account/email-reset-password-form.tsx | 93 ++++++++++++ apps/app/components/account/index.ts | 3 +- .../workspace/create-workspace-form.tsx | 1 + apps/app/pages/reset-password.tsx | 132 ++++++++++++++++++ apps/app/services/user.service.ts | 23 +++ 7 files changed, 319 insertions(+), 59 deletions(-) create mode 100644 apps/app/components/account/email-reset-password-form.tsx create mode 100644 apps/app/pages/reset-password.tsx diff --git a/apiserver/templates/emails/auth/forgot_password.html b/apiserver/templates/emails/auth/forgot_password.html index 7c3ae585f..14a15e5b2 100644 --- a/apiserver/templates/emails/auth/forgot_password.html +++ b/apiserver/templates/emails/auth/forgot_password.html @@ -2,10 +2,10 @@

Dear {{first_name}},

- Welcome! Your account has been created. - Verify your email by clicking on the link below
- {{forgot_password_url}} - successfully.

+ Here is the link to reset your password. +
+ Link: {{forgot_password_url}} +

+ Thank you

- \ No newline at end of file diff --git a/apps/app/components/account/email-password-form.tsx b/apps/app/components/account/email-password-form.tsx index 7d56d67a0..920ec0829 100644 --- a/apps/app/components/account/email-password-form.tsx +++ b/apps/app/components/account/email-password-form.tsx @@ -1,6 +1,4 @@ -import React from "react"; - -import Link from "next/link"; +import React, { useState } from "react"; // react hook form import { useForm } from "react-hook-form"; @@ -8,6 +6,8 @@ import { useForm } from "react-hook-form"; import authenticationService from "services/authentication.service"; // hooks import useToast from "hooks/use-toast"; +// components +import { EmailResetPasswordForm } from "components/account"; // ui import { Input, SecondaryButton } from "components/ui"; // types @@ -18,7 +18,10 @@ type EmailPasswordFormValues = { }; export const EmailPasswordForm = ({ handleSignIn }: any) => { + const [isResettingPassword, setIsResettingPassword] = useState(false); + const { setToastAlert } = useToast(); + const { register, handleSubmit, @@ -58,59 +61,66 @@ export const EmailPasswordForm = ({ handleSignIn }: any) => { }); }); }; + return ( <> -
-
- - /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( - value - ) || "Email ID is not valid", - }} - error={errors.email} - placeholder="Enter your Email ID" - /> -
-
- -
-
-
- - - Forgot your password? - - + {isResettingPassword ? ( + + ) : ( + +
+ + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( + value + ) || "Email ID is not valid", + }} + error={errors.email} + placeholder="Enter your Email ID" + />
-
-
- - {isSubmitting ? "Signing in..." : "Sign In"} - -
- +
+ +
+
+
+ +
+
+
+ + {isSubmitting ? "Signing in..." : "Sign In"} + +
+ + )} ); }; diff --git a/apps/app/components/account/email-reset-password-form.tsx b/apps/app/components/account/email-reset-password-form.tsx new file mode 100644 index 000000000..03ea69042 --- /dev/null +++ b/apps/app/components/account/email-reset-password-form.tsx @@ -0,0 +1,93 @@ +import React from "react"; + +// react hook form +import { useForm } from "react-hook-form"; +// services +import userService from "services/user.service"; +// hooks +import useToast from "hooks/use-toast"; +// ui +import { Input, PrimaryButton, SecondaryButton } from "components/ui"; +// types +type Props = { + setIsResettingPassword: React.Dispatch>; +}; + +export const EmailResetPasswordForm: React.FC = ({ setIsResettingPassword }) => { + const { setToastAlert } = useToast(); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + defaultValues: { + email: "", + }, + mode: "onChange", + reValidateMode: "onChange", + }); + + const forgotPassword = async (formData: any) => { + const payload = { + email: formData.email, + }; + + await userService + .forgotPassword(payload) + .then(() => + setToastAlert({ + type: "success", + title: "Success!", + message: "Password reset link has been sent to your email address.", + }) + ) + .catch((err) => { + if (err.status === 400) + setToastAlert({ + type: "error", + title: "Error!", + message: "Please check the Email ID entered.", + }); + else + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong. Please try again.", + }); + }); + }; + + return ( +
+
+ + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test( + value + ) || "Email ID is not valid", + }} + error={errors.email} + placeholder="Enter registered Email ID" + /> +
+
+ setIsResettingPassword(false)} + > + Go Back + + + {isSubmitting ? "Sending link..." : "Send reset link"} + +
+
+ ); +}; diff --git a/apps/app/components/account/index.ts b/apps/app/components/account/index.ts index d0e5e4dfa..dee3e3251 100644 --- a/apps/app/components/account/index.ts +++ b/apps/app/components/account/index.ts @@ -1,4 +1,5 @@ -export * from "./google-login"; export * from "./email-code-form"; export * from "./email-password-form"; +export * from "./email-reset-password-form"; export * from "./github-login-button"; +export * from "./google-login"; diff --git a/apps/app/components/workspace/create-workspace-form.tsx b/apps/app/components/workspace/create-workspace-form.tsx index e3da82c10..213232d24 100644 --- a/apps/app/components/workspace/create-workspace-form.tsx +++ b/apps/app/components/workspace/create-workspace-form.tsx @@ -36,6 +36,7 @@ const restrictedUrls = [ "invitations", "magic-sign-in", "onboarding", + "reset-password", "signin", "workspace-member-invitation", "404", diff --git a/apps/app/pages/reset-password.tsx b/apps/app/pages/reset-password.tsx new file mode 100644 index 000000000..86ab556b1 --- /dev/null +++ b/apps/app/pages/reset-password.tsx @@ -0,0 +1,132 @@ +import React from "react"; + +import { useRouter } from "next/router"; +import Image from "next/image"; + +// react-hook-form +import { useForm } from "react-hook-form"; +// hooks +import useToast from "hooks/use-toast"; +// services +import userService from "services/user.service"; +// layouts +import DefaultLayout from "layouts/default-layout"; +// ui +import { Input, SecondaryButton } from "components/ui"; +// icons +import Logo from "public/logo.png"; +// types +import type { NextPage } from "next"; + +type FormData = { + password: string; + confirmPassword: string; +}; + +const ResetPasswordPage: NextPage = () => { + const router = useRouter(); + const { uidb64, token } = router.query; + + const { setToastAlert } = useToast(); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm(); + + const onSubmit = async (formData: FormData) => { + if (!uidb64 || !token) return; + + if (formData.password !== formData.confirmPassword) { + setToastAlert({ + type: "error", + title: "Error!", + message: "Passwords do not match.", + }); + + return; + } + + const payload = { + new_password: formData.password, + confirm_password: formData.confirmPassword, + }; + + await userService + .resetPassword(uidb64.toString(), token.toString(), payload) + .then(() => { + setToastAlert({ + type: "success", + title: "Success!", + message: "Password reset successfully. You can now login with your new password.", + }); + router.push("/"); + }) + .catch(() => + setToastAlert({ + type: "error", + title: "Error!", + message: "Something went wrong. Please try again.", + }) + ); + }; + + return ( + +
+
+
+
+ Plane Web Logo +

+ Reset your password +

+
+
+
+
+ +
+
+ +
+
+ + {isSubmitting ? "Resetting..." : "Reset"} + +
+
+
+
+
+
+
+ ); +}; + +export default ResetPasswordPage; diff --git a/apps/app/services/user.service.ts b/apps/app/services/user.service.ts index 209d3cc4f..c6b8494a4 100644 --- a/apps/app/services/user.service.ts +++ b/apps/app/services/user.service.ts @@ -92,6 +92,29 @@ class UserService extends APIService { throw error?.response?.data; }); } + + async forgotPassword(data: { email: string }): Promise { + return this.post(`/api/forgot-password/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + async resetPassword( + uidb64: string, + token: string, + data: { + new_password: string; + confirm_password: string; + } + ): Promise { + return this.post(`/api/reset-password/${uidb64}/${token}/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } } export default new UserService();