Skip to content

Commit

Permalink
feat: #2282 Warn when safety_checks are disabled
Browse files Browse the repository at this point in the history
When safety_checks are not "Strict" display a warning banner that points
the user to safety_checks settings.

If the safety_checks are "PromptTemporarily" allow the waring to be
dismissed and not being displayed until either the Suite is
restarted/reloaded or the safety_checks are changed.
  • Loading branch information
goodhoko committed Apr 28, 2021
1 parent 8345b55 commit fdab83a
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// @group:suite
// @retry=2

// TODO: enable this test once https://github.com/trezor/trezor-user-env/issues/54
// is resolved
// describe('safety_checks Warning For PromptAlways', () => {
// beforeEach(() => {
// cy.task('startEmu', { wipe: true });
// cy.task('setupEmu');
// cy.task('startBridge');
// cy.viewport(1024, 768).resetDb();
// cy.prefixedVisit('/settings/device/');
// cy.passThroughInitialRun();
// // TODO: set safety_checks to `PromptAlways`
// });

// it('Non-dismissible warning appears', () => {
// cy.getTestElement('@banner/safety-checks/button');
// cy.getTestElement('@banner/safety-checks/dismiss').should('not.exist');
// });
// })

describe('safety_checks Warning For PromptTemporarily', () => {
beforeEach(() => {
cy.task('startEmu', { wipe: true });
cy.task('setupEmu');
cy.task('startBridge');
cy.viewport(1024, 768).resetDb();
// Start in the device settings to easily open safety_checks setting modal.
cy.prefixedVisit('/settings/device/');
cy.passThroughInitialRun();
// Set safety_checks to `PromptTemporarily'.
// TODO: do this via the `applySetting` task once https://github.com/trezor/trezor-user-env/issues/54
// is resolved.
cy.getTestElement('@settings/device/safety-checks-button').click();
cy.get(`[data-test="@radio-button"][value="PromptTemporarily"]`).click();
cy.getTestElement('@safety-checks-apply').click();
cy.task('pressYes');
});

it('Dismissible warning appears', () => {
cy.getTestElement('@banner/safety-checks/button');
cy.getTestElement('@banner/safety-checks/dismiss');
});

it('CTA button opens device settings', () => {
cy.getTestElement('@banner/safety-checks/button').click();
// In CI the path is prefixed with a branch name. Test only against the end of the path.
cy.location('pathname').should('match', /\/settings\/device\/$/)
});

it('Dismiss button hides the warning', () => {
cy.getTestElement('@banner/safety-checks/dismiss').click();
cy.getTestElement('@banner/safety-checks/button').should('not.exist');
});

it('Warning disappears when safety_checks are set to strict', () => {
// Open the safety_checks setting modal and change safety_checks to Strict.
cy.getTestElement('@settings/device/safety-checks-button').click();
cy.get('[data-test="@radio-button"][value="Strict"]').click();
cy.getTestElement('@safety-checks-apply').click();
cy.task('pressYes');
// Assert the warning is gone.
cy.getTestElement('@banner/safety-checks/button').should('not.exist');
});

it('Dismissed warning re-appears when safety_checks are set to strict and then to Prompt again.', () => {
// Dismiss the warning.
cy.getTestElement('@banner/safety-checks/dismiss').click();
// Open the safety_checks setting modal and change safety_checks to Strict.
cy.getTestElement('@settings/device/safety-checks-button').click();
cy.get('[data-test="@radio-button"][value="Strict"]').click();
cy.getTestElement('@safety-checks-apply').click();
cy.task('pressYes');
// Assert the warning is gone.
cy.getTestElement('@banner/safety-checks/button').should('not.exist');
// Set safety_checks back to PromptTemporarily
cy.getTestElement('@settings/device/safety-checks-button').click();
cy.get(`[data-test="@radio-button"][value="PromptTemporarily"]`).click();
cy.getTestElement('@safety-checks-apply').click();
cy.task('pressYes');
// Assert the warning appear again.
cy.getTestElement('@banner/safety-checks/button');
});
});
38 changes: 38 additions & 0 deletions packages/suite/src/components/suite/Banners/SafetyChecks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { Translation } from '@suite-components';
import { useActions } from '@suite-hooks';
import Wrapper from './components/Wrapper';
import * as routerActions from '@suite/actions/suite/routerActions';

