Skip to content

Commit

Permalink
feat(suite-native): add DeviceCompromisedModal for FW revision check
Browse files Browse the repository at this point in the history
  • Loading branch information
Lemonexe committed Jan 31, 2025
1 parent 49fd2cb commit 8cae3e0
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 2 deletions.
3 changes: 2 additions & 1 deletion suite-native/app/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ export const prepareTrezorEmulator = async (
// Prepare Trezor device for test scenario
await TrezorUserEnvLink.disconnect();
await TrezorUserEnvLink.connect();
await TrezorUserEnvLink.startEmu({ model: 'T3T1', wipe: true });
// start with latest officially released firmware (necessary to pass the firmware checks)
await TrezorUserEnvLink.startEmu({ model: 'T3T1', version: '2-latest', wipe: true });
await TrezorUserEnvLink.setupEmu({
label: TREZOR_DEVICE_LABEL,
mnemonic: seed,
Expand Down
1 change: 1 addition & 0 deletions suite-native/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"@suite-native/module-accounts-import": "workspace:*",
"@suite-native/module-accounts-management": "workspace:*",
"@suite-native/module-add-accounts": "workspace:*",
"@suite-native/module-authenticity-checks": "workspace:*",
"@suite-native/module-authorize-device": "workspace:*",
"@suite-native/module-connect-popup": "workspace:*",
"@suite-native/module-dev-utils": "workspace:*",
Expand Down
5 changes: 5 additions & 0 deletions suite-native/app/src/navigation/RootStackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
AccountSettingsScreen,
} from '@suite-native/module-accounts-management';
import { AddCoinAccountStackNavigator } from '@suite-native/module-add-accounts';
import { AuthenticityChecksStackNavigator } from '@suite-native/module-authenticity-checks';
import { AuthorizeDeviceStackNavigator } from '@suite-native/module-authorize-device';
import { ConnectPopupScreen } from '@suite-native/module-connect-popup';
import { DevUtilsStackNavigator } from '@suite-native/module-dev-utils';
Expand Down Expand Up @@ -118,6 +119,10 @@ export const RootStackNavigator = () => {
name={RootStackRoutes.SettingsScreenStack}
component={SettingsStackNavigator}
/>
<RootStack.Screen
name={RootStackRoutes.AuthenticityChecksStack}
component={AuthenticityChecksStackNavigator}
/>
</RootStack.Navigator>
);
};
3 changes: 3 additions & 0 deletions suite-native/app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
"path": "../module-accounts-management"
},
{ "path": "../module-add-accounts" },
{
"path": "../module-authenticity-checks"
},
{ "path": "../module-authorize-device" },
{ "path": "../module-connect-popup" },
{ "path": "../module-dev-utils" },
Expand Down
21 changes: 20 additions & 1 deletion suite-native/device/src/hooks/useHandleDeviceConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
selectIsDeviceConnectedAndAuthorized,
selectIsDeviceRemembered,
selectIsDeviceUsingPassphrase,
selectIsFirmwareAuthenticityCheckDismissed,
selectIsNoPhysicalDeviceConnected,
selectIsPortfolioTrackerDevice,
} from '@suite-common/wallet-core';
Expand All @@ -18,6 +19,7 @@ import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex';
import { selectIsFirmwareInstallationRunning } from '@suite-native/firmware';
import {
AppTabsRoutes,
AuthenticityChecksStackRoutes,
AuthorizeDeviceStackParamList,
AuthorizeDeviceStackRoutes,
HomeStackRoutes,
Expand All @@ -27,6 +29,8 @@ import {
} from '@suite-native/navigation';
import { selectIsOnboardingFinished } from '@suite-native/settings';

import { selectHasFirmwareAuthenticityCheckHardFailed } from '../selectors';

type NavigationProp = StackToStackCompositeNavigationProps<
AuthorizeDeviceStackParamList | RootStackParamList,
AuthorizeDeviceStackRoutes.PinMatrix | RootStackRoutes.Onboarding,
Expand All @@ -44,6 +48,14 @@ export const useHandleDeviceConnection = () => {
const { isBiometricsOverlayVisible } = useIsBiometricsOverlayVisible();
const isDeviceUsingPassphrase = useSelector(selectIsDeviceUsingPassphrase);
const isFirmwareInstallationRunning = useSelector(selectIsFirmwareInstallationRunning);

const isFirmwareCheckFailed = useSelector(selectHasFirmwareAuthenticityCheckHardFailed);
const isFirmwareAuthenticityCheckDismissed = useSelector(
selectIsFirmwareAuthenticityCheckDismissed,
);
const shouldNavigateToDeviceCompromisedModal =
isFirmwareCheckFailed && !isFirmwareAuthenticityCheckDismissed;

const navigation = useNavigation<NavigationProp>();
const dispatch = useDispatch();

Expand All @@ -62,7 +74,8 @@ export const useHandleDeviceConnection = () => {
isOnboardingFinished &&
!isPortfolioTrackerDevice &&
!isDeviceConnectedAndAuthorized &&
!isBiometricsOverlayVisible
!isBiometricsOverlayVisible &&
!shouldNavigateToDeviceCompromisedModal
) {
requestPrioritizedDeviceAccess({
deviceCallback: () => dispatch(authorizeDeviceThunk()),
Expand All @@ -76,6 +89,11 @@ export const useHandleDeviceConnection = () => {
});
}
}
if (shouldNavigateToDeviceCompromisedModal) {
navigation.navigate(RootStackRoutes.AuthenticityChecksStack, {
screen: AuthenticityChecksStackRoutes.DeviceCompromisedModal,
});
}
}, [
dispatch,
isDeviceConnected,
Expand All @@ -88,6 +106,7 @@ export const useHandleDeviceConnection = () => {
isDeviceUsingPassphrase,
shouldBlockSendReviewRedirect,
isFirmwareInstallationRunning,
shouldNavigateToDeviceCompromisedModal,
]);

// In case that the physical device is disconnected, redirect to the home screen and
Expand Down
13 changes: 13 additions & 0 deletions suite-native/intl/src/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,19 @@ export const en = {
},
},
},
moduleAuthenticityChecks: {
deviceCompromised: {
title: 'Your device may have been compromised',
subtitle:
'Contact our support to learn what’s going on with your device and what to do next.',
steps: {
avoidUsingDevice: 'Avoid using this device or sending any funds to it.',
contactSupport: 'Continue to Trezor support and use the Chat option.',
},
buttonContactSupport: 'Contact Trezor Support',
buttonClose: 'Close',
},
},
staking: {
stakingDetailScreen: {
title: 'Staking',
Expand Down
23 changes: 23 additions & 0 deletions suite-native/module-authenticity-checks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@suite-native/module-authenticity-checks",
"version": "1.0.0",
"private": true,
"license": "See LICENSE.md in repo root",
"sideEffects": false,
"main": "src/index",
"scripts": {
"depcheck": "yarn g:depcheck",
"lint:js": "yarn g:eslint '**/*.{ts,tsx,js}'",
"type-check": "yarn g:tsc --build"
},
"dependencies": {
"@react-navigation/native": "6.1.18",
"@react-navigation/native-stack": "6.11.0",
"@suite-common/wallet-core": "workspace:*",
"@suite-native/atoms": "workspace:*",
"@suite-native/intl": "workspace:*",
"@suite-native/link": "workspace:*",
"@suite-native/navigation": "workspace:*",
"react-redux": "8.0.7"
}
}
2 changes: 2 additions & 0 deletions suite-native/module-authenticity-checks/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './navigation/AuthenticityChecksStackNavigator';
export * from './screens/DeviceCompromisedModalScreen';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';

import {
AuthenticityChecksStackParamList,
AuthenticityChecksStackRoutes,
stackNavigationOptionsConfig,
} from '@suite-native/navigation';

import { DeviceCompromisedModalScreen } from '../screens/DeviceCompromisedModalScreen';

const AuthenticityChecksStack = createNativeStackNavigator<AuthenticityChecksStackParamList>();

export const AuthenticityChecksStackNavigator = () => (
<AuthenticityChecksStack.Navigator
initialRouteName={AuthenticityChecksStackRoutes.DeviceCompromisedModal}
screenOptions={stackNavigationOptionsConfig}
>
<AuthenticityChecksStack.Screen
name={AuthenticityChecksStackRoutes.DeviceCompromisedModal}
component={DeviceCompromisedModalScreen}
/>
</AuthenticityChecksStack.Navigator>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useDispatch, useSelector } from 'react-redux';

import { useNavigation } from '@react-navigation/native';

import { deviceActions, selectSelectedDevice } from '@suite-common/wallet-core';
import { Button, IconListTextItem, TitleHeader, VStack } from '@suite-native/atoms';
import { Translation } from '@suite-native/intl';
import { useOpenLink } from '@suite-native/link';
import {
AppTabsRoutes,
HomeStackRoutes,
RootStackParamList,
RootStackRoutes,
Screen,
ScreenHeader,
StackToStackCompositeNavigationProps,
} from '@suite-native/navigation';

// TODO this page is for desktop; await creation of new page tailored to the suite-native UX
const TREZOR_SUPPORT_FW_REVISION_CHECK_FAILED_URL =
'https://trezor.io/support/a/trezor-fw-revision-check-failed';

const InformativeList = () => (
<VStack spacing="sp24">
<IconListTextItem icon="handPalm" variant="red">
<Translation id="moduleAuthenticityChecks.deviceCompromised.steps.avoidUsingDevice" />
</IconListTextItem>

<IconListTextItem icon="chatCircle" variant="red">
<Translation id="moduleAuthenticityChecks.deviceCompromised.steps.contactSupport" />
</IconListTextItem>
</VStack>
);

type NavigationProp = StackToStackCompositeNavigationProps<
RootStackParamList,
RootStackRoutes.AppTabs,
RootStackParamList
>;

export const DeviceCompromisedModalScreen = () => {
const device = useSelector(selectSelectedDevice);
const dispatch = useDispatch();

const navigation = useNavigation<NavigationProp>();

const dismissCheck = () => {
if (device?.id) {
dispatch(deviceActions.dismissFirmwareAuthenticityCheck(device.id));
}
};

const handleClose = () => {
if (navigation.canGoBack()) {
navigation.goBack();
} else {
navigation.navigate(RootStackRoutes.AppTabs, {
screen: AppTabsRoutes.HomeStack,
params: { screen: HomeStackRoutes.Home },
});
}
dismissCheck();
};

const chatUrl = `${TREZOR_SUPPORT_FW_REVISION_CHECK_FAILED_URL}#open-chat`;
const openLink = useOpenLink();
const handleContactSupportClick = () => openLink(chatUrl);

return (
<Screen header={<ScreenHeader closeActionType="close" closeAction={dismissCheck} />}>
<VStack spacing="sp32" flex={1}>
<TitleHeader
titleVariant="titleMedium"
titleSpacing="sp12"
title={<Translation id="moduleAuthenticityChecks.deviceCompromised.title" />}
subtitle={
<Translation id="moduleAuthenticityChecks.deviceCompromised.subtitle" />
}
/>
<InformativeList />
</VStack>
<VStack spacing="sp12">
<Button colorScheme="redBold" onPress={handleContactSupportClick}>
<Translation id="moduleAuthenticityChecks.deviceCompromised.buttonContactSupport" />
</Button>
<Button colorScheme="redElevation0" onPress={handleClose}>
<Translation id="moduleAuthenticityChecks.deviceCompromised.buttonClose" />
</Button>
</VStack>
</Screen>
);
};
13 changes: 13 additions & 0 deletions suite-native/module-authenticity-checks/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "outDir": "libDev" },
"references": [
{
"path": "../../suite-common/wallet-core"
},
{ "path": "../atoms" },
{ "path": "../intl" },
{ "path": "../link" },
{ "path": "../navigation" }
]
}
6 changes: 6 additions & 0 deletions suite-native/navigation/src/navigators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
AccountsStackRoutes,
AddCoinAccountStackRoutes,
AppTabsRoutes,
AuthenticityChecksStackRoutes,
AuthorizeDeviceStackRoutes,
DevUtilsStackRoutes,
DeviceAuthenticityStackRoutes,
Expand Down Expand Up @@ -191,6 +192,10 @@ export type AuthorizeDeviceStackParamList = {
[AuthorizeDeviceStackRoutes.PassphraseFeatureUnlockForm]: undefined;
};

