-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[ECUK In-App 3DS] Prevent unwanted validate code resend and display rate limit errors in MFA flow #86320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ECUK In-App 3DS] Prevent unwanted validate code resend and display rate limit errors in MFA flow #86320
Changes from all commits
3355ed1
410354a
f097c47
732b15c
5eae9ae
5176127
d7141fd
3270a12
e572629
7fe4188
b30d0b4
b02ebf8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ import MagicCodeInput from '@components/MagicCodeInput'; | |
| import type {MagicCodeInputHandle} from '@components/MagicCodeInput'; | ||
| import {DefaultCancelConfirmModal} from '@components/MultifactorAuthentication/components/Modals'; | ||
| import {useMultifactorAuthentication, useMultifactorAuthenticationActions, useMultifactorAuthenticationState} from '@components/MultifactorAuthentication/Context'; | ||
| import addMFABreadcrumb from '@components/MultifactorAuthentication/observability/breadcrumbs'; | ||
| import MultifactorAuthenticationValidateCodeResendButton from '@components/MultifactorAuthentication/ValidateCodeResendButton'; | ||
| import type {MultifactorAuthenticationValidateCodeResendButtonHandle} from '@components/MultifactorAuthentication/ValidateCodeResendButton'; | ||
| import ScreenWrapper from '@components/ScreenWrapper'; | ||
|
|
@@ -17,12 +18,12 @@ import useNetwork from '@hooks/useNetwork'; | |
| import useOnyx from '@hooks/useOnyx'; | ||
| import useThemeStyles from '@hooks/useThemeStyles'; | ||
| import AccountUtils from '@libs/AccountUtils'; | ||
| import {getLatestErrorMessage} from '@libs/ErrorUtils'; | ||
| import {getLatestErrorField, getLatestErrorMessage} from '@libs/ErrorUtils'; | ||
| import VALUES from '@libs/MultifactorAuthentication/VALUES'; | ||
| import {isValidValidateCode} from '@libs/ValidationUtils'; | ||
| import Navigation from '@navigation/Navigation'; | ||
| import {clearAccountMessages} from '@userActions/Session'; | ||
| import {resendValidateCode} from '@userActions/User'; | ||
| import {clearValidateCodeActionError, requestValidateCodeAction} from '@userActions/User'; | ||
| import CONST from '@src/CONST'; | ||
| import type {TranslationPaths} from '@src/languages/types'; | ||
| import ONYXKEYS from '@src/ONYXKEYS'; | ||
|
|
@@ -40,7 +41,7 @@ function MultifactorAuthenticationValidateCodePage() { | |
| // Onyx data | ||
| const [account] = useOnyx(ONYXKEYS.ACCOUNT); | ||
| const [session] = useOnyx(ONYXKEYS.SESSION); | ||
|
|
||
| const [validateActionCode] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); | ||
| const contactMethod = account?.primaryLogin ?? ''; | ||
|
|
||
| // Local state | ||
|
|
@@ -62,9 +63,25 @@ function MultifactorAuthenticationValidateCodePage() { | |
| // Derived state | ||
| const hasAccountError = !!account && !isEmptyObject(account?.errors); | ||
| const hasContinuableError = !!continuableError; | ||
| const hasError = hasAccountError || hasContinuableError; | ||
| const isValidateCodeFormSubmitting = AccountUtils.isValidateCodeFormSubmitting(account); | ||
| const shouldDisableResendCode = isOffline ?? account?.isLoading; | ||
| const validateCodeActionError = getLatestErrorField(validateActionCode, 'actionVerified'); | ||
| const hasValidateCodeActionError = !isEmptyObject(validateCodeActionError); | ||
| const hasError = hasAccountError || hasContinuableError || hasValidateCodeActionError; | ||
| const errorMessage = getErrorMessage(); | ||
|
|
||
| function getErrorMessage() { | ||
| // Rate limit or other backend error when sending/resending the validate code | ||
| if (hasValidateCodeActionError) { | ||
| return Object.values(validateCodeActionError).at(0); | ||
| } | ||
| // Invalid validate code submitted by the user | ||
| if (hasContinuableError) { | ||
| return translate('validateCodeForm.error.incorrectMagicCode'); | ||
| } | ||
| // Generic account/session error (e.g. stale errors from a previous flow) | ||
| return getLatestErrorMessage(account); | ||
| } | ||
|
|
||
| // Check if this page can handle the continuable error, if not convert to regular error | ||
| useEffect(() => { | ||
|
|
@@ -124,16 +141,18 @@ function MultifactorAuthenticationValidateCodePage() { | |
| clearAccountMessages(); | ||
| } | ||
|
|
||
| // Clear validateCode and continuable error when user starts typing after an error | ||
| // This ensures process() will re-trigger on submit even if user enters the same code | ||
| // Clear continuable error when user starts typing after an error | ||
| if (continuableError) { | ||
| dispatch({type: 'SET_VALIDATE_CODE', payload: undefined}); | ||
| dispatch({type: 'CLEAR_CONTINUABLE_ERROR'}); | ||
| } | ||
|
Comment on lines
+144
to
147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After an Useful? React with 👍 / 👎.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This sounds plausible, but probably not a big deal?
Comment on lines
145
to
147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the user starts typing after an Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once the user enters a valid code, they are immediately redirected; if they enter the same code again, we don't want them to have to confirm twice that it's invalid. |
||
| }; | ||
|
|
||
| const resendValidationCode = () => { | ||
| resendValidateCode(contactMethod); | ||
| if (hasValidateCodeActionError) { | ||
| clearValidateCodeActionError('actionVerified'); | ||
| } | ||
| addMFABreadcrumb('Validate code resend requested'); | ||
| requestValidateCodeAction(); | ||
| inputRef.current?.clear(); | ||
| setInputCode(''); | ||
| setFormError({}); | ||
|
|
@@ -236,8 +255,6 @@ function MultifactorAuthenticationValidateCodePage() { | |
| ref={inputRef} | ||
| maxLength={CONST.MAGIC_CODE_LENGTH} | ||
| /> | ||
| {hasContinuableError && <FormHelpMessage message={translate('validateCodeForm.error.incorrectMagicCode')} />} | ||
| {hasAccountError && <FormHelpMessage message={getLatestErrorMessage(account)} />} | ||
| <MultifactorAuthenticationValidateCodeResendButton | ||
| ref={resendButtonRef} | ||
| shouldDisableResendCode={shouldDisableResendCode} | ||
|
|
@@ -246,15 +263,23 @@ function MultifactorAuthenticationValidateCodePage() { | |
| onResendValidationCode={resendValidationCode} | ||
| /> | ||
| </View> | ||
| <Button | ||
| success | ||
| large | ||
| style={[styles.w100, styles.p5, styles.mtAuto]} | ||
| onPress={validateAndSubmitForm} | ||
| text={translate('common.verify')} | ||
| isLoading={isValidateCodeFormSubmitting} | ||
| isDisabled={isOffline} | ||
| /> | ||
| <View style={[styles.w100, styles.mtAuto]}> | ||
| {!!errorMessage && ( | ||
| <FormHelpMessage | ||
| style={[styles.mh5]} | ||
| message={errorMessage} | ||
| /> | ||
| )} | ||
| <Button | ||
| success | ||
| large | ||
| style={[styles.w100, styles.ph5, styles.pb5, styles.mt4]} | ||
| onPress={validateAndSubmitForm} | ||
| text={translate('common.verify')} | ||
| isLoading={isValidateCodeFormSubmitting} | ||
| isDisabled={isOffline} | ||
| /> | ||
| </View> | ||
| <CancelConfirmModal | ||
| isVisible={isCancelModalVisible} | ||
| onConfirm={cancelFlow} | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.