From 926a69259a663b3cffdef10617121f52047cc2af Mon Sep 17 00:00:00 2001 From: stevepodell Date: Sun, 2 Mar 2025 13:07:50 -0800 Subject: [PATCH] Reset password works when signed in or not signed in The race conditions and hanging tab (which was due to a strange bug if you use a
with out a submit in React). There are still some clean up items to do: 1) Create person when an admin, without requiring a password verification -- but in any case a password verfication will still be required on the first login by the new staff person. 2) Escaping out of the creation/verification/reset process without completing all the steps 3) Staying signed in after a password reset This is still an ongoing development ... only bother merging for now, if you have merges you want to commit. --- .eslintrc | 1 + src/js/common/utils/logging.js | 2 +- src/js/components/Login/ResetYourPassword.jsx | 92 ++++++---- src/js/components/Navigation/HeaderBar.jsx | 26 ++- src/js/components/VerifySecretCodeModal.jsx | 5 +- src/js/contexts/ConnectAppContext.jsx | 7 +- src/js/pages/Login.jsx | 163 ++++++++++++------ src/js/pages/Teams.jsx | 2 +- src/js/react-query/mutations.jsx | 31 ++-- 9 files changed, 215 insertions(+), 114 deletions(-) diff --git a/.eslintrc b/.eslintrc index 11936e2..3d4feaf 100644 --- a/.eslintrc +++ b/.eslintrc @@ -56,6 +56,7 @@ "react/destructuring-assignment": 0, // Dec 2018: We should do this! But right now we have 3990 warnings/errors if enabled. "react/forbid-prop-types": 0, // Dec 2018: Should consider someday "react/indent-prop": 0, + "react/jsx-curly-newline": 0, // Feb 2025, started showing up with latest eslint "react/jsx-first-prop-new-line": 0, "react/jsx-indent-props": 0, "react/jsx-no-bind": 1, // Dec 2018: Should these be errors? diff --git a/src/js/common/utils/logging.js b/src/js/common/utils/logging.js index 31dc669..14cdad3 100644 --- a/src/js/common/utils/logging.js +++ b/src/js/common/utils/logging.js @@ -43,7 +43,7 @@ export function httpLog (text, res) { } } -// Log oAuth steps +// Log auth steps export function authLog (text, res) { if (webAppConfig.LOG_AUTHENTICATION) { if (res) { diff --git a/src/js/components/Login/ResetYourPassword.jsx b/src/js/components/Login/ResetYourPassword.jsx index 5f52dfd..27d080c 100644 --- a/src/js/components/Login/ResetYourPassword.jsx +++ b/src/js/components/Login/ResetYourPassword.jsx @@ -8,11 +8,11 @@ import TextField from '@mui/material/TextField'; import PropTypes from 'prop-types'; import * as React from 'react'; import { useEffect, useRef, useState } from 'react'; +import styled from 'styled-components'; import validator from 'validator'; import { renderLog } from '../../common/utils/logging'; import { useConnectAppContext } from '../../contexts/ConnectAppContext'; -import makeRequestParams from '../../react-query/makeRequestParams'; -import { useLogoutMutation, usePersonRetrieveByEmailMutation, usePersonSaveForAuthMutation } from '../../react-query/mutations'; +import { useLogoutMutation, usePasswordSaveMutation, usePersonRetrieveByEmailMutation } from '../../react-query/mutations'; import weConnectQueryFn, { METHOD } from '../../react-query/WeConnectQuery'; import { ErrorMessage } from '../Style/sharedStyles'; import VerifySecretCodeModal from '../VerifySecretCodeModal'; @@ -20,7 +20,7 @@ import VerifySecretCodeModal from '../VerifySecretCodeModal'; const ResetYourPassword = ({ openDialog, closeDialog }) => { renderLog('ResetYourPassword'); const { mutate: mutateRetrievePersonByEmail } = usePersonRetrieveByEmailMutation(); - const { mutate: mutatePersonSaveForAuth } = usePersonSaveForAuthMutation(); + const { mutate: mutatePasswordSave } = usePasswordSaveMutation(); const { mutate: mutateLogout } = useLogoutMutation(); const { getAppContextValue, setAppContextValue } = useConnectAppContext(); @@ -28,11 +28,13 @@ const ResetYourPassword = ({ openDialog, closeDialog }) => { const [displayEmailAddress, setDisplayEmailAddress] = useState(true); const [warningLine, setWarningLine] = useState(''); const [errorMessage, setErrorMessage] = useState(''); + const [personId, setPersonId] = useState(''); const emailRef = useRef(''); + const emailDisabledRef = useRef(''); const password1Ref = useRef(''); const password2Ref = useRef(''); - const authPerson = useRef(undefined); + const authPersonRef = useRef(undefined); useEffect(() => { setOpen(openDialog); @@ -43,6 +45,7 @@ const ResetYourPassword = ({ openDialog, closeDialog }) => { if (secretCodeVerified === true) { console.log('received new secretCodeVerifiedForReset', secretCodeVerified); setDisplayEmailAddress(false); + emailDisabledRef.current = authPersonRef.current?.emailPersonal || ''; emailRef.current = ''; password1Ref.current = ''; password2Ref.current = ''; @@ -52,11 +55,12 @@ const ResetYourPassword = ({ openDialog, closeDialog }) => { const auth = getAppContextValue('authenticatedPerson'); useEffect(() => { const authP = getAppContextValue('authenticatedPerson'); + authPersonRef.current = authP; if (authP && open) { console.log('received new authP', authP); - authPerson.current = authP; - console.log('authPerson.personId in Login useEffect [auth] id: ', authP.personId); - console.log('authPerson.personId in Login useEffect [auth] open: ', open); + console.log('authPersonRef.personId in Login useEffect [auth] id: ', authP.personId); + console.log('authPersonRef.personId in Login useEffect [auth] open: ', open); + setPersonId(authP.personId); weConnectQueryFn('send-email-code', { personId: authP.personId }, METHOD.POST) .then(setAppContextValue('openVerifySecretCodeModalDialog', true)); } @@ -64,21 +68,6 @@ const ResetYourPassword = ({ openDialog, closeDialog }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [auth]); - const sendEmail = async () => { - const email = emailRef.current.value; - setAppContextValue('resetEmail', email); - console.log('email in sendEmail: ', emailRef.current.value); - if (!validator.isEmail(email)) { - setWarningLine('Please enter a valid email address.'); - return; - } - setWarningLine(''); - setAppContextValue('openVerifySecretCodeModalDialog', true); - // Logout so that the current sessionID will not be reused when resetting password for a potentially differnt staff member - await mutateLogout(); - // This retrieve will set the 'authenticatedPerson' app context value, and bring back a new sessionID (without touching the cookie) - await mutateRetrievePersonByEmail({ emailPersonal: email }); - }; const handleClose = () => { setOpen(false); @@ -88,23 +77,41 @@ const ResetYourPassword = ({ openDialog, closeDialog }) => { const changePassword = async () => { const pass1 = password1Ref.current.value; const pass2 = password2Ref.current.value; - const person = authPerson.current; + const person = authPersonRef.current; + let id; + if (person?.id) { + id = person.id; + } else { + id = personId; + } - console.log('password in changePassword: ', pass1, pass2); if (pass1 !== pass2) { setErrorMessage('Your password entries do not match.'); } else { setErrorMessage(''); - // const person = getAppContextValue('authenticatedPerson'); - await mutatePersonSaveForAuth(makeRequestParams({ id: person.id }, { password: pass1 })); + await mutatePasswordSave({ personId: id, password: pass1 }); setAppContextValue('isAuthenticated', true); - console.log('ResetYourPassword changePassword pass1, pass2: ', pass1, pass2); + setAppContextValue('authenticatedPerson', person); setAppContextValue('resetPassword', pass1); handleClose(); } }; - console.log('ResetYourPassword incoming authPerson: ', authPerson); + const sendEmail = async () => { + const email = emailRef.current.value; + setAppContextValue('resetEmail', email); + if (!validator.isEmail(email)) { + setWarningLine('Please enter a valid email address.'); + return; + } + setWarningLine(''); + setAppContextValue('openVerifySecretCodeModalDialog', true); + // Logout so that the current sessionID will not be reused when resetting password for a potentially differnt staff member + await mutateLogout(); + // This retrieve will set the 'authenticatedPerson' app context value, and bring back a new sessionID (without touching the cookie) + await mutateRetrievePersonByEmail({ emailPersonal: email }); + }; + return ( <> { name="email" required type="email" - variant="standard" + variant="outlined" /> ) : ( - + <> + {/* EmailDiv clues in Google "Update password?" dialog to display this email, it probably could be css hidden */} + Email:    {emailDisabledRef.current} { margin="dense" name="password1" required - type="password" - variant="standard" + variant="outlined" /> { margin="dense" name="password2" required - type="password" - variant="standard" + variant="outlined" /> - + )}
- + {!isAuthSafe && ( + + )} + {isAdmin && ( + + )}
- {authPerson.current && - Object.keys(authPerson.current).length > 0 && - getAppContextValue('secretCodeVerified') !== true && - getAppContextValue('openVerifySecretCodeModalDialog') && ( - - )} - + {displayVerify && } + {/* This following test can be deleted or converted to an automated test */} {/* */} diff --git a/src/js/pages/Teams.jsx b/src/js/pages/Teams.jsx index 2f273dc..8b9e0c0 100644 --- a/src/js/pages/Teams.jsx +++ b/src/js/pages/Teams.jsx @@ -210,7 +210,7 @@ const Teams = () => { })}
- Sign in + Temporary link to /login page
diff --git a/src/js/react-query/mutations.jsx b/src/js/react-query/mutations.jsx index a8ac99b..922c08b 100644 --- a/src/js/react-query/mutations.jsx +++ b/src/js/react-query/mutations.jsx @@ -1,4 +1,5 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { reactQueryLog } from '../common/utils/logging'; import { useConnectAppContext } from '../contexts/ConnectAppContext'; import weConnectQueryFn, { METHOD } from './WeConnectQuery'; @@ -100,16 +101,6 @@ const usePersonSaveMutation = () => { }); }; -const usePersonSaveForAuthMutation = () => { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: (params) => weConnectQueryFn('person-save', params, METHOD.GET), - onError: (error) => console.log('error in personSaveMutation: ', error), - onSuccess: () => queryClient.invalidateQueries('get-auth'), - }); -}; - const useSaveTaskMutation = () => { const queryClient = useQueryClient(); @@ -134,33 +125,39 @@ const useGetAuthMutation = () => { return useMutation({ mutationFn: () => weConnectQueryFn('get-auth', {}, METHOD.POST), onError: (error) => console.log('error in useGetAuthMutation: ', error), - onSuccess: () => console.log('useGetAuthMutation called to force refresh'), + onSuccess: (auth) => reactQueryLog('useGetAuthMutation called to force refresh', auth), + }); +}; + +const usePasswordSaveMutation = () => { + return useMutation({ + mutationFn: (params) => weConnectQueryFn('save-password', params, METHOD.PUT), + onError: (error) => console.log('error in usePasswordSaveMutation: ', error), + onSuccess: (data, variables, context) => reactQueryLog('usePasswordSaveMutation successful, returning', data, variables, context), }); }; const usePersonRetrieveMutation = () => { - console.log('entry to useGetPersonMutation'); const { setAppContextValue } = useConnectAppContext(); return useMutation({ mutationFn: (params) => weConnectQueryFn('person-retrieve', params, METHOD.GET), onError: (error) => console.log('error in usePersonRetrieveMutation: ', error), onSuccess: (data, variables, context) => { - console.log('usePersonRetrieveMutation successful, returning', data, variables, context); + reactQueryLog('usePersonRetrieveMutation successful, returning', data, variables, context); setAppContextValue('authenticatedPerson', data); }, }); }; const usePersonRetrieveByEmailMutation = () => { - console.log('entry to useGetPersonMutation'); const { setAppContextValue } = useConnectAppContext(); return useMutation({ mutationFn: (params) => weConnectQueryFn('person-retrieve-by-email', params, METHOD.GET), onError: (error) => console.log('error in usePersonRetrieveByEmailMutation: ', error), onSuccess: (data, variables, context) => { - console.log('usePersonRetrieveByEmailMutation successful, returning', data, variables, context); + reactQueryLog('usePersonRetrieveByEmailMutation successful, returning', data, variables, context); setAppContextValue('authenticatedPerson', data); }, }); @@ -169,7 +166,7 @@ const usePersonRetrieveByEmailMutation = () => { export { useRemoveTeamMutation, useRemoveTeamMemberMutation, useAddPersonToTeamMutation, useQuestionnaireSaveMutation, useTaskDefinitionSaveMutation, useGroupSaveMutation, - usePersonAwaySaveMutation, - useQuestionSaveMutation, usePersonSaveMutation, usePersonSaveForAuthMutation, useSaveTaskMutation, useAnswerListSaveMutation, + usePersonAwaySaveMutation, usePasswordSaveMutation, + useQuestionSaveMutation, usePersonSaveMutation, useSaveTaskMutation, useAnswerListSaveMutation, useLogoutMutation, useGetAuthMutation, usePersonRetrieveMutation, usePersonRetrieveByEmailMutation };