Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export {

export default {
// Allowed methods are hardcoded here; keep in sync with allowedAuthenticationMethods in useNavigateTo3DSAuthorizationChallenge.
allowedAuthenticationMethods: [CONST.MULTIFACTOR_AUTHENTICATION.TYPE.BIOMETRICS],
allowedAuthenticationMethods: [CONST.MULTIFACTOR_AUTHENTICATION.TYPE.BIOMETRICS, CONST.MULTIFACTOR_AUTHENTICATION.TYPE.PASSKEYS],
action: authorizeTransaction,

// AuthorizeTransaction's callback navigates to the outcome screen, but if it knows the user is going to see an error outcome, we explicitly deny the transaction to make sure the user can't re-approve it on another device
Expand Down
16 changes: 15 additions & 1 deletion src/libs/actions/MultifactorAuthentication/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,21 @@ async function denyTransaction({transactionID}: DenyTransactionParams) {

/** Attempt to deny the transaction without handling errors or waiting for a response. We use this to clean up after something unexpected happened trying to authorize or deny a challenge */
async function fireAndForgetDenyTransaction({transactionID}: DenyTransactionParams) {
makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.DENY_TRANSACTION, {transactionID}, {});
makeRequestWithSideEffects(
SIDE_EFFECT_REQUEST_COMMANDS.DENY_TRANSACTION,
{transactionID},
{
optimisticData: [
{
key: ONYXKEYS.LOCALLY_PROCESSED_3DS_TRANSACTION_REVIEWS,
onyxMethod: Onyx.METHOD.MERGE,
value: {
[transactionID]: CONST.MULTIFACTOR_AUTHENTICATION.LOCALLY_PROCESSED_TRANSACTION_ACTION.DENY,
},
},
],
},
);
}

function markHasAcceptedSoftPrompt() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {SeverityLevel} from '@sentry/react-native';
import * as Sentry from '@sentry/react-native';
import React, {useState} from 'react';
import React, {useCallback, useRef, useState} from 'react';
import {View} from 'react-native';
import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
Expand All @@ -13,6 +13,7 @@ import {
} from '@components/MultifactorAuthentication/config/scenarios/AuthorizeTransaction';
import {useMultifactorAuthentication} from '@components/MultifactorAuthentication/Context';
import ScreenWrapper from '@components/ScreenWrapper';
import useBeforeRemove from '@hooks/useBeforeRemove';
import useLocalize from '@hooks/useLocalize';
import useNetworkWithOfflineStatus from '@hooks/useNetworkWithOfflineStatus';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -57,24 +58,40 @@ function MultifactorAuthenticationScenarioAuthorizeTransactionPage({route}: Mult
const {executeScenario} = useMultifactorAuthentication();

const [isConfirmModalVisible, setConfirmModalVisibility] = useState(false);
const allowNavigatingAwayRef = useRef(false);

const showConfirmModal = () => {
const showConfirmModal = useCallback(() => {
// FullPageOfflineBlockingView doesn't wrap HeaderWithBackButton, so we handle navigation manually when offline.
// Offline mode isn't supported in MFA; navigate users away immediately without showing the confirmation modal.
if (isOffline) {
addBreadcrumb('Offline back-navigation (no deny sent)', {transactionID}, 'warning');
allowNavigatingAwayRef.current = true;
Navigation.closeRHPFlow();
return;
}
setConfirmModalVisibility(true);
};
}, [isOffline, transactionID]);

const hideConfirmModal = () => {
setConfirmModalVisibility(false);
};

const onBeforeRemove: Parameters<typeof useBeforeRemove>[0] = useCallback(
(e) => {
if (allowNavigatingAwayRef.current) {
return;
}
e.preventDefault();
showConfirmModal();
},
[showConfirmModal],
);

useBeforeRemove(onBeforeRemove, !!transaction && !denyOutcomeScreen);

const onApproveTransaction = () => {
addBreadcrumb('Approve tapped', {transactionID});
allowNavigatingAwayRef.current = true;
executeScenario(CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.AUTHORIZE_TRANSACTION, {
transactionID,
});
Expand All @@ -96,6 +113,8 @@ function MultifactorAuthenticationScenarioAuthorizeTransactionPage({route}: Mult
const onSilentlyDenyTransaction = () => {
addBreadcrumb('Silent deny (user canceled flow)', {transactionID}, 'warning');
fireAndForgetDenyTransaction({transactionID});
setConfirmModalVisibility(false);
allowNavigatingAwayRef.current = true;
Navigation.closeRHPFlow();
};

Expand Down
Loading