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!')); - }, - }, - ]}> - - - -
- ); -} - -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!')); + }, + }, + ]}> + + + +
+ ); +} + +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