From 0683d1aced6474cedffe098d8b0ff7c15e718d2e Mon Sep 17 00:00:00 2001 From: Zach H Date: Tue, 23 Nov 2021 02:45:08 -0500 Subject: [PATCH] =?UTF-8?q?Support=20Server=20requests=20for=20MFA,=20Rend?= =?UTF-8?q?er=20failed=20UI=20statuses=20to=20user,=20C=E2=80=A6=20(#4483)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support Server requests for MFA, Render failed UI statuses to user, Connect to KnownHosts component --- .../ResetPasswordDialog.tsx | 2 +- webclient/src/containers/Login/Login.tsx | 33 ++--- .../src/forms/RegisterForm/RegisterForm.tsx | 130 +++++++++--------- .../RequestPasswordResetForm.css | 8 ++ .../RequestPasswordResetForm.tsx | 30 +++- .../ResetPasswordForm/ResetPasswordForm.tsx | 15 +- webclient/src/hooks/useReduxEffect.tsx | 2 +- webclient/src/store/server/server.actions.ts | 12 +- webclient/src/store/server/server.dispatch.ts | 9 ++ webclient/src/store/server/server.types.ts | 5 +- .../src/websocket/commands/SessionCommands.ts | 7 + .../persistence/SessionPersistence.ts | 6 +- 12 files changed, 168 insertions(+), 91 deletions(-) diff --git a/webclient/src/components/ResetPasswordDialog/ResetPasswordDialog.tsx b/webclient/src/components/ResetPasswordDialog/ResetPasswordDialog.tsx index d0bd8f88..59799f30 100644 --- a/webclient/src/components/ResetPasswordDialog/ResetPasswordDialog.tsx +++ b/webclient/src/components/ResetPasswordDialog/ResetPasswordDialog.tsx @@ -27,7 +27,7 @@ const ResetPasswordDialog = ({ classes, handleClose, isOpen, onSubmit }: any) => ) : null} - + ); diff --git a/webclient/src/containers/Login/Login.tsx b/webclient/src/containers/Login/Login.tsx index 8db2480b..313a1656 100644 --- a/webclient/src/containers/Login/Login.tsx +++ b/webclient/src/containers/Login/Login.tsx @@ -1,5 +1,5 @@ // eslint-disable-next-line -import React, { useState } from "react"; +import React, {useState} from "react"; import { connect } from 'react-redux'; import { Redirect } from 'react-router-dom'; import { makeStyles } from '@material-ui/core/styles'; @@ -13,9 +13,9 @@ import { RequestPasswordResetDialog, ResetPasswordDialog } from 'components'; import { LoginForm } from 'forms'; import { useReduxEffect } from 'hooks'; import { Images } from 'images'; -import { RouteEnum } from 'types'; +import { RouteEnum, StatusEnum } from 'types'; import { ServerSelectors, ServerTypes } from 'store'; - +import { SessionCommands } from 'websocket'; import './Login.css'; const useStyles = makeStyles(theme => ({ @@ -60,13 +60,18 @@ const Login = ({ state, description }: LoginProps) => { const isConnected = AuthenticationService.isConnected(state); const [dialogState, setDialogState] = useState({ - openRequest: false, - openReset: false + passwordResetRequestDialog: false, + resetPasswordDialog: false }); useReduxEffect(() => { + closeRequestPasswordResetDialog(); openResetPasswordDialog(); - }, ServerTypes.RESET_PASSWORD, []); + }, ServerTypes.RESET_PASSWORD_REQUESTED, []); + + useReduxEffect(() => { + closeResetPasswordDialog(); + }, ServerTypes.RESET_PASSWORD_SUCCESS, []); const showDescription = () => { return !isConnected && description?.length; @@ -82,30 +87,26 @@ const Login = ({ state, description }: LoginProps) => { } else { AuthenticationService.resetPasswordRequest({ user, host, port } as any); } - - closeRequestPasswordResetDialog(); }; const handleResetPasswordDialogSubmit = async ({ user, token, newPassword, passwordAgain, host, port }) => { AuthenticationService.resetPassword({ user, token, newPassword, host, port } as any); - - closeResetPasswordDialog(); }; const closeRequestPasswordResetDialog = () => { - setDialogState(s => ({ ...s, openRequest: false })); + setDialogState(s => ({ ...s, passwordResetRequestDialog: false })); } const openRequestPasswordResetDialog = () => { - setDialogState(s => ({ ...s, openRequest: true })); + setDialogState(s => ({ ...s, passwordResetRequestDialog: true })); } const closeResetPasswordDialog = () => { - setDialogState(s => ({ ...s, openReset: false })); + setDialogState(s => ({ ...s, resetPasswordDialog: false })); } const openResetPasswordDialog = () => { - setDialogState(s => ({ ...s, openReset: true })); + setDialogState(s => ({ ...s, resetPasswordDialog: true })); } return ( @@ -184,13 +185,13 @@ const Login = ({ state, description }: LoginProps) => { diff --git a/webclient/src/forms/RegisterForm/RegisterForm.tsx b/webclient/src/forms/RegisterForm/RegisterForm.tsx index 7e8b1163..7e743d20 100644 --- a/webclient/src/forms/RegisterForm/RegisterForm.tsx +++ b/webclient/src/forms/RegisterForm/RegisterForm.tsx @@ -1,66 +1,66 @@ -// eslint-disable-next-line +// eslint-disable-next-line import React, { Component } from 'react'; -import { connect } from 'react-redux'; -import { Form, Field, reduxForm, change } from 'redux-form' - -import Button from '@material-ui/core/Button'; - -import { InputField, KnownHosts } from 'components'; -import { FormKey } from 'types'; - -import './RegisterForm.css'; - -const RegisterForm = (props) => { - const { dispatch, handleSubmit } = props; - - const onHostChange = ({ host, port }) => { - dispatch(change(FormKey.REGISTER, 'host', host)); - dispatch(change(FormKey.REGISTER, 'port', port)); - } - return ( -
-
-
- - { /* Padding is off */ } -
-
- -
-
- -
-
- -
-
-
-
- -
-
- -
-
- -
- -
- -
- ); -}; - -const propsMap = { - form: FormKey.REGISTER, -}; - -const mapStateToProps = () => ({ - initialValues: { - - } -}); - -export default connect(mapStateToProps)(reduxForm(propsMap)(RegisterForm)); +import { connect } from 'react-redux'; +import { Form, Field, reduxForm, change } from 'redux-form' + +import Button from '@material-ui/core/Button'; + +import { InputField, KnownHosts } from 'components'; +import { FormKey } from 'types'; + +import './RegisterForm.css'; + +const RegisterForm = (props) => { + const { dispatch, handleSubmit } = props; + + const onHostChange = ({ host, port }) => { + dispatch(change(FormKey.REGISTER, 'host', host)); + dispatch(change(FormKey.REGISTER, 'port', port)); + } + return ( +
+
+
+ + { /* Padding is off */ } +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+ +
+ +
+ ); +}; + +const propsMap = { + form: FormKey.REGISTER, +}; + +const mapStateToProps = () => ({ + initialValues: { + + } +}); + +export default connect(mapStateToProps)(reduxForm(propsMap)(RegisterForm)); diff --git a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.css b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.css index 3087a3bf..bfc66182 100644 --- a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.css +++ b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.css @@ -2,6 +2,14 @@ width: 100%; } +.RequestPasswordResetForm-MFA-Message { + margin-top: -20px; +} + +.RequestPasswordResetForm-Error { + margin-bottom: 10px; +} + .RequestPasswordResetForm-item { margin-bottom: 20px; } diff --git a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx index 0e7386ef..4be854e8 100644 --- a/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx +++ b/webclient/src/forms/RequestPasswordResetForm/RequestPasswordResetForm.tsx @@ -1,5 +1,5 @@ // eslint-disable-next-line -import React from "react"; +import React, { useState } from "react"; import { connect } from 'react-redux'; import { Form, Field, reduxForm, change } from 'redux-form' @@ -9,21 +9,47 @@ import { InputField, KnownHosts } from 'components'; import { FormKey } from 'types'; import './RequestPasswordResetForm.css'; +import { useReduxEffect } from 'hooks'; +import { ServerTypes } from 'store'; const RequestPasswordResetForm = (props) => { const { dispatch, handleSubmit } = props; + const [errorMessage, setErrorMessage] = useState(false); + const [isMFA, setIsMFA] = useState(false); const onHostChange = ({ host, port }) => { dispatch(change(FormKey.RESET_PASSWORD_REQUEST, 'host', host)); dispatch(change(FormKey.RESET_PASSWORD_REQUEST, 'port', port)); } + useReduxEffect(() => { + setErrorMessage(true); + }, ServerTypes.RESET_PASSWORD_FAILED, []); + + useReduxEffect(() => { + setIsMFA(true); + }, ServerTypes.RESET_PASSWORD_CHALLENGE, []); + + const onSubmit = (event) => { + setErrorMessage(false); + handleSubmit(event); + } + return ( -
+
+ {errorMessage ? ( +
Request Password Reset Failed, please try again
+ ) : null}
+ {isMFA ? ( +
+
Server has multi-factor authentication enabled
+ +
+ ) : null}
diff --git a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx index 8d7853f3..d5842c76 100644 --- a/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx +++ b/webclient/src/forms/ResetPasswordForm/ResetPasswordForm.tsx @@ -1,5 +1,5 @@ // eslint-disable-next-line -import React from "react"; +import React, {useState} from "react"; import { connect } from 'react-redux'; import { Form, Field, reduxForm, change } from 'redux-form' @@ -9,18 +9,31 @@ import { InputField, KnownHosts } from 'components'; import { FormKey } from 'types'; import './ResetPasswordForm.css'; +import { useReduxEffect } from '../../hooks'; +import { ServerTypes } from '../../store'; const ResetPasswordForm = (props) => { const { dispatch, handleSubmit } = props; + const [errorMessage, setErrorMessage] = useState(false); + + const onHostChange = ({ host, port }) => { dispatch(change(FormKey.RESET_PASSWORD, 'host', host)); dispatch(change(FormKey.RESET_PASSWORD, 'port', port)); } + useReduxEffect(() => { + setErrorMessage(true); + }, ServerTypes.RESET_PASSWORD_FAILED, []); + + return (
+ {errorMessage ? ( +

Password Reset Failed, please try again

+ ) : null}
diff --git a/webclient/src/hooks/useReduxEffect.tsx b/webclient/src/hooks/useReduxEffect.tsx index 6d74f620..3bab83e4 100644 --- a/webclient/src/hooks/useReduxEffect.tsx +++ b/webclient/src/hooks/useReduxEffect.tsx @@ -1,4 +1,4 @@ -/** +/** File is adapted from https://github.com/Qeepsake/use-redux-effect under MIT License * @author Aspect Apps Limited * @description diff --git a/webclient/src/store/server/server.actions.ts b/webclient/src/store/server/server.actions.ts index 3aabc90c..e758ad37 100644 --- a/webclient/src/store/server/server.actions.ts +++ b/webclient/src/store/server/server.actions.ts @@ -1,3 +1,4 @@ +import { Type } from 'protobufjs'; import { Types } from './server.types'; export const Actions = { @@ -68,6 +69,15 @@ export const Actions = { type: Types.CLEAR_LOGS }), resetPassword: () => ({ - type: Types.RESET_PASSWORD + type: Types.RESET_PASSWORD_REQUESTED + }), + resetPasswordFailed: () => ({ + type: Types.RESET_PASSWORD_FAILED + }), + resetPasswordChallenge: () => ({ + type: Types.RESET_PASSWORD_CHALLENGE + }), + resetPasswordSuccess: () => ({ + type: Types.RESET_PASSWORD_SUCCESS }) } diff --git a/webclient/src/store/server/server.dispatch.ts b/webclient/src/store/server/server.dispatch.ts index fd54d3d2..46e23782 100644 --- a/webclient/src/store/server/server.dispatch.ts +++ b/webclient/src/store/server/server.dispatch.ts @@ -64,5 +64,14 @@ export const Dispatch = { }, resetPassword: () => { store.dispatch(Actions.resetPassword()); + }, + resetPasswordFailed: () => { + store.dispatch(Actions.resetPasswordFailed()); + }, + resetPasswordChallenge: () => { + store.dispatch(Actions.resetPasswordChallenge()); + }, + resetPasswordSuccess: () => { + store.dispatch(Actions.resetPasswordSuccess()); } } diff --git a/webclient/src/store/server/server.types.ts b/webclient/src/store/server/server.types.ts index a223f190..8e23f03e 100644 --- a/webclient/src/store/server/server.types.ts +++ b/webclient/src/store/server/server.types.ts @@ -16,5 +16,8 @@ export const Types = { USER_LEFT: '[Server] User Left', VIEW_LOGS: '[Server] View Logs', CLEAR_LOGS: '[Server] Clear Logs', - RESET_PASSWORD: '[Server] Reset Password' + RESET_PASSWORD_REQUESTED: '[Server] Reset Password Requested', + RESET_PASSWORD_FAILED: '[Server] Reset Password Failed', + RESET_PASSWORD_CHALLENGE: '[Server] Reset Password Challenge', + RESET_PASSWORD_SUCCESS: '[Server] Reset Password Success' }; diff --git a/webclient/src/websocket/commands/SessionCommands.ts b/webclient/src/websocket/commands/SessionCommands.ts index ad5b25a1..b9e04c57 100644 --- a/webclient/src/websocket/commands/SessionCommands.ts +++ b/webclient/src/websocket/commands/SessionCommands.ts @@ -268,11 +268,14 @@ export class SessionCommands { const resp = raw['.Response_ForgotPasswordRequest.ext']; if (resp.challengeEmail) { + SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Requesting MFA information'); SessionPersistence.resetPasswordChallenge(); } else { + SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Password reset in progress'); SessionPersistence.resetPassword(); } } else { + SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Password reset failed, please try again'); SessionPersistence.resetPasswordFailed(); } @@ -298,8 +301,10 @@ export class SessionCommands { webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) { + SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Password reset in progress'); SessionPersistence.resetPassword(); } else { + SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Password reset failed, please try again'); SessionPersistence.resetPasswordFailed(); } @@ -326,8 +331,10 @@ export class SessionCommands { webClient.protobuf.sendSessionCommand(sc, raw => { if (raw.responseCode === webClient.protobuf.controller.Response.ResponseCode.RespOk) { + SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Password successfully updated'); SessionPersistence.resetPasswordSuccess(); } else { + SessionCommands.updateStatus(StatusEnum.DISCONNECTED, 'Password update failed, please try again'); SessionPersistence.resetPasswordFailed(); } diff --git a/webclient/src/websocket/persistence/SessionPersistence.ts b/webclient/src/websocket/persistence/SessionPersistence.ts index 90d5fc15..c9cdbdef 100644 --- a/webclient/src/websocket/persistence/SessionPersistence.ts +++ b/webclient/src/websocket/persistence/SessionPersistence.ts @@ -82,7 +82,7 @@ export class SessionPersistence { } static resetPasswordChallenge() { - console.log('Open Modal asking for Email address associated with account'); + ServerDispatch.resetPasswordChallenge(); } static resetPassword() { @@ -90,10 +90,10 @@ export class SessionPersistence { } static resetPasswordSuccess() { - console.log('User password successfully changed Alert!'); + ServerDispatch.resetPasswordSuccess(); } static resetPasswordFailed() { - console.log('Open Alert telling user their password request failed for some reason'); + ServerDispatch.resetPasswordFailed(); } }