interface Props {
onDismiss?: () => void;
}

const SafetyChecksBanner = (props: Props) => {
const { goto } = useActions({
goto: routerActions.goto,
});

return (
<Wrapper
variant="warning"
body={<Translation id="TR_SAFETY_CHECKS_DISABLED_WARNING" />}
action={{
label: <Translation id="TR_SAFETY_CHECKS_BANNER_CHANGE" />,
// TODO: Use anchor to bring the user to the appropriate section of settings.
onClick: () => goto('settings-device'),
'data-test': '@banner/safety-checks/button',
}}
dismissal={
props.onDismiss !== undefined
? {
onClick: props.onDismiss,
'data-test': '@banner/safety-checks/dismiss',
}
: undefined
}
/>
);
};

export default SafetyChecksBanner;
21 changes: 20 additions & 1 deletion packages/suite/src/components/suite/Banners/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as React from 'react';
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';

import { isDesktop } from '@suite-utils/env';
Expand All @@ -9,6 +9,7 @@ import UpdateFirmware from './UpdateFirmware';
import NoBackup from './NoBackup';
import FailedBackup from './FailedBackup';
import MessageSystemBanner from './MessageSystemBanner';
import SafetyChecksBanner from './SafetyChecks';

import type { Message } from '@suite-types/messageSystem';

Expand All @@ -22,6 +23,11 @@ const Banners = () => {
const online = useSelector(state => state.suite.online);
const device = useSelector(state => state.suite.device);
const { validMessages, dismissedMessages, config } = useSelector(state => state.messageSystem);
// The dismissal doesn't need to outlive the session. Use local state.
const [safetyChecksDismissed, setSafetyChecksDismissed] = useState(false);
useEffect(() => {
setSafetyChecksDismissed(false);
}, [device?.features?.safety_checks]);

const showUpdateBridge = () => {
if (
Expand Down Expand Up @@ -58,6 +64,19 @@ const Banners = () => {
} else if (device?.features?.needs_backup) {
banner = <NoBackup />;
priority = 7;
} else if (device?.connected && device?.features?.safety_checks === 'PromptAlways') {
// PromptAlways could only be set via trezorctl. Warn user unconditionally.
banner = <SafetyChecksBanner />;
priority = 6;
} else if (
!safetyChecksDismissed &&
device?.connected &&
device?.features?.safety_checks === 'PromptTemporarily'
) {
// PromptTemporarily was probably set intentionally via Suite and will change back to Strict when Trezor reboots.
// Let the user dismiss the warning.
banner = <SafetyChecksBanner onDismiss={() => setSafetyChecksDismissed(true)} />;
priority = 6;
} else if (showUpdateBridge()) {
banner = <UpdateBridge />;
priority = 5;
Expand Down
8 changes: 8 additions & 0 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1610,6 +1610,14 @@ const definedMessages = defineMessages({
'Allow potentially unsafe actions, such as mismatching coin keys or extreme fees by manually approving them on your Trezor.',
id: 'TR_SAFETY_CHECKS_PROMPT_LEVEL_DESC',
},
TR_SAFETY_CHECKS_DISABLED_WARNING: {
defaultMessage: 'Safety Checks are disabled.',
id: 'TR_SAFETY_CHECKS_DISABLED_WARNING',
},
TR_SAFETY_CHECKS_BANNER_CHANGE: {
defaultMessage: 'Change',
id: 'TR_SAFETY_CHECKS_BANNER_CHANGE',
},
TR_DEVICE_SETTINGS_PASSPHRASE_TITLE: {
defaultMessage: 'Passphrase',
id: 'TR_DEVICE_SETTINGS_PASSPHRASE_TITLE',
Expand Down

0 comments on commit fdab83a

Please sign in to comment.