Skip to content

Commit

Permalink
feat(safe-apps): Add custom Safe App warning (#1258)
Browse files Browse the repository at this point in the history
  • Loading branch information
yagopv authored Nov 30, 2022
1 parent 94cd4d4 commit f222f51
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 21 deletions.
2 changes: 1 addition & 1 deletion cypress/e2e/safe-apps/browser_permissions.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('The Browser permissions system', () => {
.click()
.should(() => {
expect(window.localStorage.getItem(BROWSER_PERMISSIONS_KEY)).to.eq(
'{"https://safe-test-app.com/app":[{"feature":"camera","status":"granted"},{"feature":"microphone","status":"denied"}]}',
'{"https://safe-test-app.com":[{"feature":"camera","status":"granted"},{"feature":"microphone","status":"denied"}]}',
)
})
})
Expand Down
32 changes: 32 additions & 0 deletions cypress/e2e/safe-apps/custom_apps.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const appUrl = 'https://safe-custom-app.com'

describe('When visiting a custom Safe App', () => {
beforeEach(() => {
cy.fixture('safe-app').then((html) => {
cy.intercept('GET', `${appUrl}`, html)
cy.intercept('GET', `${appUrl}/manifest.json`, {
name: 'Cypress Test App',
description: 'Cypress Test App Description',
icons: [{ src: 'logo.svg', sizes: 'any', type: 'image/svg+xml' }],
})
})
})

it('should show the custom app warning', () => {
cy.visitSafeApp(`${appUrl}`)

cy.findByText(/accept selection/i).click()
cy.findByRole('heading', { content: /warning/i })
cy.findByText('https://safe-custom-app.com')
cy.reload()
cy.findByRole('heading', { content: /warning/i })
cy.findByText('https://safe-custom-app.com')
})

it('should stop showing the warning when the check is marked', () => {
cy.findByRole('checkbox').should('exist').click()
cy.findByRole('button', { name: /continue/i }).click()
cy.reload()
cy.findByRole('heading', { content: /warning/i }).should('not.exist')
})
})
2 changes: 1 addition & 1 deletion cypress/e2e/safe-apps/tx_modal.cy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { TEST_SAFE } from './constants'

const appUrl = 'http://safe-test-app.com'
const appUrl = 'https://safe-test-app.com'

describe('The transaction modal', () => {
before(() => {
Expand Down
4 changes: 3 additions & 1 deletion cypress/support/safe-apps-commands.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { INFO_MODAL_KEY, TEST_SAFE } from '../e2e/safe-apps/constants'

const allowedApps = ['https://safe-test-app.com']

Cypress.Commands.add('visitSafeApp', (appUrl, testSafe = TEST_SAFE) => {
cy.on('window:before:load', (window) => {
window.localStorage.setItem(
INFO_MODAL_KEY,
JSON.stringify({
5: { consentsAccepted: true },
5: { consentsAccepted: true, warningCheckedCustomApps: allowedApps },
}),
)
})
Expand Down
20 changes: 20 additions & 0 deletions src/components/safe-apps/SafeAppsInfoModal/Domain.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react'
import { Typography } from '@mui/material'
import CheckIcon from '@mui/icons-material/Check'

import styles from './styles.module.css'

type DomainProps = {
url: string
showInOneLine?: boolean
}

const Domain: React.FC<DomainProps> = ({ url, showInOneLine }): React.ReactElement => {
return (
<Typography className={styles.domainText} sx={showInOneLine ? { overflowY: 'hidden', whiteSpace: 'nowrap' } : {}}>
<CheckIcon color="success" className={styles.domainIcon} /> {url}
</Typography>
)
}

export default Domain
56 changes: 56 additions & 0 deletions src/components/safe-apps/SafeAppsInfoModal/UnknownAppWarning.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useState } from 'react'
import { Box, Checkbox, FormControlLabel, Typography } from '@mui/material'
import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined'
import Domain from './Domain'
import palette from '@/styles/colors'

type UnknownAppWarningProps = {
url?: string
onHideWarning?: (hideWarning: boolean) => void
}

const UnknownAppWarning = ({ url, onHideWarning }: UnknownAppWarningProps): React.ReactElement => {
const [toggleHideWarning, setToggleHideWarning] = useState(false)

const handleToggleWarningPreference = (): void => {
onHideWarning?.(!toggleHideWarning)
setToggleHideWarning(!toggleHideWarning)
}

return (
<Box display="flex" flexDirection="column" height="100%" alignItems="center">
<Box display="block" alignItems="center" mt={6}>
<WarningAmberOutlinedIcon fontSize="large" color="warning" />
<Typography variant="h3" fontWeight={700} mt={2} color={palette.warning.main}>
Warning
</Typography>
</Box>
<Typography my={2} fontWeight={700} color={palette.warning.main}>
The application you are trying to access is not in the default Safe Apps list
</Typography>

<Typography my={2} textAlign="center">
Check the link you are using and ensure that it comes from a source you trust
</Typography>

{url && <Domain url={url} showInOneLine />}

{onHideWarning && (
<Box mt={2}>
<FormControlLabel
control={
<Checkbox
checked={toggleHideWarning}
onChange={handleToggleWarningPreference}
name="Warning message preference"
/>
}
label="Don't show this warning again"
/>
</Box>
)}
</Box>
)
}

export default UnknownAppWarning
32 changes: 27 additions & 5 deletions src/components/safe-apps/SafeAppsInfoModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,31 @@ import LegalDisclaimer from './LegalDisclaimer'
import AllowedFeaturesList from './AllowedFeaturesList'
import type { AllowedFeatures, AllowedFeatureSelection } from '../types'
import { PermissionStatus } from '../types'
import UnknownAppWarning from './UnknownAppWarning'
import { getOrigin } from '../utils'

type SafeAppsInfoModalProps = {
onCancel: () => void
onConfirm: (browserPermissions: BrowserPermission[]) => void
onConfirm: (shouldHide: boolean, browserPermissions: BrowserPermission[]) => void
features: AllowedFeatures[]
appUrl: string
isConsentAccepted?: boolean
isPermissionsReviewCompleted: boolean
isSafeAppInDefaultList: boolean
isFirstTimeAccessingApp: boolean
}

const SafeAppsInfoModal = ({
onCancel,
onConfirm,
features,
appUrl,
isConsentAccepted,
isPermissionsReviewCompleted,
isSafeAppInDefaultList,
isFirstTimeAccessingApp,
}: SafeAppsInfoModalProps): JSX.Element => {
const [hideWarning, setHideWarning] = useState(false)
const [selectedFeatures, setSelectedFeatures] = useState<AllowedFeatureSelection[]>(
features.map((feature) => {
return {
Expand All @@ -45,8 +54,12 @@ const SafeAppsInfoModal = ({
totalSlides += 1
}

if (!isSafeAppInDefaultList && isFirstTimeAccessingApp) {
totalSlides += 1
}

return totalSlides
}, [isConsentAccepted, isPermissionsReviewCompleted])
}, [isConsentAccepted, isFirstTimeAccessingApp, isPermissionsReviewCompleted, isSafeAppInDefaultList])

const handleSlideChange = (newStep: number) => {
const isFirstStep = newStep === -1
Expand All @@ -58,6 +71,7 @@ const SafeAppsInfoModal = ({

if (isLastStep) {
onConfirm(
hideWarning,
selectedFeatures.map(({ feature, checked }) => {
return {
feature,
Expand All @@ -74,6 +88,11 @@ const SafeAppsInfoModal = ({
return ((currentSlide + 1) * 100) / totalSlides
}, [currentSlide, totalSlides])

const shouldShowUnknownAppWarning = useMemo(
() => !isSafeAppInDefaultList && isFirstTimeAccessingApp,
[isFirstTimeAccessingApp, isSafeAppInDefaultList],
)

const handleFeatureSelectionChange = (feature: AllowedFeatures, checked: boolean) => {
setSelectedFeatures(
selectedFeatures.map((feat) => {
Expand All @@ -88,13 +107,14 @@ const SafeAppsInfoModal = ({
)
}

const origin = useMemo(() => getOrigin(appUrl), [appUrl])

return (
<Box display="flex" alignItems="center" justifyContent="center" flexDirection="column" height="calc(100vh - 52px)">
<Box
sx={({ palette, shape }) => ({
sx={({ palette }) => ({
width: '450px',
backgroundColor: palette.background.paper,
borderRadius: shape.borderRadius,
boxShadow: `1px 2px 10px 0 ${alpha(palette.text.primary, 0.18)}`,
})}
>
Expand All @@ -106,7 +126,8 @@ const SafeAppsInfoModal = ({
backgroundColor: palette.background.paper,
borderRadius: '8px 8px 0 0',
'> .MuiLinearProgress-bar': {
backgroundColor: palette.primary.main,
backgroundColor:
progressValue === 100 && shouldShowUnknownAppWarning ? palette.warning.main : palette.primary.main,
borderRadius: '8px',
},
})}
Expand All @@ -120,6 +141,7 @@ const SafeAppsInfoModal = ({
onFeatureSelectionChange={handleFeatureSelectionChange}
/>
)}
{shouldShowUnknownAppWarning && <UnknownAppWarning url={origin} onHideWarning={setHideWarning} />}
</Slider>
</Grid>
</Box>
Expand Down
17 changes: 17 additions & 0 deletions src/components/safe-apps/SafeAppsInfoModal/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,20 @@
.disclaimerInner p {
text-align: justify;
}

.domainIcon {
position: relative;
top: 6px;
padding-right: 4px;
}

.domainText {
display: block;
font-size: 12px;
font-weight: bold;
overflow-wrap: anywhere;
background-color: var(--color-background-light);
padding: 0 15px 10px 10px;
border-radius: 8px;
max-width: 75%;
}
Loading

0 comments on commit f222f51

Please sign in to comment.