diff --git a/src/login/LoginFailure.jsx b/src/login/LoginFailure.jsx index 08cd02f8..06cdb1f1 100644 --- a/src/login/LoginFailure.jsx +++ b/src/login/LoginFailure.jsx @@ -23,6 +23,13 @@ const LoginFailureMessage = (props) => { const { context, errorCode, value } = props.loginError; let errorList; let link; + const resetLink = ( + <> + + {intl.formatMessage(messages['login.incorrect.credentials.error.reset.link.text'])} + + + ); switch (errorCode) { case NON_COMPLIANT_PASSWORD_EXCEPTION: { @@ -87,60 +94,52 @@ const LoginFailureMessage = (props) => { ); break; case FAILED_LOGIN_ATTEMPT: { - const resetLink = ( - - {intl.formatMessage(messages['login.failed.link.text'])} - - ); errorList = ( - <> -
  • - {intl.formatMessage(messages['login.incorrect.credentials.error'])} -
  • -
  • - {intl.formatMessage(messages['login.failed.attempt.error'], { remainingAttempts: context.remainingAttempts })} -
  • -
  • - -
  • - + , + remainingAttempts: {context.remainingAttempts}, + warning: Warning: , + allowedFailureAttempts: {context.allowedFailureAttempts} consecutive, + resetLink, + }} + /> ); break; } case ACCOUNT_LOCKED_OUT: { - const resetLink = ( - - {intl.formatMessage(messages['login.failed.link.text'])} - - ); errorList = ( - <> -
  • - {intl.formatMessage(messages['login.locked.out.error.message'], { lockedOutPeriod: context.lockedOutPeriod })} -
  • -
  • - -
  • - + , + resetLink, + blockText:

    Your account {props.loginError.email} is blocked for 30 minutes due to reaching the maximum {context.allowedFailureAttempts} failed login attempts.

    , + }} + /> ); break; } case INCORRECT_EMAIL_PASSWORD: - errorList = ( -
  • - {intl.formatMessage(messages['login.incorrect.credentials.error'])} -
  • - ); + if (context.failureCount <= 1) { + errorList = intl.formatMessage(messages['login.incorrect.credentials.error']); + } else if (context.failureCount === 2) { + errorList = ( + + ); + } break; default: // TODO: use errorCode instead of processing error messages on frontend @@ -167,7 +166,7 @@ const LoginFailureMessage = (props) => { } return ( - + {intl.formatMessage(messages['login.failure.header.title'])} { errorList } diff --git a/src/login/messages.jsx b/src/login/messages.jsx index 900abf8c..ff546bab 100644 --- a/src/login/messages.jsx +++ b/src/login/messages.jsx @@ -178,16 +178,16 @@ const messages = defineMessages({ defaultMessage: 'contact {platformName} support', description: 'Link text used in inactive user error message to go to learner help center', }, - 'login.failed.link.text': { - id: 'login.failed.link.text', - defaultMessage: 'here', - description: 'Link text used in failed login attempt user error message to reset password', - }, 'login.incorrect.credentials.error': { id: 'login.incorrect.credentials.error', - defaultMessage: 'Email or password is incorrect.', + defaultMessage: 'The username, email or password you entered is incorrect. Please try again.', description: 'Error message for incorrect email or password', }, + 'login.incorrect.credentials.error.with.reset.link': { + id: 'login.incorrect.credentials.error.with.reset.link', + defaultMessage: 'The username, email or password you entered is incorrect. Please try again or {resetLink}.', + description: 'Error message for incorrect email or password with reset link', + }, 'login.failed.attempt.error': { id: 'login.failed.attempt.error', defaultMessage: 'You have {remainingAttempts} more sign in attempts before your account is temporarily locked.', @@ -203,6 +203,11 @@ const messages = defineMessages({ defaultMessage: 'Please fill in the fields below.', description: 'Login form empty input user message', }, + 'login.incorrect.credentials.error.reset.link.text': { + id: 'login.incorrect.credentials.error.reset.link.text', + defaultMessage: 'reset your password', + description: 'Reset password link text for incorrect email or password credentials', + }, }); export default messages; diff --git a/src/login/tests/LoginFailure.test.jsx b/src/login/tests/LoginFailure.test.jsx index 1690aa27..bbae064c 100644 --- a/src/login/tests/LoginFailure.test.jsx +++ b/src/login/tests/LoginFailure.test.jsx @@ -10,6 +10,8 @@ import { INTERNAL_SERVER_ERROR, INVALID_FORM, NON_COMPLIANT_PASSWORD_EXCEPTION, + FAILED_LOGIN_ATTEMPT, + INCORRECT_EMAIL_PASSWORD, } from '../data/constants'; const IntlLoginFailureMessage = injectIntl(LoginFailureMessage); @@ -64,6 +66,74 @@ describe('LoginFailureMessage', () => { expect(loginFailureMessage.find('#login-failure-alert').find('a').props().href).toEqual('https://support.edx.org/'); }); + it('test match failed login attempt error', () => { + props = { + loginError: { + email: 'text@example.com', + errorCode: FAILED_LOGIN_ATTEMPT, + context: { + remainingAttempts: 3, + allowedFailureAttempts: 6, + resetLink: '/reset', + }, + }, + }; + + const loginFailureMessage = mount( + + + , + ); + const expectedMessage = 'We couldn\'t sign you in.The username, email or password you entered is incorrect. Please try again or ' + + 'reset your password Attempts remaining: 3 Warning: After 6 consecutive unsuccessful login attempts, your account will be locked.'; + + expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); + }); + + it('test match failed login error first attempt', () => { + props = { + loginError: { + email: 'text@example.com', + errorCode: INCORRECT_EMAIL_PASSWORD, + context: { + failureCount: 1, + resetLink: '/reset', + }, + }, + }; + + const loginFailureMessage = mount( + + + , + ); + const expectedMessage = 'We couldn\'t sign you in.The username, email or password you entered is incorrect. Please try again.'; + + expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); + }); + + it('test match failed login error second attempt', () => { + props = { + loginError: { + email: 'text@example.com', + errorCode: INCORRECT_EMAIL_PASSWORD, + context: { + failureCount: 2, + resetLink: '/reset', + }, + }, + }; + + const loginFailureMessage = mount( + + + , + ); + const expectedMessage = 'We couldn\'t sign you in.The username, email or password you entered is incorrect. Please try again or reset your password.'; + + expect(loginFailureMessage.find('#login-failure-alert').first().text()).toEqual(expectedMessage); + }); + it('should match rate limit error message', () => { props = { loginError: {