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() {