Skip to content
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

Open sea security provider warning message #17662

Merged
merged 9 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
18 changes: 18 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion app/scripts/lib/security-provider-helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
import { MESSAGE_TYPE } from '../../../shared/constants/app';

const fetchWithTimeout = getFetchWithTimeout();

export async function securityProviderCheck(
requestData,
methodName,
Expand Down Expand Up @@ -47,7 +50,7 @@ export async function securityProviderCheck(
};
}

const response = await fetch(
const response = await fetchWithTimeout(
'https://eos9d7dmfj.execute-api.us-east-1.amazonaws.com/metamask/validate',
{
method: 'POST',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import Typography from '../../../ui/typography';
import { TypographyVariant } from '../../../../helpers/constants/design-system';
import DepositPopover from '../../deposit-popover/deposit-popover';

import SecurityProviderBannerMessage from '../../security-provider-banner-message/security-provider-banner-message';
import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../../security-provider-banner-message/security-provider-banner-message.constants';
import { ConfirmPageContainerSummary, ConfirmPageContainerWarning } from '.';

export default class ConfirmPageContainerContent extends Component {
Expand Down Expand Up @@ -55,6 +57,7 @@ export default class ConfirmPageContainerContent extends Component {
toAddress: PropTypes.string,
transactionType: PropTypes.string,
isBuyableChain: PropTypes.bool,
txData: PropTypes.object,
};

state = {
Expand Down Expand Up @@ -166,6 +169,7 @@ export default class ConfirmPageContainerContent extends Component {
toAddress,
transactionType,
isBuyableChain,
txData,
} = this.props;

const { t } = this.context;
Expand All @@ -187,6 +191,13 @@ export default class ConfirmPageContainerContent extends Component {
{ethGasPriceWarning && (
<ConfirmPageContainerWarning warning={ethGasPriceWarning} />
)}
{txData?.securityProviderResponse?.flagAsDangerous !== undefined &&
txData?.securityProviderResponse?.flagAsDangerous !==
SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS && (
<SecurityProviderBannerMessage
securityProviderResponse={txData.securityProviderResponse}
/>
)}
<ConfirmPageContainerSummary
className={classnames({
'confirm-page-container-summary--border':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will be nice to have test coverage of the code change in this component.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed here: 40ce40f

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import configureMockStore from 'redux-mock-store';
import { TransactionType } from '../../../../../shared/constants/transaction';
import { renderWithProvider } from '../../../../../test/lib/render-helpers';
import { TRANSACTION_ERROR_KEY } from '../../../../helpers/constants/error-keys';
import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from '../../security-provider-banner-message/security-provider-banner-message.constants';
import ConfirmPageContainerContent from './confirm-page-container-content.component';

describe('Confirm Page Container Content', () => {
Expand Down Expand Up @@ -47,6 +48,13 @@ describe('Confirm Page Container Content', () => {
disabled: true,
origin: 'http://localhost:4200',
hideTitle: false,
txData: {
securityProviderResponse: {
flagAsDangerous: '?',
reason: 'Some reason...',
reason_header: 'Some reason header...',
},
},
};
});

Expand Down Expand Up @@ -126,4 +134,40 @@ describe('Confirm Page Container Content', () => {

expect(queryByText('Address Book Account 1')).not.toBeInTheDocument();
});

it('should render SecurityProviderBannerMessage component properly', () => {
const { queryByText } = renderWithProvider(
<ConfirmPageContainerContent {...props} />,
store,
);

expect(queryByText('Request not verified')).toBeInTheDocument();
expect(
queryByText(
'Because of an error, this request was not verified by the security provider. Proceed with caution.',
),
).toBeInTheDocument();
expect(
queryByText('This is based on information from'),
).toBeInTheDocument();
});

it('should not render SecurityProviderBannerMessage component when flagAsDangerous is not malicious', () => {
props.txData.securityProviderResponse = {
flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_MALICIOUS,
};

const { queryByText } = renderWithProvider(
<ConfirmPageContainerContent {...props} />,
store,
);

expect(queryByText('Request not verified')).toBeNull();
expect(
queryByText(
'Because of an error, this request was not verified by the security provider. Proceed with caution.',
),
).toBeNull();
expect(queryByText('This is based on information from')).toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ const ConfirmPageContainer = (props) => {
currentTransaction,
supportsEIP1559,
nativeCurrency,
///: BEGIN:ONLY_INCLUDE_IN(flask)
txData,
///: END:ONLY_INCLUDE_IN(flask)
assetStandard,
isApprovalOrRejection,
} = props;
Expand Down Expand Up @@ -223,6 +221,7 @@ const ConfirmPageContainer = (props) => {
toAddress={toAddress}
transactionType={currentTransaction.type}
isBuyableChain={isBuyableChain}
txData={txData}
/>
)}
{shouldDisplayWarning && errorKey === INSUFFICIENT_FUNDS_ERROR_KEY && (
Expand Down Expand Up @@ -346,9 +345,7 @@ ConfirmPageContainer.propTypes = {
dataComponent: PropTypes.node,
dataHexComponent: PropTypes.node,
detailsComponent: PropTypes.node,
///: BEGIN:ONLY_INCLUDE_IN(flask)
txData: PropTypes.object,
///: END:ONLY_INCLUDE_IN(flask)
tokenAddress: PropTypes.string,
nonce: PropTypes.string,
warning: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './security-provider-banner-message';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const SECURITY_PROVIDER_MESSAGE_SEVERITIES = {
NOT_MALICIOUS: 0,
MALICIOUS: 1,
NOT_SAFE: 2,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import {
Color,
SEVERITIES,
Size,
TypographyVariant,
} from '../../../helpers/constants/design-system';
import { I18nContext } from '../../../../.storybook/i18n';
import { BannerAlert, ButtonLink } from '../../component-library';
import Typography from '../../ui/typography/typography';
import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from './security-provider-banner-message.constants';

export default function SecurityProviderBannerMessage({
securityProviderResponse,
}) {
const t = useContext(I18nContext);

let messageTitle;
let messageText;
let severity;

if (
securityProviderResponse.flagAsDangerous ===
SECURITY_PROVIDER_MESSAGE_SEVERITIES.MALICIOUS
) {
messageTitle = securityProviderResponse.reason_header;
messageText = securityProviderResponse.reason;
severity = SEVERITIES.DANGER;
} else if (
securityProviderResponse.flagAsDangerous ===
SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_SAFE
) {
messageTitle = t('requestMayNotBeSafe');
messageText = t('requestMayNotBeSafeError');
severity = SEVERITIES.WARNING;
} else {
messageTitle = t('requestNotVerified');
messageText = t('requestNotVerifiedError');
severity = SEVERITIES.WARNING;
}

return (
<BannerAlert
marginTop={4}
marginRight={4}
marginLeft={4}
title={messageTitle}
severity={severity}
>
<Typography variant={TypographyVariant.H6}>{messageText}</Typography>
<Typography variant={TypographyVariant.H7} color={Color.textAlternative}>
{t('thisIsBasedOn')}
<ButtonLink
size={Size.inherit}
href="https://opensea.io/"
target="_blank"
>
{t('openSeaNew')}
</ButtonLink>
</Typography>
</BannerAlert>
);
}

SecurityProviderBannerMessage.propTypes = {
securityProviderResponse: PropTypes.object,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { fireEvent } from '@testing-library/react';
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import SecurityProviderBannerMessage from './security-provider-banner-message';
import { SECURITY_PROVIDER_MESSAGE_SEVERITIES } from './security-provider-banner-message.constants';

describe('Security Provider Banner Message', () => {
const store = configureMockStore()({});

const thisIsBasedOnText = 'This is based on information from';

it('should render SecurityProviderBannerMessage component properly when flagAsDangerous is malicious', () => {
const securityProviderResponse = {
flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.MALICIOUS,
reason:
'Approval is to an unverified smart contract known for stealing NFTs in the past.',
reason_header: 'This could be a scam',
};

const { getByText } = renderWithProvider(
<SecurityProviderBannerMessage
securityProviderResponse={securityProviderResponse}
/>,
store,
);

expect(getByText(securityProviderResponse.reason)).toBeInTheDocument();
expect(
getByText(securityProviderResponse.reason_header),
).toBeInTheDocument();
expect(getByText(thisIsBasedOnText)).toBeInTheDocument();
});

it('should render SecurityProviderBannerMessage component properly when flagAsDangerous is not safe', () => {
const securityProviderResponse = {
flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_SAFE,
reason: 'Some reason...',
reason_header: 'Some reason header...',
};

const requestMayNotBeSafe = 'Request may not be safe';
const requestMayNotBeSafeError =
"The security provider didn't detect any known malicious activity, but it still may not be safe to continue.";

const { getByText } = renderWithProvider(
<SecurityProviderBannerMessage
securityProviderResponse={securityProviderResponse}
/>,
store,
);

expect(getByText(requestMayNotBeSafe)).toBeInTheDocument();
expect(getByText(requestMayNotBeSafeError)).toBeInTheDocument();
expect(getByText(thisIsBasedOnText)).toBeInTheDocument();
});

it('should render SecurityProviderBannerMessage component properly when flagAsDangerous is undefined', () => {
const securityProviderResponse = {
flagAsDangerous: '?',
reason: 'Some reason...',
reason_header: 'Some reason header...',
};

const requestNotVerified = 'Request not verified';
const requestNotVerifiedError =
'Because of an error, this request was not verified by the security provider. Proceed with caution.';

const { getByText } = renderWithProvider(
<SecurityProviderBannerMessage
securityProviderResponse={securityProviderResponse}
/>,
store,
);

expect(getByText(requestNotVerified)).toBeInTheDocument();
expect(getByText(requestNotVerifiedError)).toBeInTheDocument();
expect(getByText(thisIsBasedOnText)).toBeInTheDocument();
});

it('should render SecurityProviderBannerMessage component properly when securityProviderResponse is empty', () => {
const securityProviderResponse = {};

const requestNotVerified = 'Request not verified';
const requestNotVerifiedError =
'Because of an error, this request was not verified by the security provider. Proceed with caution.';

const { getByText } = renderWithProvider(
<SecurityProviderBannerMessage
securityProviderResponse={securityProviderResponse}
/>,
store,
);

expect(getByText(requestNotVerified)).toBeInTheDocument();
expect(getByText(requestNotVerifiedError)).toBeInTheDocument();
expect(getByText(thisIsBasedOnText)).toBeInTheDocument();
});

it('should navigate to the OpenSea web page when clicked on the OpenSea button', () => {
const securityProviderResponse = {
flagAsDangerous: SECURITY_PROVIDER_MESSAGE_SEVERITIES.NOT_SAFE,
reason: 'Some reason...',
reason_header: 'Some reason header...',
};

const { getByText } = renderWithProvider(
<SecurityProviderBannerMessage
securityProviderResponse={securityProviderResponse}
/>,
store,
);

const link = getByText('OpenSea');

expect(link).toBeInTheDocument();

fireEvent.click(link);

expect(link.closest('a')).toHaveAttribute('href', 'https://opensea.io/');
});
});
Loading