1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-21 17:37:01 +01:00

New BlockaidBannerAlert component (#20051)

This commit is contained in:
Ariella Vu 2023-07-24 08:54:17 +02:00 committed by GitHub
parent e030f28ba0
commit 737173ed5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 516 additions and 0 deletions

View File

@ -565,6 +565,30 @@
"blockaid": {
"message": "Blockaid"
},
"blockaidDescriptionApproveFarming": {
"message": "If you approve this request, a third party known for scams might take all your assets."
},
"blockaidDescriptionBlurFarming": {
"message": "If you approve this request, someone can steal your assets listed on Blur."
},
"blockaidDescriptionMaliciousDomain": {
"message": "You're interacting with a malicious domain. If you approve this request, you might lose your assets."
},
"blockaidDescriptionMightLoseAssets": {
"message": "If you approve this request, you might lose your assets."
},
"blockaidDescriptionSeaportFarming": {
"message": "If you approve this request, someone can steal your assets listed on OpenSea."
},
"blockaidDescriptionTransferFarming": {
"message": "If you approve this request, a third party known for scams will take all your assets."
},
"blockaidTitleDeceptive": {
"message": "This is a deceptive request"
},
"blockaidTitleSuspicious": {
"message": "This is a suspicious request"
},
"blockies": {
"message": "Blockies"
},

View File

@ -19,6 +19,42 @@ export const SECURITY_PROVIDER_CONFIG: Readonly<SecurityProviderConfig> = {
},
};
/** The reason, also referred to as the attack type, provided in the PPOM Response */
export enum BlockaidReason {
/** Approval for a malicious spender */
approvalFarming = 'approval_farming',
/** Malicious signature on Blur order */
blurFarming = 'blur_farming',
/** A known malicous site invoked that transaction */
maliciousDomain = 'malicious_domain',
/** Malicious signature on a Permit order */
permitFarming = 'permit_farming',
/** Direct theft of native assets (ETH/MATIC/AVAX/ etc …) */
rawNativeTokenTransfer = 'raw_native_token_transfer',
/** Malicious raw signature from the user */
rawSignatureFarming = 'raw_signature_farming',
/** Malicious signature on a Seaport order */
seaportFarming = 'seaport_farming',
/** setApprovalForAll for a malicious operator */
setApprovalForAll = 'set_approval_for_all',
/** Malicious signature on other type of trade order (Zero-X / Rarible / etc..) */
tradeOrderFarming = 'trade_order_farming',
/** Direct theft of assets using transfer */
transferFarming = 'transfer_farming',
/** Direct theft of assets using transferFrom */
transferFromFarming = 'transfer_from_farming',
/** Malicious trade that results in the victim being rained */
unfairTrade = 'unfair_trade',
other = 'other',
}
export enum BlockaidResultType {
Malicious = 'Malicious',
Warning = 'Warning',
Benign = 'Benign',
}
/**
* @typedef {object} SecurityProviderMessageSeverity
* @property {0} NOT_MALICIOUS - Indicates message is not malicious

View File

@ -0,0 +1,179 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Blockaid Banner Alert should render 'danger' UI when ppomResponse.resultType is 'Malicious 1`] = `
<div
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-danger box--margin-top-4 box--margin-right-4 box--margin-left-4 box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-error-muted box--rounded-sm"
>
<span
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-error-default"
style="mask-image: url('./images/icons/danger.svg');"
/>
<div>
<h5
class="mm-box mm-text mm-banner-base__title mm-text--body-lg-medium mm-box--color-text-default"
data-testid="mm-banner-base-title"
>
This is a deceptive request
</h5>
<p
class="mm-box mm-text mm-text--body-md mm-box--margin-top-2 mm-box--color-text-default"
>
If you approve this request, a third party known for scams might take all your assets.
</p>
<p
class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--align-items-center mm-box--color-text-alternative"
>
<span
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
style="mask-image: url('./images/icons/security-tick.svg');"
/>
<span>
Security advice by
<a
class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
href="https://blockaid.io/"
rel="noopener noreferrer"
target="_blank"
>
Blockaid
</a>
</span>
</p>
</div>
</div>
`;
exports[`Blockaid Banner Alert should render 'warning' UI when ppomResponse.resultType is 'Warning 1`] = `
<div
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-warning box--margin-top-4 box--margin-right-4 box--margin-left-4 box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-warning-muted box--rounded-sm"
>
<span
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-warning-default"
style="mask-image: url('./images/icons/warning.svg');"
/>
<div>
<h5
class="mm-box mm-text mm-banner-base__title mm-text--body-lg-medium mm-box--color-text-default"
data-testid="mm-banner-base-title"
>
This is a deceptive request
</h5>
<p
class="mm-box mm-text mm-text--body-md mm-box--margin-top-2 mm-box--color-text-default"
>
If you approve this request, a third party known for scams might take all your assets.
</p>
<p
class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--align-items-center mm-box--color-text-alternative"
>
<span
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
style="mask-image: url('./images/icons/security-tick.svg');"
/>
<span>
Security advice by
<a
class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
href="https://blockaid.io/"
rel="noopener noreferrer"
target="_blank"
>
Blockaid
</a>
</span>
</p>
</div>
</div>
`;
exports[`Blockaid Banner Alert should render details when provided 1`] = `
<div>
<div
class="box mm-banner-base mm-banner-alert mm-banner-alert--severity-warning box--margin-top-4 box--margin-right-4 box--margin-left-4 box--padding-3 box--padding-left-2 box--display-flex box--gap-2 box--flex-direction-row box--background-color-warning-muted box--rounded-sm"
>
<span
class="box mm-icon mm-icon--size-lg box--display-inline-block box--flex-direction-row box--color-warning-default"
style="mask-image: url('./images/icons/warning.svg');"
/>
<div>
<h5
class="mm-box mm-text mm-banner-base__title mm-text--body-lg-medium mm-box--color-text-default"
data-testid="mm-banner-base-title"
>
This is a deceptive request
</h5>
<p
class="mm-box mm-text mm-text--body-md mm-box--margin-top-2 mm-box--color-text-default"
>
If you approve this request, a third party known for scams might take all your assets.
</p>
<div
class="disclosure"
>
<details>
<summary
class="disclosure__summary is-arrow"
>
<p
class="mm-box mm-text mm-text--body-md mm-box--color-primary-default"
>
See details
</p>
<span
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-start-2 box--display-inline-block box--flex-direction-row box--color-primary-default"
style="mask-image: url('./images/icons/arrow-up.svg');"
/>
</summary>
<div
class="disclosure__content normal"
>
<ul
class="mm-box mm-text mm-text--body-md mm-box--color-text-default"
>
<li>
Operator is an EOA
</li>
<li>
Operator is untrusted according to previous activity
</li>
</ul>
</div>
<div
class="disclosure__footer"
/>
</details>
</div>
<p
class="mm-box mm-text mm-text--body-sm mm-box--margin-top-2 mm-box--align-items-center mm-box--color-text-alternative"
>
<span
class="box disclosure__summary--icon mm-icon mm-icon--size-sm box--margin-inline-end-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
style="mask-image: url('./images/icons/security-tick.svg');"
/>
<span>
Security advice by
<a
class="mm-box mm-text mm-button-base mm-button-link mm-button-link--size-inherit mm-text--body-md-medium mm-box--padding-right-0 mm-box--padding-left-0 mm-box--display-inline-flex mm-box--justify-content-center mm-box--align-items-center mm-box--color-primary-default mm-box--background-color-transparent"
href="https://blockaid.io/"
rel="noopener noreferrer"
target="_blank"
>
Blockaid
</a>
</span>
</p>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,90 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { captureException } from '@sentry/browser';
import { Text } from '../../../component-library';
import { Severity } from '../../../../helpers/constants/design-system';
import { I18nContext } from '../../../../contexts/i18n';
import {
BlockaidReason,
BlockaidResultType,
SecurityProvider,
} from '../../../../../shared/constants/security-provider';
import SecurityProviderBannerAlert from '../security-provider-banner-alert';
/** Reason to description translation key mapping. Grouped by translations. */
const REASON_TO_DESCRIPTION_TKEY = Object.freeze({
[BlockaidReason.approvalFarming]: 'blockaidDescriptionApproveFarming',
[BlockaidReason.permitFarming]: 'blockaidDescriptionApproveFarming',
[BlockaidReason.setApprovalForAll]: 'blockaidDescriptionApproveFarming',
[BlockaidReason.blurFarming]: 'blockaidDescriptionBlurFarming',
[BlockaidReason.seaportFarming]: 'blockaidDescriptionSeaportFarming',
[BlockaidReason.maliciousDomain]: 'blockaidDescriptionMaliciousDomain',
[BlockaidReason.rawSignatureFarming]: 'blockaidDescriptionMightLoseAssets',
[BlockaidReason.tradeOrderFarming]: 'blockaidDescriptionMightLoseAssets',
[BlockaidReason.unfairTrade]: 'blockaidDescriptionMightLoseAssets',
[BlockaidReason.rawNativeTokenTransfer]: 'blockaidDescriptionTransferFarming',
[BlockaidReason.transferFarming]: 'blockaidDescriptionTransferFarming',
[BlockaidReason.transferFromFarming]: 'blockaidDescriptionTransferFarming',
[BlockaidReason.other]: 'blockaidDescriptionMightLoseAssets',
});
/** List of suspicious reason(s). Other reasons will be deemed as deceptive. */
const SUSPCIOUS_REASON = [BlockaidReason.rawSignatureFarming];
function BlockaidBannerAlert({
ppomResponse: { reason, resultType, features },
}) {
const t = useContext(I18nContext);
if (resultType === BlockaidResultType.Benign) {
return null;
}
if (!REASON_TO_DESCRIPTION_TKEY[reason]) {
captureException(`BlockaidBannerAlert: Unidentified reason '${reason}'`);
}
const description = t(REASON_TO_DESCRIPTION_TKEY[reason] || 'other');
const details = Boolean(features?.length) && (
<Text as="ul">
{features.map((feature, i) => (
<li key={`blockaid-detail-${i}`}> {feature}</li>
))}
</Text>
);
const severity =
resultType === BlockaidResultType.Malicious
? Severity.Danger
: Severity.Warning;
const title =
SUSPCIOUS_REASON.indexOf(reason) > -1
? t('blockaidTitleSuspicious')
: t('blockaidTitleDeceptive');
return (
<SecurityProviderBannerAlert
description={description}
details={details}
provider={SecurityProvider.Blockaid}
severity={severity}
title={title}
/>
);
}
BlockaidBannerAlert.propTypes = {
ppomResponse: PropTypes.object,
};
export default BlockaidBannerAlert;

