From 4f40d8db413b5cfd827193b7ee95d947641db30d Mon Sep 17 00:00:00 2001
From: Matt Fancher <142915944+FancMa01@users.noreply.github.com>
Date: Mon, 23 Dec 2024 10:19:57 -0700
Subject: [PATCH 1/4] Wire Password Reset
---
.../src/components/login/ForgotPassword.js | 13 +++-
.../src/components/login/ResetPassword.js | 61 +++++++++++++------
Tombolo/server/controllers/authController.js | 40 +++++++++---
3 files changed, 83 insertions(+), 31 deletions(-)
diff --git a/Tombolo/client-reactjs/src/components/login/ForgotPassword.js b/Tombolo/client-reactjs/src/components/login/ForgotPassword.js
index 5a29d95f..a3be9c46 100644
--- a/Tombolo/client-reactjs/src/components/login/ForgotPassword.js
+++ b/Tombolo/client-reactjs/src/components/login/ForgotPassword.js
@@ -1,10 +1,17 @@
import React from 'react';
import { Form, Input, Button, Divider, message } from 'antd';
+import { authHeader } from '../common/AuthHeader';
const ForgotPassword = () => {
- const onFinish = (values) => {
- console.log('Received values:', values);
- success();
+ const onFinish = async (values) => {
+ try {
+ const url = '/api/auth/handlePasswordResetRequest';
+ await fetch(url, { headers: authHeader(), method: 'POST', body: JSON.stringify(values) });
+
+ success();
+ } catch (err) {
+ message.error(err.message);
+ }
};
const [messageApi, contextHolder] = message.useMessage();
diff --git a/Tombolo/client-reactjs/src/components/login/ResetPassword.js b/Tombolo/client-reactjs/src/components/login/ResetPassword.js
index 28a89867..181cbeb9 100644
--- a/Tombolo/client-reactjs/src/components/login/ResetPassword.js
+++ b/Tombolo/client-reactjs/src/components/login/ResetPassword.js
@@ -2,8 +2,12 @@ import React, { useEffect, useState } from 'react';
import { Form, Input, Button, Divider, message, Popover } from 'antd';
import { useParams } from 'react-router-dom';
import passwordComplexityValidator from '../common/passwordComplexityValidator';
+import { authHeader } from '../common/AuthHeader';
+
+import { getDeviceInfo } from './utils';
+import { setUser } from '../common/userStorage';
+
const ResetPassword = () => {
- const [user, setUser] = useState(null);
const [popOverContent, setPopOverContent] = useState(null);
//we will get the reset token from the url and test if it is valid to get the user information
@@ -29,28 +33,49 @@ const ResetPassword = () => {
});
};
+ //if there is no token, we will show an error message to the user
useEffect(() => {
- //check if reset token is valid, if it is, we will get the user ID and store it, if not, we will redirect to the login page
- if (user === null && resetToken !== undefined) {
- //get user information by reset token
+ if (resetToken === undefined) {
+ invalidToken();
+ }
+ }, []);
- // get user by reset token route
+ const onFinish = async (values) => {
+ try {
+ const url = '/api/auth/resetTempPassword';
+ const password = values.newPassword;
+ const deviceInfo = getDeviceInfo();
- //if user is found, set user and return
- console.log(setUser);
+ const response = await fetch(url, {
+ headers: authHeader(),
+ method: 'POST',
+ body: JSON.stringify({ password, token: resetToken, deviceInfo }),
+ });
- //if user is not found, message
- invalidToken();
- }
+ if (!response.ok) {
+ let json = await response.json();
- if (resetToken === undefined) {
- //redirect to login page
- }
- }, []);
+ if (json.message) {
+ message.error(json.message);
+ } else {
+ message.error('An undefined error occurred. Please try again later');
+ }
+ return;
+ }
- const onFinish = (values) => {
- console.log('Received values:', values);
- alert('reset password code fires here');
+ if (response.ok) {
+ message.success('Password reset successfully.');
+ let json = await response.json();
+ if (json.success === true) {
+ json.data.isAuthenticated = true;
+ setUser(json.data);
+ //reload window
+ window.location.href = '/';
+ }
+ }
+ } catch (err) {
+ message.error(err.message);
+ }
};
useEffect(() => {}, [popOverContent]);
@@ -119,7 +144,7 @@ const ResetPassword = () => {
-
diff --git a/Tombolo/server/controllers/authController.js b/Tombolo/server/controllers/authController.js
index 5e3e006d..9ae17bf5 100644
--- a/Tombolo/server/controllers/authController.js
+++ b/Tombolo/server/controllers/authController.js
@@ -283,7 +283,7 @@ const verifyEmail = async (req, res) => {
//Reset Temp password
const resetTempPassword = async (req, res) => {
try {
- const { tempPassword, password, token, deviceInfo } = req.body;
+ const { password, token, deviceInfo } = req.body;
// From AccountVerificationCodes table findUser ID by code, where code is resetToken
const accountVerificationCode = await AccountVerificationCodes.findOne({
@@ -308,11 +308,6 @@ const resetTempPassword = async (req, res) => {
throw { status: 404, message: "User not found" };
}
- // Compare temp password with hash.
- if (!bcrypt.compareSync(tempPassword, user.hash)) {
- throw { status: 500, message: "Invalid temporary password" };
- }
-
// Hash the new password
const salt = bcrypt.genSaltSync(10);
user.hash = bcrypt.hashSync(password, salt);
@@ -328,6 +323,11 @@ const resetTempPassword = async (req, res) => {
where: { code: token },
});
+ //delete password reset link
+ await PasswordResetLinks.destroy({
+ where: { id: token },
+ });
+
// Create token id
const tokenId = uuidv4();
@@ -549,7 +549,14 @@ const handlePasswordResetRequest = async (req, res) => {
// Generate a password reset token
const randomId = uuidv4();
- const passwordRestLink = `${process.env.WEB_URL}/reset-password/${randomId}`;
+ let webUrl = process.env.WEB_URL;
+
+ //cut off last character if it is a slash
+ if (webUrl[webUrl.length - 1] === "/") {
+ webUrl = webUrl.slice(0, -1);
+ }
+
+ const passwordRestLink = `${webUrl}/reset-password/${randomId}`;
// Notification subject
let subject = "Password Reset Link";
@@ -557,6 +564,9 @@ const handlePasswordResetRequest = async (req, res) => {
subject = `${process.env.INSTANCE_NAME} - ${subject}`;
}
+ //include searchableNotificationId in the notification meta data
+ const searchableNotificationId = uuidv4();
+
// Queue notification
await NotificationQueue.create({
type: "email",
@@ -566,9 +576,12 @@ const handlePasswordResetRequest = async (req, res) => {
createdBy: "System",
updatedBy: "System",
metaData: {
- mainRecipients: [email],
- subject,
- body: "Password reset link",
+ notificationId: searchableNotificationId,
+ recipientName: `${user.firstName}`,
+ notificationOrigin: "Reset Password",
+ subject: "PasswordResetLink",
+ mainRecipients: [user.email],
+ notificationDescription: "Password Reset Link",
validForHours: 24,
passwordRestLink,
},
@@ -583,6 +596,13 @@ const handlePasswordResetRequest = async (req, res) => {
expiresAt: new Date(new Date().getTime() + 24 * 60 * 60 * 1000),
});
+ // Create account verification code
+ await AccountVerificationCodes.create({
+ code: randomId,
+ userId: user.id,
+ expiresAt: new Date(new Date().getTime() + 24 * 60 * 60 * 1000),
+ });
+
// response
res.status(200).json({ success: true });
} catch (err) {
From 29dbbb61353dba2f8f4695678b10399c43393fdb Mon Sep 17 00:00:00 2001
From: Matt Fancher <142915944+FancMa01@users.noreply.github.com>
Date: Mon, 23 Dec 2024 11:27:28 -0700
Subject: [PATCH 2/4] Remove unecessary reset temp password code that isn't
used with temp password.
We use the link and token method for resetting temp passwords, no need to have temporary password code any longer
---
.../src/components/login/AuthRoutes.js | 2 -
.../src/components/login/ResetPassword.js | 2 +-
.../src/components/login/ResetTempPw.jsx | 126 ------------------
.../src/components/login/utils.js | 21 ---
Tombolo/server/controllers/authController.js | 67 +---------
Tombolo/server/routes/authRoutes.js | 8 +-
6 files changed, 11 insertions(+), 215 deletions(-)
delete mode 100644 Tombolo/client-reactjs/src/components/login/ResetTempPw.jsx
diff --git a/Tombolo/client-reactjs/src/components/login/AuthRoutes.js b/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
index b95123be..00acd3c6 100644
--- a/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
+++ b/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
@@ -5,7 +5,6 @@ const Login = React.lazy(() => import('./login.js'));
const Register = React.lazy(() => import('./register.js'));
const ResetPassword = React.lazy(() => import('./ResetPassword.js'));
const ForgotPassword = React.lazy(() => import('./ForgotPassword.js'));
-const resetTempPassword = React.lazy(() => import('./ResetTempPw'));
const AuthRoutes = () => {
//if traditional login isn't enabled, redirect user to login page
@@ -32,7 +31,6 @@ const AuthRoutes = () => {
-
{/* redirect all other routes hit to login */}
diff --git a/Tombolo/client-reactjs/src/components/login/ResetPassword.js b/Tombolo/client-reactjs/src/components/login/ResetPassword.js
index 181cbeb9..0f6ec064 100644
--- a/Tombolo/client-reactjs/src/components/login/ResetPassword.js
+++ b/Tombolo/client-reactjs/src/components/login/ResetPassword.js
@@ -42,7 +42,7 @@ const ResetPassword = () => {
const onFinish = async (values) => {
try {
- const url = '/api/auth/resetTempPassword';
+ const url = '/api/auth/resetPasswordWithToken';
const password = values.newPassword;
const deviceInfo = getDeviceInfo();
diff --git a/Tombolo/client-reactjs/src/components/login/ResetTempPw.jsx b/Tombolo/client-reactjs/src/components/login/ResetTempPw.jsx
deleted file mode 100644
index ed5eb879..00000000
--- a/Tombolo/client-reactjs/src/components/login/ResetTempPw.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { Form, Input, Button, Spin, message, Popover } from 'antd';
-import { resetTempPassword } from './utils';
-import passwordComplexityValidator from '../common/passwordComplexityValidator';
-import { setUser } from '../common/userStorage';
-
-function ResetTempPassword() {
- const [loading, setLoading] = useState(false);
- const [popOverContent, setPopOverContent] = useState(null);
- const [resetToken, setResetToken] = useState(null);
- const [form] = Form.useForm();
-
- // For password validator pop over
- const validatePassword = (value) => {
- setPopOverContent(passwordComplexityValidator({ password: value, generateContent: true }));
- };
-
- // On component load, get the token from the URL
- useEffect(() => {
- const url = window.location.href;
- const urlParts = url.split('/');
- const token = urlParts[urlParts.length - 1];
- setResetToken(token);
- }, []);
-
- // Handle form submission
- const handleSubmit = async () => {
- try {
- setLoading(true);
- let values;
- try {
- values = await form.validateFields();
- } catch (err) {
- return;
- }
- values.token = resetToken;
- const result = await resetTempPassword(values);
-
- // Save user token to local storage
- setUser(JSON.stringify(result.data));
- window.location.href = '/';
- } catch (err) {
- message.error(err.message);
- } finally {
- setLoading(false);
- }
- };
-
- return (
- value.trim()}
- rules={[
- {
- required: true,
- message: 'Please input your temporary password!',
- },
- ]}>
-
-
-
- value.trim()}
- rules={[
- {
- required: true,
- message: 'Please input your new password!',
- },
- () => ({
- validator(_, value) {
- //passwordComplexityValidator always returns an array with at least one attributes element
- const errors = passwordComplexityValidator({ password: value });
- if (!value || errors.length === 1) {
- return Promise.resolve();
- }
- return Promise.reject(new Error('Password does not meet complexity requirements!'));
- },
- }),
- ]}>
- {
- validatePassword(e.target.value);
- }}
- onFocus={(e) => {
- validatePassword(e.target.value);
- }}
- />
-
-
- value.trim()}
- rules={[
- {
- required: true,
- message: 'Please confirm your new password!',
- },
- {
- validator: async (_, value) => {
- if (!value || value === form.getFieldValue('password')) {
- return Promise.resolve();
- }
- return Promise.reject(new Error('The two passwords that you entered do not match!'));
- },
- },
- ]}>
-
-
-
- Reset Password {loading && }
-
-
- );
-}
-
-export default ResetTempPassword;
diff --git a/Tombolo/client-reactjs/src/components/login/utils.js b/Tombolo/client-reactjs/src/components/login/utils.js
index 916d2908..10d0aa32 100644
--- a/Tombolo/client-reactjs/src/components/login/utils.js
+++ b/Tombolo/client-reactjs/src/components/login/utils.js
@@ -22,27 +22,6 @@ export const getDeviceInfo = () => {
return { os, browser: browserName };
};
-// Make a request to the server to reset the temporary password
-export const resetTempPassword = async (resetData) => {
- const payload = {
- method: 'POST',
- headers: authHeader(),
- body: JSON.stringify({ ...resetData, deviceInfo: getDeviceInfo() }),
- };
-
- const response = await fetch('/api/auth/resetTempPassword', payload);
-
- // Get the data from the response
- const responseJson = await response.json();
-
- // Check if the response is ok
- if (!response.ok) {
- throw new Error(responseJson.message);
- }
-
- return responseJson;
-};
-
// Make POST request to api/auth/verifyEmail with token in body
export const verifyEmail = async (token) => {
// eslint-disable-next-line no-unreachable
diff --git a/Tombolo/server/controllers/authController.js b/Tombolo/server/controllers/authController.js
index 9ae17bf5..0e1d4301 100644
--- a/Tombolo/server/controllers/authController.js
+++ b/Tombolo/server/controllers/authController.js
@@ -281,7 +281,7 @@ const verifyEmail = async (req, res) => {
};
//Reset Temp password
-const resetTempPassword = async (req, res) => {
+const resetPasswordWithToken = async (req, res) => {
try {
const { password, token, deviceInfo } = req.body;
@@ -328,6 +328,11 @@ const resetTempPassword = async (req, res) => {
where: { id: token },
});
+ //remove all sessions for user before initiating new session
+ await RefreshTokens.destroy({
+ where: { userId: user.id },
+ });
+
// Create token id
const tokenId = uuidv4();
@@ -613,63 +618,6 @@ const handlePasswordResetRequest = async (req, res) => {
}
};
-// Reset password
-const resetPassword = async (req, res) => {
- try {
- // Get the password reset token
- const { token, password } = req.body;
-
- // Find the password reset token
- const passwordResetLink = await PasswordResetLinks.findOne({
- where: { id: token },
- });
-
- // Token does not exist
- if (!passwordResetLink) {
- logger.error(`Reset password: Token ${token} does not exist`);
- return res.status(404).json({
- success: false,
- message: "Password reset link Invalid or expired",
- });
- }
-
- // Token has expired
- if (new Date() > passwordResetLink.expiresAt) {
- logger.error(`Reset password: Token ${token} has expired`);
- return res
- .status(400)
- .json({ success: false, message: "Token has expired" });
- }
-
- // Find the user
- const user = await User.findOne({
- where: { id: passwordResetLink.userId },
- });
-
- // Hash the new password
- const salt = bcrypt.genSaltSync(10);
- user.hash = bcrypt.hashSync(password, salt);
-
- // Save the user
- await user.save();
-
- // Delete the password link from
- PasswordResetLinks.destroy({
- where: { id: token },
- });
-
- // response
- res
- .status(200)
- .json({ success: true, message: "Password updated successfully" });
- } catch (err) {
- logger.error(`Reset password: ${err.message}`);
- res
- .status(err.status || 500)
- .json({ success: false, message: err.message });
- }
-};
-
// Login or register with azure user - loginOrRegisterAzureUser [ `https://login.microsoftonline.com/${tenant_id}/oauth2/v2.0/token`]
const loginOrRegisterAzureUser = async (req, res, next) => {
try {
@@ -829,11 +777,10 @@ const loginOrRegisterAzureUser = async (req, res, next) => {
module.exports = {
createBasicUser,
verifyEmail,
- resetTempPassword,
+ resetPasswordWithToken,
loginBasicUser,
logOutBasicUser,
handlePasswordResetRequest,
- resetPassword,
createApplicationOwner,
loginOrRegisterAzureUser,
};
diff --git a/Tombolo/server/routes/authRoutes.js b/Tombolo/server/routes/authRoutes.js
index 2a3bcc53..760120a4 100644
--- a/Tombolo/server/routes/authRoutes.js
+++ b/Tombolo/server/routes/authRoutes.js
@@ -18,9 +18,8 @@ const {
loginBasicUser,
logOutBasicUser,
handlePasswordResetRequest,
- resetPassword,
createApplicationOwner,
- resetTempPassword,
+ resetPasswordWithToken,
verifyEmail,
loginOrRegisterAzureUser,
} = require("../controllers/authController");
@@ -45,11 +44,10 @@ router.post(
validatePasswordResetRequestPayload,
handlePasswordResetRequest
); // Reset password
-router.post("/resetPassword", validateResetPasswordPayload, resetPassword); // Reset password
router.post(
- "/resetTempPassword",
+ "/resetPasswordWithToken",
validateResetPasswordPayload,
- resetTempPassword
+ resetPasswordWithToken
); // Complete registration by resetting temporary password
router.post("/verifyEmail", verifyEmail); // Verify email
// TODO - Forgot password route
From 5f4bea92eb08809a1ccaac5b3d667a6942dbad15 Mon Sep 17 00:00:00 2001
From: Matt Fancher <142915944+FancMa01@users.noreply.github.com>
Date: Thu, 2 Jan 2025 13:57:44 -0700
Subject: [PATCH 3/4] Remove unecessary reset temp password code that isn't
used with temp password.
Amended this commit to re-introduce reset temp password items.
---
.../src/components/login/AuthRoutes.js | 6 +-
...tPassword.js => ResetPasswordWithToken.js} | 0
.../src/components/login/ResetTempPassword.js | 126 ++++++++++++++++++
.../src/components/login/utils.js | 21 +++
Tombolo/server/controllers/authController.js | 107 ++++++++++++++-
Tombolo/server/routes/authRoutes.js | 8 +-
6 files changed, 264 insertions(+), 4 deletions(-)
rename Tombolo/client-reactjs/src/components/login/{ResetPassword.js => ResetPasswordWithToken.js} (100%)
create mode 100644 Tombolo/client-reactjs/src/components/login/ResetTempPassword.js
diff --git a/Tombolo/client-reactjs/src/components/login/AuthRoutes.js b/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
index 00acd3c6..e53361f2 100644
--- a/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
+++ b/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
@@ -3,8 +3,9 @@ import BasicLayout from '../common/BasicLayout';
import { Route, Switch } from 'react-router-dom';
const Login = React.lazy(() => import('./login.js'));
const Register = React.lazy(() => import('./register.js'));
-const ResetPassword = React.lazy(() => import('./ResetPassword.js'));
+const ResetPassword = React.lazy(() => import('./ResetPasswordWithToken.js'));
const ForgotPassword = React.lazy(() => import('./ForgotPassword.js'));
+const resetTempPassword = React.lazy(() => import('./ResetTempPassword.js'));
const AuthRoutes = () => {
//if traditional login isn't enabled, redirect user to login page
@@ -29,8 +30,11 @@ const AuthRoutes = () => {
+ {/* reset password with self requested token */}
+ {/* reset password with temp password from owner/admin registration */}
+
{/* redirect all other routes hit to login */}
diff --git a/Tombolo/client-reactjs/src/components/login/ResetPassword.js b/Tombolo/client-reactjs/src/components/login/ResetPasswordWithToken.js
similarity index 100%
rename from Tombolo/client-reactjs/src/components/login/ResetPassword.js
rename to Tombolo/client-reactjs/src/components/login/ResetPasswordWithToken.js
diff --git a/Tombolo/client-reactjs/src/components/login/ResetTempPassword.js b/Tombolo/client-reactjs/src/components/login/ResetTempPassword.js
new file mode 100644
index 00000000..ed5eb879
--- /dev/null
+++ b/Tombolo/client-reactjs/src/components/login/ResetTempPassword.js
@@ -0,0 +1,126 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Button, Spin, message, Popover } from 'antd';
+import { resetTempPassword } from './utils';
+import passwordComplexityValidator from '../common/passwordComplexityValidator';
+import { setUser } from '../common/userStorage';
+
+function ResetTempPassword() {
+ const [loading, setLoading] = useState(false);
+ const [popOverContent, setPopOverContent] = useState(null);
+ const [resetToken, setResetToken] = useState(null);
+ const [form] = Form.useForm();
+
+ // For password validator pop over
+ const validatePassword = (value) => {
+ setPopOverContent(passwordComplexityValidator({ password: value, generateContent: true }));
+ };
+
+ // On component load, get the token from the URL
+ useEffect(() => {
+ const url = window.location.href;
+ const urlParts = url.split('/');
+ const token = urlParts[urlParts.length - 1];
+ setResetToken(token);
+ }, []);
+
+ // Handle form submission
+ const handleSubmit = async () => {
+ try {
+ setLoading(true);
+ let values;
+ try {
+ values = await form.validateFields();
+ } catch (err) {
+ return;
+ }
+ values.token = resetToken;
+ const result = await resetTempPassword(values);
+
+ // Save user token to local storage
+ setUser(JSON.stringify(result.data));
+ window.location.href = '/';
+ } catch (err) {
+ message.error(err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ value.trim()}
+ rules={[
+ {
+ required: true,
+ message: 'Please input your temporary password!',
+ },
+ ]}>
+
+
+
+ value.trim()}
+ rules={[
+ {
+ required: true,
+ message: 'Please input your new password!',
+ },
+ () => ({
+ validator(_, value) {
+ //passwordComplexityValidator always returns an array with at least one attributes element
+ const errors = passwordComplexityValidator({ password: value });
+ if (!value || errors.length === 1) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('Password does not meet complexity requirements!'));
+ },
+ }),
+ ]}>
+ {
+ validatePassword(e.target.value);
+ }}
+ onFocus={(e) => {
+ validatePassword(e.target.value);
+ }}
+ />
+
+
+ value.trim()}
+ rules={[
+ {
+ required: true,
+ message: 'Please confirm your new password!',
+ },
+ {
+ validator: async (_, value) => {
+ if (!value || value === form.getFieldValue('password')) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('The two passwords that you entered do not match!'));
+ },
+ },
+ ]}>
+
+
+
+ Reset Password {loading && }
+
+
+ );
+}
+
+export default ResetTempPassword;
diff --git a/Tombolo/client-reactjs/src/components/login/utils.js b/Tombolo/client-reactjs/src/components/login/utils.js
index 10d0aa32..3070c635 100644
--- a/Tombolo/client-reactjs/src/components/login/utils.js
+++ b/Tombolo/client-reactjs/src/components/login/utils.js
@@ -22,6 +22,27 @@ export const getDeviceInfo = () => {
return { os, browser: browserName };
};
+// Make a request to the server to reset the temporary password - OWNER REGISTRATION
+export const resetTempPassword = async (resetData) => {
+ const payload = {
+ method: 'POST',
+ headers: authHeader(),
+ body: JSON.stringify({ ...resetData, deviceInfo: getDeviceInfo() }),
+ };
+
+ const response = await fetch('/api/auth/resetTempPassword', payload);
+
+ // Get the data from the response
+ const responseJson = await response.json();
+
+ // Check if the response is ok
+ if (!response.ok) {
+ throw new Error(responseJson.message);
+ }
+
+ return responseJson;
+};
+
// Make POST request to api/auth/verifyEmail with token in body
export const verifyEmail = async (token) => {
// eslint-disable-next-line no-unreachable
diff --git a/Tombolo/server/controllers/authController.js b/Tombolo/server/controllers/authController.js
index 0e1d4301..c67b8264 100644
--- a/Tombolo/server/controllers/authController.js
+++ b/Tombolo/server/controllers/authController.js
@@ -280,7 +280,7 @@ const verifyEmail = async (req, res) => {
}
};
-//Reset Temp password
+//Reset Password With Token - Self Requested
const resetPasswordWithToken = async (req, res) => {
try {
const { password, token, deviceInfo } = req.body;
@@ -382,6 +382,108 @@ const resetPasswordWithToken = async (req, res) => {
}
};
+//Reset Password with Temp Password - Owner/Admin requested
+const resetTempPassword = async (req, res) => {
+ try {
+ const { password, token, deviceInfo } = req.body;
+
+ // From AccountVerificationCodes table findUser ID by code, where code is resetToken
+ const accountVerificationCode = await AccountVerificationCodes.findOne({
+ where: { code: token },
+ });
+
+ // If accountVerificationCode not found
+ if (!accountVerificationCode) {
+ throw { status: 404, message: "Invalid or expired reset token" };
+ }
+
+ // If accountVerificationCode has expired
+ if (new Date() > accountVerificationCode.expiresAt) {
+ throw { status: 400, message: "Reset token has expired" };
+ }
+
+ // Find user by ID
+ let user = await getAUser({ id: accountVerificationCode.userId });
+
+ // If user not found
+ if (!user) {
+ throw { status: 404, message: "User not found" };
+ }
+
+ // Hash the new password
+ const salt = bcrypt.genSaltSync(10);
+ user.hash = bcrypt.hashSync(password, salt);
+ user.verifiedUser = true;
+ user.verifiedAt = new Date();
+ user.forcePasswordReset = false;
+
+ // Save user with updated details
+ await user.save();
+
+ // Delete the account verification code
+ await AccountVerificationCodes.destroy({
+ where: { code: token },
+ });
+
+ //delete password reset link
+ await PasswordResetLinks.destroy({
+ where: { id: token },
+ });
+
+ //remove all sessions for user before initiating new session
+ await RefreshTokens.destroy({
+ where: { userId: user.id },
+ });
+
+ // Create token id
+ const tokenId = uuidv4();
+
+ // Create access jwt
+ const accessToken = generateAccessToken({ ...user.toJSON(), tokenId });
+
+ // Generate refresh token
+ const refreshToken = generateRefreshToken({ tokenId });
+
+ // Save refresh token to DB
+ const { iat, exp } = jwt.decode(refreshToken);
+
+ // Save refresh token in DB
+ await RefreshTokens.create({
+ id: tokenId,
+ userId: user.id,
+ token: refreshToken,
+ deviceInfo,
+ metaData: {},
+ iat: new Date(iat * 1000),
+ exp: new Date(exp * 1000),
+ });
+
+ await setTokenCookie(res, accessToken);
+
+ await generateAndSetCSRFToken(req, res, accessToken);
+
+ // User data obj to send to the client
+ const userObj = {
+ ...user.toJSON(),
+ };
+
+ // remove hash from user object
+ delete userObj.hash;
+
+ // Success response
+ res.status(200).json({
+ success: true,
+ message: "Password updated successfully",
+ data: userObj,
+ });
+ } catch (err) {
+ logger.error(`Reset Temp Password: ${err.message}`);
+ res
+ .status(err.status || 500)
+ .json({ success: false, message: err.message });
+ }
+};
+
//Login Basic user
const loginBasicUser = async (req, res, next) => {
try {
@@ -513,7 +615,7 @@ const logOutBasicUser = async (req, res) => {
}
};
-// Fulfill password reset request
+// Fulfill password reset request - Self Requested
const handlePasswordResetRequest = async (req, res) => {
try {
// Get user email
@@ -778,6 +880,7 @@ module.exports = {
createBasicUser,
verifyEmail,
resetPasswordWithToken,
+ resetTempPassword,
loginBasicUser,
logOutBasicUser,
handlePasswordResetRequest,
diff --git a/Tombolo/server/routes/authRoutes.js b/Tombolo/server/routes/authRoutes.js
index 760120a4..92644c70 100644
--- a/Tombolo/server/routes/authRoutes.js
+++ b/Tombolo/server/routes/authRoutes.js
@@ -20,6 +20,7 @@ const {
handlePasswordResetRequest,
createApplicationOwner,
resetPasswordWithToken,
+ resetTempPassword,
verifyEmail,
loginOrRegisterAzureUser,
} = require("../controllers/authController");
@@ -48,7 +49,12 @@ router.post(
"/resetPasswordWithToken",
validateResetPasswordPayload,
resetPasswordWithToken
-); // Complete registration by resetting temporary password
+); // Reset Password - Self Requested
+router.post(
+ "/resetPasswordWithToken",
+ validateResetPasswordPayload,
+ resetTempPassword
+); // Reset Password - Owner/Admin requested through registration flow
router.post("/verifyEmail", verifyEmail); // Verify email
// TODO - Forgot password route
From f2e0ab536196e16521234d87cd9c25a513b5fdc2 Mon Sep 17 00:00:00 2001
From: Matt Fancher <142915944+FancMa01@users.noreply.github.com>
Date: Thu, 2 Jan 2025 14:31:09 -0700
Subject: [PATCH 4/4] Fixes
---
.../src/components/login/AuthRoutes.js | 2 +-
.../src/components/login/ResetTempPassword.js | 15 ++++++++++++---
Tombolo/server/routes/authRoutes.js | 2 +-
3 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/Tombolo/client-reactjs/src/components/login/AuthRoutes.js b/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
index e53361f2..49fd6459 100644
--- a/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
+++ b/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
@@ -34,7 +34,7 @@ const AuthRoutes = () => {
{/* reset password with temp password from owner/admin registration */}
-
+
{/* redirect all other routes hit to login */}
diff --git a/Tombolo/client-reactjs/src/components/login/ResetTempPassword.js b/Tombolo/client-reactjs/src/components/login/ResetTempPassword.js
index ed5eb879..71e9d5d8 100644
--- a/Tombolo/client-reactjs/src/components/login/ResetTempPassword.js
+++ b/Tombolo/client-reactjs/src/components/login/ResetTempPassword.js
@@ -36,9 +36,18 @@ function ResetTempPassword() {
values.token = resetToken;
const result = await resetTempPassword(values);
- // Save user token to local storage
- setUser(JSON.stringify(result.data));
- window.location.href = '/';
+ if (result?.data) {
+ let user = result.data;
+
+ //set isAuthenticated to true so application loads
+ user.isAuthenticated = true;
+
+ // Save user token to local storage
+ setUser(JSON.stringify(user));
+ window.location.href = '/';
+ } else {
+ message.error(result.message);
+ }
} catch (err) {
message.error(err.message);
} finally {
diff --git a/Tombolo/server/routes/authRoutes.js b/Tombolo/server/routes/authRoutes.js
index 92644c70..80873a94 100644
--- a/Tombolo/server/routes/authRoutes.js
+++ b/Tombolo/server/routes/authRoutes.js
@@ -51,7 +51,7 @@ router.post(
resetPasswordWithToken
); // Reset Password - Self Requested
router.post(
- "/resetPasswordWithToken",
+ "/resetTempPassword",
validateResetPasswordPayload,
resetTempPassword
); // Reset Password - Owner/Admin requested through registration flow