diff --git a/Tombolo/client-reactjs/src/components/login/AuthRoutes.js b/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
index b95123be..49fd6459 100644
--- a/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
+++ b/Tombolo/client-reactjs/src/components/login/AuthRoutes.js
@@ -3,9 +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('./ResetTempPw'));
+const resetTempPassword = React.lazy(() => import('./ResetTempPassword.js'));
const AuthRoutes = () => {
//if traditional login isn't enabled, redirect user to login page
@@ -30,9 +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/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/ResetPasswordWithToken.js
similarity index 73%
rename from Tombolo/client-reactjs/src/components/login/ResetPassword.js
rename to Tombolo/client-reactjs/src/components/login/ResetPasswordWithToken.js
index 28a89867..0f6ec064 100644
--- a/Tombolo/client-reactjs/src/components/login/ResetPassword.js
+++ b/Tombolo/client-reactjs/src/components/login/ResetPasswordWithToken.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/resetPasswordWithToken';
+ 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/client-reactjs/src/components/login/ResetTempPw.jsx b/Tombolo/client-reactjs/src/components/login/ResetTempPassword.js
similarity index 92%
rename from Tombolo/client-reactjs/src/components/login/ResetTempPw.jsx
rename to Tombolo/client-reactjs/src/components/login/ResetTempPassword.js
index ed5eb879..71e9d5d8 100644
--- a/Tombolo/client-reactjs/src/components/login/ResetTempPw.jsx
+++ 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/client-reactjs/src/components/login/utils.js b/Tombolo/client-reactjs/src/components/login/utils.js
index 916d2908..3070c635 100644
--- a/Tombolo/client-reactjs/src/components/login/utils.js
+++ b/Tombolo/client-reactjs/src/components/login/utils.js
@@ -22,7 +22,7 @@ export const getDeviceInfo = () => {
return { os, browser: browserName };
};
-// Make a request to the server to reset the temporary password
+// Make a request to the server to reset the temporary password - OWNER REGISTRATION
export const resetTempPassword = async (resetData) => {
const payload = {
method: 'POST',
diff --git a/Tombolo/server/controllers/authController.js b/Tombolo/server/controllers/authController.js
index 5e3e006d..c67b8264 100644
--- a/Tombolo/server/controllers/authController.js
+++ b/Tombolo/server/controllers/authController.js
@@ -280,10 +280,10 @@ const verifyEmail = async (req, res) => {
}
};
-//Reset Temp password
-const resetTempPassword = async (req, res) => {
+//Reset Password With Token - Self Requested
+const resetPasswordWithToken = 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,9 +308,106 @@ 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);
+ 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 });
+ }
+};
+
+//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
@@ -328,6 +425,16 @@ const resetTempPassword = async (req, res) => {
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();
@@ -508,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
@@ -549,7 +656,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 +671,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 +683,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,65 +703,15 @@ const handlePasswordResetRequest = async (req, res) => {
expiresAt: new Date(new Date().getTime() + 24 * 60 * 60 * 1000),
});
- // response
- res.status(200).json({ success: true });
- } catch (err) {
- logger.error(`Reset password: ${err.message}`);
- res
- .status(err.status || 500)
- .json({ success: false, message: err.message });
- }
-};
-
-// 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 },
+ // 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, message: "Password updated successfully" });
+ res.status(200).json({ success: true });
} catch (err) {
logger.error(`Reset password: ${err.message}`);
res
@@ -809,11 +879,11 @@ const loginOrRegisterAzureUser = async (req, res, next) => {
module.exports = {
createBasicUser,
verifyEmail,
+ resetPasswordWithToken,
resetTempPassword,
loginBasicUser,
logOutBasicUser,
handlePasswordResetRequest,
- resetPassword,
createApplicationOwner,
loginOrRegisterAzureUser,
};
diff --git a/Tombolo/server/routes/authRoutes.js b/Tombolo/server/routes/authRoutes.js
index 2a3bcc53..80873a94 100644
--- a/Tombolo/server/routes/authRoutes.js
+++ b/Tombolo/server/routes/authRoutes.js
@@ -18,8 +18,8 @@ const {
loginBasicUser,
logOutBasicUser,
handlePasswordResetRequest,
- resetPassword,
createApplicationOwner,
+ resetPasswordWithToken,
resetTempPassword,
verifyEmail,
loginOrRegisterAzureUser,
@@ -45,12 +45,16 @@ router.post(
validatePasswordResetRequestPayload,
handlePasswordResetRequest
); // Reset password
-router.post("/resetPassword", validateResetPasswordPayload, resetPassword); // Reset password
+router.post(
+ "/resetPasswordWithToken",
+ validateResetPasswordPayload,
+ resetPasswordWithToken
+); // Reset Password - Self Requested
router.post(
"/resetTempPassword",
validateResetPasswordPayload,
resetTempPassword
-); // Complete registration by resetting temporary password
+); // Reset Password - Owner/Admin requested through registration flow
router.post("/verifyEmail", verifyEmail); // Verify email
// TODO - Forgot password route