From addfc9d69cc29ed9dc0cbeed5a2400c60579f404 Mon Sep 17 00:00:00 2001 From: Simon Larsen Date: Fri, 11 Nov 2022 18:03:17 +0000 Subject: [PATCH] fix accounts page --- Accounts/src/App.tsx | 3 +- Accounts/src/Pages/ResetPassword.tsx | 134 ++++++ Accounts/src/Utils/ApiPaths.ts | 4 + Common/Types/Email/EmailTemplateType.ts | 1 + Identity/API/AuthenticationAPI.ts | 57 ++- Mail/Templates/PasswordChanged.hbs | 550 ++++++++++++++++++++++++ 6 files changed, 730 insertions(+), 19 deletions(-) create mode 100644 Accounts/src/Pages/ResetPassword.tsx create mode 100644 Mail/Templates/PasswordChanged.hbs diff --git a/Accounts/src/App.tsx b/Accounts/src/App.tsx index fc90f796d0..a747f211d8 100644 --- a/Accounts/src/App.tsx +++ b/Accounts/src/App.tsx @@ -16,6 +16,7 @@ import 'CommonUI/src/Styles/theme.scss'; import Navigation from 'CommonUI/src/Utils/Navigation'; import VerifyEmail from './Pages/VerifyEmail'; import User from 'CommonUI/src/Utils/User'; +import ResetPasswordPage from './Pages/ResetPassword'; function App(): ReactElement { Navigation.setNavigateHook(useNavigate()); @@ -37,7 +38,7 @@ function App(): ReactElement { /> } + element={} /> } /> } /> diff --git a/Accounts/src/Pages/ResetPassword.tsx b/Accounts/src/Pages/ResetPassword.tsx new file mode 100644 index 0000000000..25631c0a0f --- /dev/null +++ b/Accounts/src/Pages/ResetPassword.tsx @@ -0,0 +1,134 @@ +import React, { FunctionComponent, useState } from 'react'; +import ModelForm, { FormType } from 'CommonUI/src/Components/Forms/ModelForm'; +import User from 'Model/Models/User'; +import Link from 'CommonUI/src/Components/Link/Link'; +import Route from 'Common/Types/API/Route'; +import FormFieldSchemaType from 'CommonUI/src/Components/Forms/Types/FormFieldSchemaType'; +import OneUptimeLogo from 'CommonUI/src/Images/logos/OneUptimePNG/7.png'; + +import URL from 'Common/Types/API/URL'; +import { RESET_PASSWORD_API_URL } from '../Utils/ApiPaths'; +import Navigation from 'CommonUI/src/Utils/Navigation'; + +const RegisterPage: FunctionComponent = () => { + const apiUrl: URL = RESET_PASSWORD_API_URL; + const [isSuccess, setIsSuccess] = useState(false); + + return ( +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+ Reset Password. +
+ {!isSuccess &&

+ Please enter your new password and we will have it updated.{' '} +

} + + {isSuccess &&

+ Your password has been updated. Please log in. +

} + +
+ + {!isSuccess && + modelType={User} + id="register-form" + onBeforeCreate={(item: User) => { + item.resetPasswordToken = Navigation.getLastParam()?.toString().replace("/", "").toString() ||'' + return item; + }} + showAsColumns={1} + maxPrimaryButtonWidth={true} + initialValues={{ + password: '', + confirmPassword: '', + }} + fields={[ + + { + field: { + password: true, + }, + fieldType: + FormFieldSchemaType.Password, + validation: { + minLength: 6, + }, + placeholder: 'New Password', + title: 'New Password', + required: true, + }, + { + field: { + password: true, + }, + validation: { + minLength: 6, + toMatchField: + 'password', + }, + fieldType: + FormFieldSchemaType.Password, + placeholder: + 'Confirm Password', + title: 'Confirm Password', + overideFieldKey: + 'confirmPassword', + required: true, + }, + ]} + apiUrl={apiUrl} + formType={FormType.Create} + submitButtonText={'Reset Password'} + onSuccess={() => { + setIsSuccess(true); + }} + />} + +
+

+ Know your password?{' '} + + Log in. + +

+
+
+
+
+
+
+ +
+
+
+
+ ); +}; + +export default RegisterPage; diff --git a/Accounts/src/Utils/ApiPaths.ts b/Accounts/src/Utils/ApiPaths.ts index d8d1a3d8e2..37d6939806 100644 --- a/Accounts/src/Utils/ApiPaths.ts +++ b/Accounts/src/Utils/ApiPaths.ts @@ -15,4 +15,8 @@ export const FORGOT_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute( export const VERIFY_EMAIL_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute( new Route('/verify-email') +); + +export const RESET_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute( + new Route('/reset-password') ); \ No newline at end of file diff --git a/Common/Types/Email/EmailTemplateType.ts b/Common/Types/Email/EmailTemplateType.ts index e33c6fea86..ec2c81e00a 100644 --- a/Common/Types/Email/EmailTemplateType.ts +++ b/Common/Types/Email/EmailTemplateType.ts @@ -2,6 +2,7 @@ enum EmailTemplateType { ForgotPassword = 'ForgotPassword.hbs', SignupWelcomeEmail = 'SignupWelcomeEmail.hbs', EmailVerified = 'EmailVerified.hbs', + PasswordChanged = 'PasswordChanged.hbs', } export default EmailTemplateType; diff --git a/Identity/API/AuthenticationAPI.ts b/Identity/API/AuthenticationAPI.ts index 7028244d75..71c852014b 100644 --- a/Identity/API/AuthenticationAPI.ts +++ b/Identity/API/AuthenticationAPI.ts @@ -106,7 +106,7 @@ router.post( const emailVerificationToken = new EmailVerificationToken(); emailVerificationToken.userId = savedUser?.id!; emailVerificationToken.email = savedUser?.email!; - emailVerificationToken.token = generatedToken; + emailVerificationToken.token = generatedToken; emailVerificationToken.expires = OneUptimeDate.getOneDayAfter() await EmailVerificationTokenService.create({ @@ -321,33 +321,54 @@ router.post( await user.password?.hashValue(EncryptionSecret); const alreadySavedUser: User | null = await UserService.findOneBy({ - query: { email: user.email!, password: user.password! }, + query: { resetPasswordToken: user.resetPasswordToken as string || '' }, select: { _id: true, password: true, name: true, email: true, isMasterAdmin: true, + resetPasswordExpires: true }, props: { isRoot: true, }, }); - if (alreadySavedUser) { - const token: string = JSONWebToken.sign( - alreadySavedUser, - OneUptimeDate.getSecondsInDays(new PositiveNumber(30)) - ); - - return Response.sendJsonObjectResponse(req, res, { - token: token, - user: BaseModel.toJSON(alreadySavedUser, User), - }); + if (!alreadySavedUser) { + throw new BadDataException("Invalid link. Please go to forgot password page again and request a new link.") } - throw new BadDataException( - 'Invalid login: Email or password does not match.' - ); + + if (alreadySavedUser && OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!)) { + throw new BadDataException("Expired link. Please go to forgot password page again and request a new link.") + } + + + await UserService.updateOneById({ + id: alreadySavedUser.id!, + data: { + password: user.password!, + resetPasswordToken: null!, + resetPasswordExpires: null! + }, + props: { + isRoot: true, + }, + }); + + MailService.sendMail({ + toEmail: alreadySavedUser.email!, + subject: 'Password Changed.', + templateType: EmailTemplateType.PasswordChanged, + vars: { + homeURL: new URL(HttpProtocol, Domain).toString(), + }, + }).catch((err: Error) => { + logger.error(err); + }); + + return Response.sendEmptyResponse(req, res); + } catch (err) { return next(err); } @@ -391,16 +412,16 @@ router.post( const emailVerificationToken = new EmailVerificationToken(); emailVerificationToken.userId = alreadySavedUser?.id!; emailVerificationToken.email = alreadySavedUser?.email!; - emailVerificationToken.token = generatedToken; + emailVerificationToken.token = generatedToken; emailVerificationToken.expires = OneUptimeDate.getOneDayAfter() - + await EmailVerificationTokenService.create({ data: emailVerificationToken, props: { isRoot: true }, }); - + MailService.sendMail({ toEmail: alreadySavedUser.email!, diff --git a/Mail/Templates/PasswordChanged.hbs b/Mail/Templates/PasswordChanged.hbs new file mode 100644 index 0000000000..a328c5519a --- /dev/null +++ b/Mail/Templates/PasswordChanged.hbs @@ -0,0 +1,550 @@ + + + + + + + Welcome to OneUptime. + + + + + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + +
+
+
+ +

You have changed your password.

+ +
+
+
+
+
+ + + + + + + + + + + + +
+
+
+ + + + Your password has been updated. You can now log in. + + + +
+
+
+
+ + + + + + + + + + + + +
+
+
+
+
+
+
+ + + + + + + + + + + +
+
+
+ + + Thanks, have a great day. + + + +
+
+
+
+ + + + + + + + + + + +
+
+
+ + + OneUptime Team. + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
 
+
+ + + + \ No newline at end of file