diff --git a/src/register/RegistrationFailure.jsx b/src/register/RegistrationFailure.jsx
index 59b22a1c..13eb6b02 100644
--- a/src/register/RegistrationFailure.jsx
+++ b/src/register/RegistrationFailure.jsx
@@ -1,10 +1,12 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
+
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
-import { INTERNAL_SERVER_ERROR } from '../login/data/constants';
-import { DEFAULT_STATE, PENDING_STATE } from '../data/constants';
+
+import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from './data/constants';
import messages from './messages';
+import { DEFAULT_STATE, PENDING_STATE } from '../data/constants';
const RegistrationFailureMessage = (props) => {
const errorMessage = props.errors;
@@ -27,7 +29,15 @@ const RegistrationFailureMessage = (props) => {
);
userErrors.push(serverError);
break;
-
+ case FORBIDDEN_REQUEST:
+ userErrors.push(
+ (
+
+ {props.intl.formatMessage(messages['register.rate.limit.reached.message'])}
+
+ ),
+ );
+ break;
default:
Object.keys(errorMessage).forEach((key) => {
if (key !== 'error_code') {
diff --git a/src/register/data/constants.js b/src/register/data/constants.js
index 33c05fa6..f6d923c7 100644
--- a/src/register/data/constants.js
+++ b/src/register/data/constants.js
@@ -1,3 +1,7 @@
+// Registration Error Codes
+export const INTERNAL_SERVER_ERROR = 'internal-server-error';
+export const FORBIDDEN_REQUEST = 'forbidden-request';
+
export const YEAR_OF_BIRTH_OPTIONS = (() => {
const currentYear = new Date().getFullYear();
const years = [];
diff --git a/src/register/data/sagas.js b/src/register/data/sagas.js
index 624898d1..271e4c3a 100644
--- a/src/register/data/sagas.js
+++ b/src/register/data/sagas.js
@@ -1,5 +1,6 @@
import { call, put, takeEvery } from 'redux-saga/effects';
+import { camelCaseObject } from '@edx/frontend-platform';
import { logError, logInfo } from '@edx/frontend-platform/logging';
// Actions
@@ -13,10 +14,10 @@ import {
fetchRealtimeValidationsSuccess,
fetchRealtimeValidationsFailure,
} from './actions';
+import { INTERNAL_SERVER_ERROR } from './constants';
// Services
import { getFieldsValidations, registerRequest } from './service';
-import { INTERNAL_SERVER_ERROR } from '../../login/data/constants';
export function* handleNewUserRegistration(action) {
try {
@@ -29,10 +30,13 @@ export function* handleNewUserRegistration(action) {
success,
));
} catch (e) {
- const statusCodes = [400, 409, 403];
+ const statusCodes = [400, 409];
if (e.response && statusCodes.includes(e.response.status)) {
yield put(registerNewUserFailure(e.response.data));
logInfo(e);
+ } else if (e.response.status === 403) {
+ yield put(registerNewUserFailure(camelCaseObject(e.response.data)));
+ logInfo(e);
} else {
yield put(registerNewUserFailure({ errorCode: INTERNAL_SERVER_ERROR }));
logError(e);
diff --git a/src/register/data/tests/sagas.test.js b/src/register/data/tests/sagas.test.js
index 2b5cd2ab..541923db 100644
--- a/src/register/data/tests/sagas.test.js
+++ b/src/register/data/tests/sagas.test.js
@@ -8,7 +8,7 @@ import {
} from '../sagas';
import * as api from '../service';
import initializeMockLogging from '../../../setupTest';
-import { INTERNAL_SERVER_ERROR } from '../../../data/constants';
+import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../constants';
const { loggingService } = initializeMockLogging();
@@ -176,7 +176,7 @@ describe('handleNewUserRegistration', () => {
});
it('should call service and dispatch error action', async () => {
- const loginErrorResponse = {
+ const registerErrorResponse = {
response: {
status: 400,
data: {
@@ -185,7 +185,7 @@ describe('handleNewUserRegistration', () => {
},
};
const registerRequest = jest.spyOn(api, 'registerRequest')
- .mockImplementation(() => Promise.reject(loginErrorResponse));
+ .mockImplementation(() => Promise.reject(registerErrorResponse));
const dispatched = [];
await runSaga(
@@ -198,7 +198,36 @@ describe('handleNewUserRegistration', () => {
expect(loggingService.logInfo).toHaveBeenCalled();
expect(dispatched).toEqual([
actions.registerNewUserBegin(),
- actions.registerNewUserFailure(loginErrorResponse.response.data),
+ actions.registerNewUserFailure(registerErrorResponse.response.data),
+ ]);
+ registerRequest.mockClear();
+ });
+
+ it('should handle rate limit error code', async () => {
+ const registerErrorResponse = {
+ response: {
+ status: 403,
+ data: {
+ errorCode: FORBIDDEN_REQUEST,
+ },
+ },
+ };
+
+ const registerRequest = jest.spyOn(api, 'registerRequest')
+ .mockImplementation(() => Promise.reject(registerErrorResponse));
+
+ const dispatched = [];
+ await runSaga(
+ { dispatch: (action) => dispatched.push(action) },
+ handleNewUserRegistration,
+ params,
+ );
+
+ expect(registerRequest).toHaveBeenCalledTimes(1);
+ expect(loggingService.logInfo).toHaveBeenCalled();
+ expect(dispatched).toEqual([
+ actions.registerNewUserBegin(),
+ actions.registerNewUserFailure(registerErrorResponse.response.data),
]);
registerRequest.mockClear();
});
diff --git a/src/register/messages.jsx b/src/register/messages.jsx
index 17194d2c..25156129 100644
--- a/src/register/messages.jsx
+++ b/src/register/messages.jsx
@@ -51,6 +51,11 @@ const messages = defineMessages({
defaultMessage: 'Email (required)',
description: 'Label that appears above email field on register page',
},
+ 'register.rate.limit.reached.message': {
+ id: 'register.rate.limit.reached.message',
+ defaultMessage: 'Too many failed registration attempts. Try again later.',
+ description: 'Error message that appears when an anonymous user has made too many failed registration attempts',
+ },
'email.validation.message': {
id: 'email.validation.message',
defaultMessage: 'Please enter your email.',
diff --git a/src/register/tests/RegistrationPage.test.jsx b/src/register/tests/RegistrationPage.test.jsx
index 64bb94a7..89f98099 100644
--- a/src/register/tests/RegistrationPage.test.jsx
+++ b/src/register/tests/RegistrationPage.test.jsx
@@ -12,8 +12,8 @@ import RegistrationPage from '../RegistrationPage';
import { RenderInstitutionButton } from '../../common-components';
import RegistrationFailureMessage from '../RegistrationFailure';
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
-import { INTERNAL_SERVER_ERROR } from '../../login/data/constants';
import { fetchRealtimeValidations, registerNewUser } from '../data/actions';
+import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR } from '../data/constants';
jest.mock('@edx/frontend-platform/analytics');
@@ -434,6 +434,19 @@ describe('RegistrationPageTests', () => {
expect(tree.toJSON()).toMatchSnapshot();
});
+ it('should match registration api rate limit error message', () => {
+ props = {
+ errors: {
+ errorCode: FORBIDDEN_REQUEST,
+ },
+ };
+
+ const registrationPage = mount(reduxWrapper());
+ expect(registrationPage.find('div.alert-heading').length).toEqual(1);
+ const expectedMessage = 'We couldn\'t create your account.Too many failed registration attempts. Try again later.';
+ expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage);
+ });
+
it('should match internal server error message', () => {
props = {
errors: {