export type AuthenticityChecksStackParamList = {
[AuthenticityChecksStackRoutes.DeviceCompromisedModal]: undefined;
};

export type RootStackParamList = {
[RootStackRoutes.AppTabs]: NavigatorScreenParams<AppTabsParamList>;
[RootStackRoutes.Onboarding]: NavigatorScreenParams<OnboardingStackParamList>;
Expand All @@ -215,6 +220,7 @@ export type RootStackParamList = {
parsedUrl: ParsedURL;
};
[RootStackRoutes.SettingsScreenStack]: NavigatorScreenParams<SettingsStackParamList>;
[RootStackRoutes.AuthenticityChecksStack]: NavigatorScreenParams<AuthenticityChecksStackParamList>;
};

export type TradingStackParamList = {
Expand Down
5 changes: 5 additions & 0 deletions suite-native/navigation/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export enum RootStackRoutes {
CoinEnablingInit = 'CoinEnablingInit',
ConnectPopup = 'ConnectPopup',
SettingsScreenStack = 'SettingsScreenStack',
AuthenticityChecksStack = 'AuthenticityChecksStack',
}

export enum AppTabsRoutes {
Expand Down Expand Up @@ -123,3 +124,7 @@ export enum SettingsStackRoutes {
export enum TradingStackRoutes {
Trading = 'Trading',
}

export enum AuthenticityChecksStackRoutes {
DeviceCompromisedModal = 'DeviceCompromisedModal',
}
16 changes: 16 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9997,6 +9997,7 @@ __metadata:
"@suite-native/module-accounts-import": "workspace:*"
"@suite-native/module-accounts-management": "workspace:*"
"@suite-native/module-add-accounts": "workspace:*"
"@suite-native/module-authenticity-checks": "workspace:*"
"@suite-native/module-authorize-device": "workspace:*"
"@suite-native/module-connect-popup": "workspace:*"
"@suite-native/module-dev-utils": "workspace:*"
Expand Down Expand Up @@ -10643,6 +10644,21 @@ __metadata:
languageName: unknown
linkType: soft

"@suite-native/module-authenticity-checks@workspace:*, @suite-native/module-authenticity-checks@workspace:suite-native/module-authenticity-checks":
version: 0.0.0-use.local
resolution: "@suite-native/module-authenticity-checks@workspace:suite-native/module-authenticity-checks"
dependencies:
"@react-navigation/native": "npm:6.1.18"
"@react-navigation/native-stack": "npm:6.11.0"
"@suite-common/wallet-core": "workspace:*"
"@suite-native/atoms": "workspace:*"
"@suite-native/intl": "workspace:*"
"@suite-native/link": "workspace:*"
"@suite-native/navigation": "workspace:*"
react-redux: "npm:8.0.7"
languageName: unknown
linkType: soft

"@suite-native/module-authorize-device@workspace:*, @suite-native/module-authorize-device@workspace:suite-native/module-authorize-device":
version: 0.0.0-use.local
resolution: "@suite-native/module-authorize-device@workspace:suite-native/module-authorize-device"
Expand Down

0 comments on commit 8cae3e0

Please sign in to comment.