From e94d05d9e2aba4d36b74c8ca641c4540a8edbc49 Mon Sep 17 00:00:00 2001 From: Waheed Ahmed Date: Thu, 7 May 2020 21:37:28 +0500 Subject: [PATCH] Handle 403 password reset response. Recently we have changed rate limiting configuration for password reset endpoint to one request per email and IP. I have added the frontend functionality to show proper error message to users. PROD-1427 --- src/account-settings/data/reducers.js | 1 + src/account-settings/data/utils/reduxUtils.js | 4 ++++ .../data/utils/reduxUtils.test.js | 1 + .../reset-password/RequestInProgressAlert.jsx | 24 +++++++++++++++++++ .../reset-password/ResetPassword.jsx | 2 ++ .../reset-password/data/actions.js | 4 ++++ .../reset-password/data/reducers.js | 5 ++++ .../reset-password/data/sagas.js | 14 ++++++++--- 8 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 src/account-settings/reset-password/RequestInProgressAlert.jsx diff --git a/src/account-settings/data/reducers.js b/src/account-settings/data/reducers.js index 2e831b1..864b96b 100644 --- a/src/account-settings/data/reducers.js +++ b/src/account-settings/data/reducers.js @@ -196,6 +196,7 @@ const reducer = (state = defaultState, action) => { case RESET_PASSWORD.BEGIN: case RESET_PASSWORD.SUCCESS: + case RESET_PASSWORD.FORBIDDEN: return { ...state, resetPassword: resetPasswordReducer(state.resetPassword, action), diff --git a/src/account-settings/data/utils/reduxUtils.js b/src/account-settings/data/utils/reduxUtils.js index 0a75d19..7cab9e9 100644 --- a/src/account-settings/data/utils/reduxUtils.js +++ b/src/account-settings/data/utils/reduxUtils.js @@ -27,6 +27,10 @@ export class AsyncActionType { get RESET() { return `${this.topic}__${this.name}__RESET`; } + + get FORBIDDEN() { + return `${this.topic}__${this.name}__FORBIDDEN`; + } } /** diff --git a/src/account-settings/data/utils/reduxUtils.test.js b/src/account-settings/data/utils/reduxUtils.test.js index 586a8ba..e07adf4 100644 --- a/src/account-settings/data/utils/reduxUtils.test.js +++ b/src/account-settings/data/utils/reduxUtils.test.js @@ -12,6 +12,7 @@ describe('AsyncActionType', () => { expect(actionType.SUCCESS).toBe('HOUSE_CATS__START_THE_RACE__SUCCESS'); expect(actionType.FAILURE).toBe('HOUSE_CATS__START_THE_RACE__FAILURE'); expect(actionType.RESET).toBe('HOUSE_CATS__START_THE_RACE__RESET'); + expect(actionType.FORBIDDEN).toBe('HOUSE_CATS__START_THE_RACE__FORBIDDEN'); }); }); diff --git a/src/account-settings/reset-password/RequestInProgressAlert.jsx b/src/account-settings/reset-password/RequestInProgressAlert.jsx new file mode 100644 index 0000000..b2dc143 --- /dev/null +++ b/src/account-settings/reset-password/RequestInProgressAlert.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; + +import Alert from '../Alert'; + +const RequestInProgressAlert = (props) => { + + return ( + } + > + + + ); +}; + +export default RequestInProgressAlert; diff --git a/src/account-settings/reset-password/ResetPassword.jsx b/src/account-settings/reset-password/ResetPassword.jsx index aeac863..96eb011 100644 --- a/src/account-settings/reset-password/ResetPassword.jsx +++ b/src/account-settings/reset-password/ResetPassword.jsx @@ -7,6 +7,7 @@ import { StatefulButton } from '@edx/paragon'; import { resetPassword } from './data/actions'; import messages from './messages'; import ConfirmationAlert from './ConfirmationAlert'; +import RequestInProgressAlert from './RequestInProgressAlert'; const ResetPassword = (props) => { const { email, intl, status } = props; @@ -43,6 +44,7 @@ const ResetPassword = (props) => { />

{status === 'complete' ? : null} + {status === 'forbidden' ? : null} ); }; diff --git a/src/account-settings/reset-password/data/actions.js b/src/account-settings/reset-password/data/actions.js index c184792..80360ef 100644 --- a/src/account-settings/reset-password/data/actions.js +++ b/src/account-settings/reset-password/data/actions.js @@ -18,3 +18,7 @@ export const resetPasswordSuccess = () => ({ export const resetPasswordReset = () => ({ type: RESET_PASSWORD.RESET, }); + +export const resetPasswordForbidden = () => ({ + type: RESET_PASSWORD.FORBIDDEN, +}); diff --git a/src/account-settings/reset-password/data/reducers.js b/src/account-settings/reset-password/data/reducers.js index bd89ab7..38f21a1 100644 --- a/src/account-settings/reset-password/data/reducers.js +++ b/src/account-settings/reset-password/data/reducers.js @@ -17,6 +17,11 @@ const reducer = (state = defaultState, action = null) => { ...state, status: 'complete', }; + case RESET_PASSWORD.FORBIDDEN: + return { + ...state, + status: 'forbidden', + }; default: } diff --git a/src/account-settings/reset-password/data/sagas.js b/src/account-settings/reset-password/data/sagas.js index 29fadc6..956f2f9 100644 --- a/src/account-settings/reset-password/data/sagas.js +++ b/src/account-settings/reset-password/data/sagas.js @@ -1,12 +1,20 @@ import { put, call, takeEvery } from 'redux-saga/effects'; -import { resetPasswordBegin, resetPasswordSuccess, RESET_PASSWORD } from './actions'; +import { resetPasswordBegin, resetPasswordForbidden, resetPasswordSuccess, RESET_PASSWORD } from './actions'; import { postResetPassword } from './service'; function* handleResetPassword(action) { yield put(resetPasswordBegin()); - const response = yield call(postResetPassword, action.payload.email); - yield put(resetPasswordSuccess(response)); + try { + const response = yield call(postResetPassword, action.payload.email); + yield put(resetPasswordSuccess(response)); + } catch (error) { + if (error.response && error.response.status === 403) { + yield put(resetPasswordForbidden(error)); + } else { + throw error; + } + } } export default function* saga() {