View File

@ -0,0 +1,42 @@
import React from 'react';
import {
BlockaidReason,
BlockaidResultType,
} from '../../../../../shared/constants/security-provider';
import BlockaidBannerAlert from '.';
const mockFeatures = [
'Operator is an EOA',
'Operator is untrusted according to previous activity',
];
export default {
title: 'Components/App/SecurityProviderBannerAlert/BlockaidBannerAlert',
argTypes: {
features: {
control: 'array',
description:
'ppomResponse.features value which is a list displayed as SecurityProviderBannerAlert details',
},
reason: {
control: 'select',
options: Object.values(BlockaidReason),
description: 'ppomResponse.reason value',
},
resultType: {
control: 'select',
options: Object.values(BlockaidResultType),
description: 'ppomResponse.resultType value',
},
},
args: {
features: mockFeatures,
reason: BlockaidReason.setApprovalForAll,
resultType: BlockaidResultType.Warning,
},
};
export const DefaultStory = (args) => (
<BlockaidBannerAlert ppomResponse={args} />
);
DefaultStory.storyName = 'Default';

View File

@ -0,0 +1,144 @@
import React from 'react';
import { renderWithLocalization } from '../../../../../test/lib/render-helpers';
import { Severity } from '../../../../helpers/constants/design-system';
import {
BlockaidReason,
BlockaidResultType,
} from '../../../../../shared/constants/security-provider';
import BlockaidBannerAlert from '.';
const mockPpomResponse = {
resultType: BlockaidResultType.Warning,
reason: BlockaidReason.setApprovalForAll,
description:
'A SetApprovalForAll request was made on {contract}. We found the operator {operator} to be malicious',
args: {
contract: '0xa7206d878c5c3871826dfdb42191c49b1d11f466',
operator: '0x92a3b9773b1763efa556f55ccbeb20441962d9b2',
},
};
describe('Blockaid Banner Alert', () => {
it(`should not render when ppomResponse.resultType is '${BlockaidResultType.Benign}'`, () => {
const { container } = renderWithLocalization(
<BlockaidBannerAlert
ppomResponse={{
...mockPpomResponse,
resultType: BlockaidResultType.Benign,
}}
/>,
);
expect(container.querySelector('.mm-banner-alert')).toBeNull();
});
it(`should render '${Severity.Danger}' UI when ppomResponse.resultType is '${BlockaidResultType.Malicious}`, () => {
const { container } = renderWithLocalization(
<BlockaidBannerAlert
ppomResponse={{
...mockPpomResponse,
resultType: BlockaidResultType.Malicious,
}}
/>,
);
const dangerBannerAlert = container.querySelector(
'.mm-banner-alert--severity-danger',
);
expect(dangerBannerAlert).toBeInTheDocument();
expect(dangerBannerAlert).toMatchSnapshot();
});
it(`should render '${Severity.Warning}' UI when ppomResponse.resultType is '${BlockaidResultType.Warning}`, () => {
const { container } = renderWithLocalization(
<BlockaidBannerAlert ppomResponse={mockPpomResponse} />,
);
const warningBannerAlert = container.querySelector(
'.mm-banner-alert--severity-warning',
);
expect(warningBannerAlert).toBeInTheDocument();
expect(warningBannerAlert).toMatchSnapshot();
});
it('should render title, "This is a deceptive request"', () => {
const { getByText } = renderWithLocalization(
<BlockaidBannerAlert ppomResponse={mockPpomResponse} />,
);
expect(getByText('This is a deceptive request')).toBeInTheDocument();
});
it('should render title, "This is a suspicious request", when the reason is "raw_signature_farming"', () => {
const { getByText } = renderWithLocalization(
<BlockaidBannerAlert
ppomResponse={{
...mockPpomResponse,
reason: BlockaidReason.rawSignatureFarming,
}}
/>,
);
expect(getByText('This is a suspicious request')).toBeInTheDocument();
});
it('should render details when provided', () => {
const mockFeatures = [
'Operator is an EOA',
'Operator is untrusted according to previous activity',
];
const { container, getByText } = renderWithLocalization(
<BlockaidBannerAlert
ppomResponse={{ ...mockPpomResponse, features: mockFeatures }}
/>,
);
expect(container).toMatchSnapshot();
expect(container.querySelector('.disclosure')).toBeInTheDocument();
mockFeatures.forEach((feature) => {
expect(getByText(`${feature}`)).toBeInTheDocument();
});
});
describe('when rendering description', () => {
Object.entries({
[BlockaidReason.approvalFarming]:
'If you approve this request, a third party known for scams might take all your assets.',
[BlockaidReason.blurFarming]:
'If you approve this request, someone can steal your assets listed on Blur.',
[BlockaidReason.maliciousDomain]:
"You're interacting with a malicious domain. If you approve this request, you might lose your assets.",
[BlockaidReason.other]:
'If you approve this request, you might lose your assets.',
[BlockaidReason.permitFarming]:
'If you approve this request, a third party known for scams might take all your assets.',
[BlockaidReason.rawNativeTokenTransfer]:
'If you approve this request, a third party known for scams will take all your assets.',
[BlockaidReason.rawSignatureFarming]:
'If you approve this request, you might lose your assets.',
[BlockaidReason.seaportFarming]:
'If you approve this request, someone can steal your assets listed on OpenSea.',
[BlockaidReason.setApprovalForAll]:
'If you approve this request, a third party known for scams might take all your assets.',
[BlockaidReason.tradeOrderFarming]:
'If you approve this request, you might lose your assets.',
[BlockaidReason.transferFromFarming]:
'If you approve this request, a third party known for scams will take all your assets.',
[BlockaidReason.transferFarming]:
'If you approve this request, a third party known for scams will take all your assets.',
[BlockaidReason.unfairTrade]:
'If you approve this request, you might lose your assets.',
}).forEach(([reason, expectedDescription]) => {
it(`should render for '${reason}' correctly`, () => {
const { getByText } = renderWithLocalization(
<BlockaidBannerAlert
ppomResponse={{ ...mockPpomResponse, reason }}
/>,
);
expect(getByText(expectedDescription)).toBeInTheDocument();
});
});
});
});

View File

@ -0,0 +1 @@
export { default } from './blockaid-banner-alert';