fix: render tpa pipeline error from django messages on mfe (#829)
VAN-1339 Co-authored-by: Syed Sajjad Hussain Shah <syed.sajjad@H7FKF7K6XD.local>
This commit is contained in:
committed by
GitHub
parent
a2ab6c196a
commit
9d487d7b61
@@ -20,6 +20,7 @@ import { getTpaHint, getTpaProvider, updatePathWithQueryParams } from '../data/u
|
||||
import { LoginPage } from '../login';
|
||||
import { RegistrationPage } from '../register';
|
||||
import { backupRegistrationForm } from '../register/data/actions';
|
||||
import { clearThirdPartyAuthContextErrorMessage } from './data/actions';
|
||||
import {
|
||||
tpaProvidersSelector,
|
||||
} from './data/selectors';
|
||||
@@ -56,6 +57,7 @@ const Logistration = (props) => {
|
||||
|
||||
const handleOnSelect = (tabKey) => {
|
||||
sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
|
||||
props.clearThirdPartyAuthContextErrorMessage();
|
||||
if (tabKey === LOGIN_PAGE) {
|
||||
props.backupRegistrationForm();
|
||||
}
|
||||
@@ -135,6 +137,7 @@ const Logistration = (props) => {
|
||||
Logistration.propTypes = {
|
||||
selectedPage: PropTypes.string,
|
||||
backupRegistrationForm: PropTypes.func.isRequired,
|
||||
clearThirdPartyAuthContextErrorMessage: PropTypes.func.isRequired,
|
||||
tpaProviders: PropTypes.shape({
|
||||
providers: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
@@ -160,5 +163,6 @@ export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
backupRegistrationForm,
|
||||
clearThirdPartyAuthContextErrorMessage,
|
||||
},
|
||||
)(Logistration);
|
||||
|
||||
@@ -21,9 +21,6 @@ const RedirectLogistration = (props) => {
|
||||
let finalRedirectUrl = '';
|
||||
|
||||
if (success) {
|
||||
// After successful registeration remove the tpaHintedAuthentication flag from local storage if set
|
||||
localStorage.removeItem('tpaHintedAuthentication');
|
||||
|
||||
// If we're in a third party auth pipeline, we must complete the pipeline
|
||||
// once user has successfully logged in. Otherwise, redirect to the specified redirect url.
|
||||
// Note: For multiple enterprise use case, we need to make sure that user first visits the
|
||||
|
||||
@@ -13,13 +13,9 @@ const SocialAuthProviders = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { referrer, socialAuthProviders } = props;
|
||||
|
||||
function handleSubmit(e, skipRegistrationForm) {
|
||||
function handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (skipRegistrationForm) {
|
||||
localStorage.setItem('tpaHintedAuthentication', 'true');
|
||||
}
|
||||
|
||||
const url = e.currentTarget.dataset.providerUrl;
|
||||
window.location.href = getConfig().LMS_BASE_URL + url;
|
||||
}
|
||||
@@ -31,7 +27,7 @@ const SocialAuthProviders = (props) => {
|
||||
type="button"
|
||||
className={`btn-social btn-${provider.id} ${index % 2 === 0 ? 'mr-3' : ''}`}
|
||||
data-provider-url={referrer === LOGIN_PAGE ? provider.loginUrl : provider.registerUrl}
|
||||
onClick={(e) => handleSubmit(e, provider.skipRegistrationForm)}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
{provider.iconImage ? (
|
||||
<div aria-hidden="true">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AsyncActionType } from '../../data/utils';
|
||||
|
||||
export const THIRD_PARTY_AUTH_CONTEXT = new AsyncActionType('THIRD_PARTY_AUTH', 'GET_THIRD_PARTY_AUTH_CONTEXT');
|
||||
export const THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG = 'THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG';
|
||||
|
||||
// Third party auth context
|
||||
export const getThirdPartyAuthContext = (urlParams) => ({
|
||||
@@ -20,3 +21,7 @@ export const getThirdPartyAuthContextSuccess = (fieldDescriptions, optionalField
|
||||
export const getThirdPartyAuthContextFailure = () => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT.FAILURE,
|
||||
});
|
||||
|
||||
export const clearThirdPartyAuthContextErrorMessage = () => ({
|
||||
type: THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { COMPLETE_STATE, PENDING_STATE } from '../../data/constants';
|
||||
import { THIRD_PARTY_AUTH_CONTEXT } from './actions';
|
||||
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from './actions';
|
||||
|
||||
export const defaultState = {
|
||||
fieldDescriptions: {},
|
||||
@@ -12,6 +12,7 @@ export const defaultState = {
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
pipelineUserDetails: null,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -36,6 +37,15 @@ const reducer = (state = defaultState, action = {}) => {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
};
|
||||
case THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG:
|
||||
return {
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...state.thirdPartyAuthContext,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { THIRD_PARTY_AUTH_CONTEXT } from '../actions';
|
||||
import { PENDING_STATE } from '../../../data/constants';
|
||||
import { THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG } from '../actions';
|
||||
import reducer from '../reducers';
|
||||
|
||||
describe('common components reducer', () => {
|
||||
@@ -14,6 +15,7 @@ describe('common components reducer', () => {
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
pipelineUserDetails: null,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
const fieldDescriptions = {
|
||||
@@ -43,4 +45,38 @@ describe('common components reducer', () => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should clear tpa context error message', () => {
|
||||
const state = {
|
||||
fieldDescriptions: {},
|
||||
optionalFields: {},
|
||||
thirdPartyAuthApiStatus: null,
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
countryCode: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
pipelineUserDetails: null,
|
||||
errorMessage: 'An error occured',
|
||||
},
|
||||
};
|
||||
|
||||
const action = {
|
||||
type: THIRD_PARTY_AUTH_CONTEXT_CLEAR_ERROR_MSG,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(state, action),
|
||||
).toEqual(
|
||||
{
|
||||
...state,
|
||||
thirdPartyAuthApiStatus: PENDING_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...state.thirdPartyAuthContext,
|
||||
errorMessage: null,
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import configureStore from 'redux-mock-store';
|
||||
|
||||
import { COMPLETE_STATE, LOGIN_PAGE } from '../../data/constants';
|
||||
import { backupRegistrationForm } from '../../register/data/actions';
|
||||
import { clearThirdPartyAuthContextErrorMessage } from '../data/actions';
|
||||
import { RenderInstitutionButton } from '../InstitutionLogistration';
|
||||
import Logistration from '../Logistration';
|
||||
|
||||
@@ -245,14 +246,13 @@ describe('Logistration', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(backupRegistrationForm());
|
||||
});
|
||||
|
||||
it('should remove tpaHintedAuthentication from localStorage on registeration success', () => {
|
||||
localStorage.setItem('tpaHintedAuthentication', 'true');
|
||||
mergeConfig({
|
||||
ALLOW_PUBLIC_ACCOUNT_CREATION: true,
|
||||
});
|
||||
it('should clear tpa context errorMessage tab click', () => {
|
||||
store = mockStore({
|
||||
login: {
|
||||
loginResult: { success: false, redirectUrl: '' },
|
||||
},
|
||||
register: {
|
||||
registrationResult: { success: true, redirectUrl: '' },
|
||||
registrationResult: { success: false, redirectUrl: '' },
|
||||
registrationError: {},
|
||||
},
|
||||
commonComponents: {
|
||||
@@ -262,8 +262,10 @@ describe('Logistration', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
mount(reduxWrapper(<IntlLogistration />));
|
||||
|
||||
expect(localStorage.getItem('tpaHintedAuthentication')).toEqual(null);
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration />));
|
||||
logistration.find('a[data-rb-event-key="/login"]').simulate('click');
|
||||
expect(store.dispatch).toHaveBeenCalledWith(clearThirdPartyAuthContextErrorMessage());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { mount } from 'enzyme';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
import registerIcons from '../RegisterFaIcons';
|
||||
@@ -75,46 +74,4 @@ describe('SocialAuthProviders', () => {
|
||||
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should set tpaHintedAuthentication in localStorage if skipRegistrationForm is true in provider', () => {
|
||||
localStorage.clear();
|
||||
props = {
|
||||
socialAuthProviders: [{
|
||||
...appleProvider,
|
||||
iconClass: 'default',
|
||||
iconImage: null,
|
||||
skipRegistrationForm: true,
|
||||
}],
|
||||
};
|
||||
|
||||
const tree = mount(
|
||||
<IntlProvider locale="en">
|
||||
<SocialAuthProviders {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
tree.find(`button#${appleProvider.id}`).simulate('click');
|
||||
expect(localStorage.getItem('tpaHintedAuthentication')).toEqual('true');
|
||||
});
|
||||
|
||||
it('should not set tpaHintedAuthentication in localStorage if skipRegistrationForm is false in provider', () => {
|
||||
localStorage.clear();
|
||||
props = {
|
||||
socialAuthProviders: [{
|
||||
...appleProvider,
|
||||
iconClass: 'default',
|
||||
iconImage: null,
|
||||
skipRegistrationForm: false,
|
||||
}],
|
||||
};
|
||||
|
||||
const tree = mount(
|
||||
<IntlProvider locale="en">
|
||||
<SocialAuthProviders {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
tree.find(`button#${appleProvider.id}`).simulate('click');
|
||||
expect(localStorage.getItem('tpaHintedAuthentication')).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,12 +20,14 @@ import {
|
||||
NON_COMPLIANT_PASSWORD_EXCEPTION,
|
||||
NUDGE_PASSWORD_CHANGE,
|
||||
REQUIRE_PASSWORD_CHANGE,
|
||||
TPA_AUTHENTICATION_FAILURE,
|
||||
} from './data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const LoginFailureMessage = (props) => {
|
||||
const { formatMessage } = useIntl();
|
||||
const { context, errorCode } = props.loginError;
|
||||
|
||||
const authService = getAuthService();
|
||||
let errorList;
|
||||
let resetLink = (
|
||||
@@ -165,6 +167,16 @@ const LoginFailureMessage = (props) => {
|
||||
);
|
||||
case REQUIRE_PASSWORD_CHANGE:
|
||||
return <ChangePasswordPrompt />;
|
||||
case TPA_AUTHENTICATION_FAILURE:
|
||||
errorList = (
|
||||
<p>{formatMessage(messages['login.tpa.authentication.failure'], {
|
||||
platform_name: getConfig().SITE_NAME,
|
||||
lineBreak: <br />,
|
||||
errorMessage: context.errorMessage,
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
break;
|
||||
case INTERNAL_SERVER_ERROR:
|
||||
default:
|
||||
errorList = <p>{formatMessage(messages['internal.server.error.message'])}</p>;
|
||||
@@ -183,6 +195,7 @@ LoginFailureMessage.defaultProps = {
|
||||
loginError: {
|
||||
redirectUrl: null,
|
||||
errorCode: null,
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -196,6 +209,7 @@ LoginFailureMessage.propTypes = {
|
||||
allowedDomain: PropTypes.string,
|
||||
remainingAttempts: PropTypes.number,
|
||||
failureCount: PropTypes.number,
|
||||
errorMessage: PropTypes.string,
|
||||
}),
|
||||
email: PropTypes.string,
|
||||
errorCode: PropTypes.string,
|
||||
|
||||
@@ -37,7 +37,7 @@ import AccountActivationMessage from './AccountActivationMessage';
|
||||
import {
|
||||
loginRemovePasswordResetBanner, loginRequest, loginRequestFailure, loginRequestReset, setLoginFormData,
|
||||
} from './data/actions';
|
||||
import { INVALID_FORM } from './data/constants';
|
||||
import { INVALID_FORM, TPA_AUTHENTICATION_FAILURE } from './data/constants';
|
||||
import { loginErrorSelector, loginFormDataSelector, loginRequestSelector } from './data/selectors';
|
||||
import LoginFailureMessage from './LoginFailure';
|
||||
import messages from './messages';
|
||||
@@ -223,7 +223,13 @@ class LoginPage extends React.Component {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const tpaAuthenticationError = {};
|
||||
if (thirdPartyAuthContext.errorMessage) {
|
||||
tpaAuthenticationError.context = {
|
||||
errorMessage: thirdPartyAuthContext.errorMessage,
|
||||
};
|
||||
tpaAuthenticationError.errorCode = TPA_AUTHENTICATION_FAILURE;
|
||||
}
|
||||
if (this.props.loginResult.success) {
|
||||
setSurveyCookie('login');
|
||||
|
||||
@@ -253,6 +259,7 @@ class LoginPage extends React.Component {
|
||||
platformName={thirdPartyAuthContext.platformName}
|
||||
/>
|
||||
{this.props.loginError ? <LoginFailureMessage loginError={this.props.loginError} /> : null}
|
||||
{thirdPartyAuthContext.errorMessage ? <LoginFailureMessage loginError={tpaAuthenticationError} /> : null}
|
||||
{submitState === DEFAULT_STATE && this.state.isSubmitted ? windowScrollTo({ left: 0, top: 0, behavior: 'smooth' }) : null}
|
||||
{activationMsgType && <AccountActivationMessage messageType={activationMsgType} />}
|
||||
{this.props.resetPassword && !this.props.loginError ? <ResetPasswordSuccess /> : null}
|
||||
@@ -361,6 +368,7 @@ LoginPage.defaultProps = {
|
||||
thirdPartyAuthApiStatus: 'pending',
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
errorMessage: null,
|
||||
finishAuthUrl: null,
|
||||
providers: [],
|
||||
secondaryProviders: [],
|
||||
@@ -395,6 +403,7 @@ LoginPage.propTypes = {
|
||||
thirdPartyAuthApiStatus: PropTypes.string,
|
||||
thirdPartyAuthContext: PropTypes.shape({
|
||||
currentProvider: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
platformName: PropTypes.string,
|
||||
providers: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
|
||||
@@ -10,6 +10,7 @@ export const INCORRECT_EMAIL_PASSWORD = 'incorrect-email-or-password';
|
||||
export const NUDGE_PASSWORD_CHANGE = 'nudge-password-change';
|
||||
export const REQUIRE_PASSWORD_CHANGE = 'require-password-change';
|
||||
export const ALLOWED_DOMAIN_LOGIN_ERROR = 'allowed-domain-login-error';
|
||||
export const TPA_AUTHENTICATION_FAILURE = 'tpa-authentication-failure';
|
||||
|
||||
// Account Activation Message
|
||||
export const ACCOUNT_ACTIVATION_MESSAGE = {
|
||||
|
||||
@@ -209,6 +209,13 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Reset your password',
|
||||
description: 'Button to redirect users to Reset Password page',
|
||||
},
|
||||
'login.tpa.authentication.failure': {
|
||||
id: 'login.tpa.authentication.failure',
|
||||
defaultMessage: 'We are sorry, you are not authorized to access {platform_name} via this channel. '
|
||||
+ 'Please contact your learning administrator or manager in order to access {platform_name}.'
|
||||
+ '{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}',
|
||||
description: 'Error message third party authentication pipeline fails',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
NON_COMPLIANT_PASSWORD_EXCEPTION,
|
||||
NUDGE_PASSWORD_CHANGE,
|
||||
REQUIRE_PASSWORD_CHANGE,
|
||||
TPA_AUTHENTICATION_FAILURE,
|
||||
} from '../data/constants';
|
||||
import LoginFailureMessage from '../LoginFailure';
|
||||
|
||||
@@ -219,6 +220,28 @@ describe('LoginFailureMessage', () => {
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match tpa authentication failed error message', () => {
|
||||
props = {
|
||||
loginError: {
|
||||
errorCode: TPA_AUTHENTICATION_FAILURE,
|
||||
context: {
|
||||
errorMessage: 'An error occured',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const loginFailureMessage = mount(
|
||||
<IntlProvider locale="en">
|
||||
<IntlLoginFailureMessage {...props} />
|
||||
</IntlProvider>,
|
||||
);
|
||||
|
||||
const expectedMessageSubstring = 'We are sorry, you are not authorized to access';
|
||||
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toContain(expectedMessageSubstring);
|
||||
expect(loginFailureMessage.find('#login-failure-alert').first().text()).toContain('An error occured');
|
||||
});
|
||||
|
||||
it('should show modal that nudges users to change password', () => {
|
||||
props = {
|
||||
loginError: {
|
||||
|
||||
@@ -441,6 +441,23 @@ describe('LoginPage', () => {
|
||||
expect(loginPage.find('#tpa-alert').find('p').text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should show tpa authentication fails error message', () => {
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
currentProvider: null,
|
||||
errorMessage: 'An error occured',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
|
||||
expect(loginPage.find('#login-failure-alert').find('p').text()).toContain('An error occured');
|
||||
});
|
||||
|
||||
it('should match invalid login form error message', () => {
|
||||
const errorMessage = 'Please fill in the fields below.';
|
||||
store = mockStore({
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
import { Error } from '@edx/paragon/icons';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { windowScrollTo } from '../data/utils';
|
||||
import { FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR, TPA_SESSION_EXPIRED } from './data/constants';
|
||||
import {
|
||||
FORBIDDEN_REQUEST,
|
||||
INTERNAL_SERVER_ERROR,
|
||||
TPA_AUTHENTICATION_FAILURE,
|
||||
TPA_SESSION_EXPIRED,
|
||||
} from './data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
const RegistrationFailureMessage = (props) => {
|
||||
@@ -31,6 +37,14 @@ const RegistrationFailureMessage = (props) => {
|
||||
case FORBIDDEN_REQUEST:
|
||||
errorMessage = formatMessage(messages['registration.rate.limit.error']);
|
||||
break;
|
||||
case TPA_AUTHENTICATION_FAILURE:
|
||||
errorMessage = formatMessage(messages['registration.tpa.authentication.failure'],
|
||||
{
|
||||
platform_name: getConfig().SITE_NAME,
|
||||
lineBreak: <br />,
|
||||
errorMessage: context.errorMessage,
|
||||
});
|
||||
break;
|
||||
case TPA_SESSION_EXPIRED:
|
||||
errorMessage = formatMessage(messages['registration.tpa.session.expired'], { provider: context.provider });
|
||||
break;
|
||||
@@ -48,12 +62,15 @@ const RegistrationFailureMessage = (props) => {
|
||||
};
|
||||
|
||||
RegistrationFailureMessage.defaultProps = {
|
||||
context: {},
|
||||
context: {
|
||||
errorMessage: null,
|
||||
},
|
||||
};
|
||||
|
||||
RegistrationFailureMessage.propTypes = {
|
||||
context: PropTypes.shape({
|
||||
provider: PropTypes.string,
|
||||
errorMessage: PropTypes.string,
|
||||
}),
|
||||
errorCode: PropTypes.string.isRequired,
|
||||
failureCount: PropTypes.number.isRequired,
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from '../common-components/data/selectors';
|
||||
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
||||
import {
|
||||
COMPLETE_STATE,
|
||||
DEFAULT_STATE, INVALID_NAME_REGEX, LETTER_REGEX, NUMBER_REGEX, PENDING_STATE, REGISTER_PAGE, VALID_EMAIL_REGEX,
|
||||
} from '../data/constants';
|
||||
import {
|
||||
@@ -41,10 +42,11 @@ import {
|
||||
COUNTRY_DISPLAY_KEY,
|
||||
FIELDS,
|
||||
FORM_SUBMISSION_ERROR,
|
||||
TPA_AUTHENTICATION_FAILURE,
|
||||
} from './data/constants';
|
||||
import { registrationErrorSelector, validationsSelector } from './data/selectors';
|
||||
import {
|
||||
getSuggestionForInvalidEmail, isTpaHintedAuthentication, validateCountryField, validateEmailAddress,
|
||||
getSuggestionForInvalidEmail, validateCountryField, validateEmailAddress,
|
||||
} from './data/utils';
|
||||
import messages from './messages';
|
||||
import RegistrationFailure from './RegistrationFailure';
|
||||
@@ -95,7 +97,7 @@ const RegistrationPage = (props) => {
|
||||
const [configurableFormFields, setConfigurableFormFields] = useState({ ...backedUpFormData.configurableFormFields });
|
||||
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
|
||||
const [emailSuggestion, setEmailSuggestion] = useState({ ...backedUpFormData.emailSuggestion });
|
||||
const [autoSubmitRegisterForm, setAutoSubmitRegisterForm] = useState(isTpaHintedAuthentication());
|
||||
const [autoSubmitRegisterForm, setAutoSubmitRegisterForm] = useState(false);
|
||||
const [errorCode, setErrorCode] = useState({ type: '', count: 0 });
|
||||
const [formStartTime, setFormStartTime] = useState(null);
|
||||
const [focusedField, setFocusedField] = useState(null);
|
||||
@@ -127,13 +129,13 @@ const RegistrationPage = (props) => {
|
||||
* Set the userPipelineDetails data in formFields for only first time
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (!userPipelineDataLoaded) {
|
||||
if (!userPipelineDataLoaded && thirdPartyAuthApiStatus === COMPLETE_STATE) {
|
||||
const { autoSubmitRegForm, pipelineUserDetails, errorMessage } = thirdPartyAuthContext;
|
||||
if (errorMessage) {
|
||||
localStorage.removeItem('tpaHintedAuthentication');
|
||||
setAutoSubmitRegisterForm(false);
|
||||
setErrorCode(prevState => ({ type: TPA_AUTHENTICATION_FAILURE, count: prevState.count + 1 }));
|
||||
} else if (autoSubmitRegForm) {
|
||||
checkTOSandHonorCodeFields();
|
||||
setAutoSubmitRegisterForm(true);
|
||||
}
|
||||
if (pipelineUserDetails && Object.keys(pipelineUserDetails).length !== 0) {
|
||||
const { name = '', username = '', email = '' } = pipelineUserDetails;
|
||||
@@ -538,7 +540,7 @@ const RegistrationPage = (props) => {
|
||||
<RegistrationFailure
|
||||
errorCode={errorCode.type}
|
||||
failureCount={errorCode.count}
|
||||
context={{ provider: currentProvider }}
|
||||
context={{ provider: currentProvider, errorMessage: thirdPartyAuthContext.errorMessage }}
|
||||
/>
|
||||
<Form id="registration-form" name="registration-form">
|
||||
<FormGroup
|
||||
|
||||
@@ -9,6 +9,7 @@ export const FIELDS = {
|
||||
export const FORBIDDEN_REQUEST = 'forbidden-request';
|
||||
export const FORM_SUBMISSION_ERROR = 'form-submission-error';
|
||||
export const INTERNAL_SERVER_ERROR = 'internal-server-error';
|
||||
export const TPA_AUTHENTICATION_FAILURE = 'tpa-authentication-failure';
|
||||
export const TPA_SESSION_EXPIRED = 'tpa-session-expired';
|
||||
|
||||
export const YEAR_OF_BIRTH_OPTIONS = (() => {
|
||||
|
||||
@@ -111,5 +111,3 @@ export function validateCountryField(value, countryList, errorMessage) {
|
||||
}
|
||||
return { error, countryCode, displayValue };
|
||||
}
|
||||
|
||||
export const isTpaHintedAuthentication = () => localStorage.getItem('tpaHintedAuthentication') === 'true';
|
||||
|
||||
@@ -162,6 +162,13 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Registration using {provider} has timed out.',
|
||||
description: '',
|
||||
},
|
||||
'registration.tpa.authentication.failure': {
|
||||
id: 'registration.tpa.authentication.failure',
|
||||
defaultMessage: 'We are sorry, you are not authorized to access {platform_name} via this channel. '
|
||||
+ 'Please contact your learning administrator or manager in order to access {platform_name}.'
|
||||
+ '{lineBreak}{lineBreak}Error Details:{lineBreak}{errorMessage}',
|
||||
description: 'Error message third party authentication pipeline fails',
|
||||
},
|
||||
// Terms of Service and Honor Code
|
||||
'terms.of.service.and.honor.code': {
|
||||
id: 'terms.of.service.and.honor.code',
|
||||
|
||||
@@ -22,9 +22,8 @@ import {
|
||||
setUserPipelineDataLoaded,
|
||||
} from '../data/actions';
|
||||
import {
|
||||
FIELDS, FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR, TPA_SESSION_EXPIRED,
|
||||
FIELDS, FORBIDDEN_REQUEST, INTERNAL_SERVER_ERROR, TPA_AUTHENTICATION_FAILURE, TPA_SESSION_EXPIRED,
|
||||
} from '../data/constants';
|
||||
import * as utils from '../data/utils';
|
||||
import RegistrationFailureMessage from '../RegistrationFailure';
|
||||
import RegistrationPage from '../RegistrationPage';
|
||||
|
||||
@@ -482,6 +481,21 @@ describe('RegistrationPage', () => {
|
||||
expect(registrationPage.find('div.alert').first().text()).toEqual(expectedMessage);
|
||||
});
|
||||
|
||||
it('should match tpa authentication failed error message', () => {
|
||||
const expectedMessageSubstring = 'We are sorry, you are not authorized to access';
|
||||
props = {
|
||||
context: {
|
||||
provider: 'Google',
|
||||
},
|
||||
errorCode: TPA_AUTHENTICATION_FAILURE,
|
||||
failureCount: 0,
|
||||
};
|
||||
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationFailure {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toContain(expectedMessageSubstring);
|
||||
});
|
||||
|
||||
// ******** test form buttons and fields ********
|
||||
|
||||
it('should match default button state', () => {
|
||||
@@ -876,6 +890,7 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
pipelineUserDetails: {
|
||||
@@ -1134,8 +1149,15 @@ describe('RegistrationPage', () => {
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: { // setting register to display form for testing TOS and honor code value.
|
||||
...initialState.register,
|
||||
registrationError: {
|
||||
errorCode: 'register-error',
|
||||
},
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
pipelineUserDetails: {
|
||||
@@ -1164,17 +1186,6 @@ describe('RegistrationPage', () => {
|
||||
expect(registrationPage.find('input#honor-code').props().value).toEqual(true);
|
||||
});
|
||||
|
||||
it('should set autoSubmitRegisterForm true if isTpaHintedAuthentication returns true', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
utils.isTpaHintedAuthentication = jest.fn().mockImplementation(() => true);
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(registrationPage.find('#tpa-spinner').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should show spinner instead of form while registering if autoSubmitRegForm is true', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
@@ -1188,6 +1199,7 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
currentProvider: ssoProvider.name,
|
||||
@@ -1220,6 +1232,7 @@ describe('RegistrationPage', () => {
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
currentProvider: ssoProvider.name,
|
||||
@@ -1234,7 +1247,36 @@ describe('RegistrationPage', () => {
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(registrationPage.find('#tpa-spinner').exists()).toBeFalsy();
|
||||
expect(registrationPage.find('#registration-form').exists()).toBeTruthy();
|
||||
expect(localStorage.getItem('tpaHintedAuthentication')).toEqual(null);
|
||||
});
|
||||
|
||||
it('should display errorMessage if third party authentication fails', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
getLocale.mockImplementation(() => ('en-us'));
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
register: {
|
||||
...initialState.register,
|
||||
backendCountryCode: 'PK',
|
||||
userPipelineDataLoaded: false,
|
||||
},
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
thirdPartyAuthContext: {
|
||||
...initialState.commonComponents.thirdPartyAuthContext,
|
||||
currentProvider: null,
|
||||
pipelineUserDetails: {},
|
||||
errorMessage: 'An error occured',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
|
||||
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
expect(registrationPage.find('div.alert-heading').length).toEqual(1);
|
||||
expect(registrationPage.find('div.alert').first().text()).toContain('An error occured');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user