Skip to content

Commit

Permalink
Merge pull request #1831 from TryQuiet/feature/119
Browse files Browse the repository at this point in the history
feature/Registered usernames that are duplicates or invalid should trigger aggressive warning modal
  • Loading branch information
Kacper-RF authored Oct 5, 2023
2 parents c23826e + 165bad7 commit d6242b9
Show file tree
Hide file tree
Showing 22 changed files with 979 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@

[unreleased]

* Add possible impersonation attack UI for desktop and mobile

* Fix truncated long messages in channelInput component

* Unblock mobile e2e tests
Expand Down
3 changes: 2 additions & 1 deletion packages/desktop/src/renderer/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import ChannelCreationModal from './components/ChannelCreationModal/ChannelCreat
import { SaveStateComponent } from './components/SaveState/SaveStateComponent'
import UnregisteredModalContainer from './components/widgets/userLabel/unregistered/UnregisteredModal.container'
import DuplicateModalContainer from './components/widgets/userLabel/duplicate/DuplicateModal.container'
// Trigger lerna
import PossibleImpersonationAttackModalContainer from './components/widgets/possibleImpersonationAttackModal/PossibleImpersonationAttackModal.container'

export const persistor = persistStore(store)
export default () => {
Expand All @@ -48,6 +48,7 @@ export default () => {
<DuplicateModalContainer />
<SearchModal />
<ErrorModal />
<PossibleImpersonationAttackModalContainer />
<LoadingPanel />
<ChannelCreationModal />
<CreateChannel />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react'
import Modal from '../../ui/Modal/Modal'
import { Button, Grid, Typography } from '@mui/material'
import { styled } from '@mui/material/styles'
import WarnIcon from '../../../static/images/exclamationMark.svg'

const PREFIX = 'PossibleImpersonationAttackModalComponent-'

const classes = {
bodyText: `${PREFIX}bodyText`,
button: `${PREFIX}button`,
image: `${PREFIX}image`,
}

const StyledGrid = styled(Grid)(({ theme }) => ({
[`& .${classes.bodyText}`]: {
textAlign: 'center',
width: '65%',
margin: '24px 0 4px',
},
[`& .${classes.image}`]: {
width: '70px',
height: '70px',
margin: '30px 0 24px',
},
[`& .${classes.button}`]: {
marginTop: 16,
textTransform: 'none',
padding: '0 24px',
height: 40,
borderRadius: '8px',
color: theme.palette.colors.white,
backgroundColor: theme.palette.colors.quietBlue,
'&:hover': {
opacity: 0.7,
backgroundColor: theme.palette.colors.quietBlue,
},
},
}))

export interface PossibleImpersonationAttackModalComponentProps {
communityName: string
leaveCommunity: () => void
open: boolean
handleClose: () => void
}

const PossibleImpersonationAttackModalComponent: React.FC<PossibleImpersonationAttackModalComponentProps> = ({
communityName,
leaveCommunity,
handleClose,
open,
}) => {
return (
<Modal open={open} handleClose={handleClose} title={'Warning!'} isBold addBorder>
<StyledGrid
container
item
direction='column'
justifyContent='flex-start'
alignItems='center'
data-testid={'possible-impersonation-attack-modal-component'}
>
<img className={classes.image} src={WarnIcon} />
<Typography variant='h4'>Possible impersonation attack</Typography>
<Typography className={classes.bodyText} variant='body2'>
The owner of <strong>{communityName}</strong> has registered an invalid username. Either something is very
broken, the community owner is trying to impersonate other users, or the community owner has been hacked.
<br />
<strong>This should never happen and we recommend leaving this community immediately!</strong>
</Typography>
<Button className={classes.button} data-testid='unregistered-button' onClick={leaveCommunity}>
Leave community
</Button>
</StyledGrid>
</Modal>
)
}

export default PossibleImpersonationAttackModalComponent
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { capitalizeFirstLetter } from '@quiet/common'
import { communities, users } from '@quiet/state-manager'
import React, { useEffect } from 'react'
import { useSelector } from 'react-redux'
import { clearCommunity } from '../../..'
import { useModal } from '../../../containers/hooks'
import { ModalName } from '../../../sagas/modals/modals.types'
import PossibleImpersonationAttackModalComponent from './PossibleImpersonationAttackModal.component'

const PossibleImpersonationAttackModalContainer = () => {
const possibleImpersonationAttackModal = useModal(ModalName.possibleImpersonationAttackModal)

const community = useSelector(communities.selectors.currentCommunity)
const duplicateCerts = useSelector(users.selectors.duplicateCerts)

let communityName = '...'

if (community?.name) {
communityName = capitalizeFirstLetter(community.name)
}

const leaveCommunity = async () => {
await clearCommunity()
}

useEffect(() => {
if (duplicateCerts) {
possibleImpersonationAttackModal.handleOpen()
}
}, [duplicateCerts])

return (
<PossibleImpersonationAttackModalComponent
communityName={communityName}
leaveCommunity={leaveCommunity}
{...possibleImpersonationAttackModal}
/>
)
}

export default PossibleImpersonationAttackModalContainer
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'
import { withTheme } from '../../../storybook/decorators'
import PossibleImpersonationAttackModalComponent, {
PossibleImpersonationAttackModalComponentProps,
} from './PossibleImpersonationAttackModal.component'

const Template: ComponentStory<typeof PossibleImpersonationAttackModalComponent> = args => {
return (
<div style={{ height: '800px', position: 'relative' }}>
<PossibleImpersonationAttackModalComponent {...args} />
</div>
)
}

export const Component = Template.bind({})

const args: PossibleImpersonationAttackModalComponentProps = {
handleClose: function (): void {},
open: true,
communityName: 'devteam',
leaveCommunity: function (): void {},
}

Component.args = args

const component: ComponentMeta<typeof PossibleImpersonationAttackModalComponent> = {
title: 'Components/PossibleImpersonationAttackModalComponent',
decorators: [withTheme],
component: PossibleImpersonationAttackModalComponent,
}

export default component
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import React from 'react'
import theme from '../../../theme'
import { ThemeProvider } from '@mui/material/styles'
import { renderComponent } from '../../../testUtils/renderComponent'
import PossibleImpersonationAttackModalComponent from './PossibleImpersonationAttackModal.component'

describe('PossibleImpersonationAttackModal', () => {
it('renderComponent', () => {
const result = renderComponent(
<ThemeProvider theme={theme}>
<PossibleImpersonationAttackModalComponent
handleClose={() => {}}
open={true}
communityName={'devteam'}
leaveCommunity={() => {}}
/>
</ThemeProvider>
)
expect(result.baseElement).toMatchInlineSnapshot(`
<body
style="padding-right: 1024px; overflow: hidden;"
>
<div
aria-hidden="true"
/>
<div
class="MuiModal-root css-1vjugmr-MuiModal-root"
role="presentation"
zindex="1300"
>
<div
aria-hidden="true"
class="MuiBackdrop-root css-i9fmh8-MuiBackdrop-root-MuiModal-backdrop"
style="opacity: 1; webkit-transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; transition: opacity 225ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;"
/>
<div
data-testid="sentinelStart"
tabindex="0"
/>
<div
class="MuiGrid-root MuiGrid-container MuiGrid-direction-xs-column Modalcentered css-6gh8l0-MuiGrid-root"
tabindex="-1"
>
<div
class="MuiGrid-root MuiGrid-container MuiGrid-item Modalheader ModalheaderBorder css-lx31tv-MuiGrid-root"
>
<div
class="MuiGrid-root MuiGrid-container MuiGrid-item MuiGrid-grid-xs-true css-1r61agb-MuiGrid-root"
>
<div
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-true css-1vd824g-MuiGrid-root"
>
<h6
class="MuiTypography-root MuiTypography-subtitle1 MuiTypography-alignCenter Modaltitle Modalbold css-jxzupi-MuiTypography-root"
style="margin-left: 36px;"
>
Warning!
</h6>
</div>
<div
class="MuiGrid-root MuiGrid-item css-13i4rnv-MuiGrid-root"
>
<div
class="MuiGrid-root MuiGrid-container MuiGrid-item Modalactions css-hoc6b0-MuiGrid-root"
data-testid="ModalActions"
>
<button
class="MuiButtonBase-root MuiIconButton-root IconButtonroot MuiIconButton-sizeMedium css-c8hoqc-MuiButtonBase-root-MuiIconButton-root"
tabindex="0"
type="button"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root"
data-testid="ClearIcon"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
/>
</svg>
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</div>
<div
class="MuiGrid-root MuiGrid-container MuiGrid-item ModalfullPage css-1h16bbz-MuiGrid-root"
>
<div
class="MuiGrid-root MuiGrid-container MuiGrid-item Modalcontent css-1f064cs-MuiGrid-root"
style="width: 600px;"
>
<div
class="MuiGrid-root MuiGrid-container MuiGrid-item MuiGrid-direction-xs-column css-e4g1qd-MuiGrid-root"
data-testid="possible-impersonation-attack-modal-component"
>
<img
class="PossibleImpersonationAttackModalComponent-image"
src="test-file-stub"
/>
<h4
class="MuiTypography-root MuiTypography-h4 css-ajdqea-MuiTypography-root"
>
Possible impersonation attack
</h4>
<p
class="MuiTypography-root MuiTypography-body2 PossibleImpersonationAttackModalComponent-bodyText css-16d47hw-MuiTypography-root"
>
The owner of
<strong>
devteam
</strong>
has registered an invalid username. Either something is very broken, the community owner is trying to impersonate other users, or the community owner has been hacked.
<br />
<strong>
This should never happen and we recommend leaving this community immediately!
</strong>
</p>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium PossibleImpersonationAttackModalComponent-button css-1skytee-MuiButtonBase-root-MuiButton-root"
data-testid="unregistered-button"
tabindex="0"
type="button"
>
Leave community
<span
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
</button>
</div>
</div>
</div>
</div>
<div
data-testid="sentinelEnd"
tabindex="0"
/>
</div>
</body>
`)
})
})
3 changes: 2 additions & 1 deletion packages/desktop/src/renderer/sagas/modals/modals.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ export class ModalsInitialState {
[ModalName.loadingPanel] = { open: true, args: {} }; // Loading modal is open by default and closes on websocket connection
[ModalName.channelCreationModal] = { open: false, args: {} };
[ModalName.unregisteredUsernameModal] = { open: false, args: {} };
[ModalName.duplicatedUsernameModal] = { open: false, args: {} }
[ModalName.duplicatedUsernameModal] = { open: false, args: {} };
[ModalName.possibleImpersonationAttackModal] = { open: false, args: {} }
}

export const modalsSlice = createSlice({
Expand Down
1 change: 1 addition & 0 deletions packages/desktop/src/renderer/sagas/modals/modals.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ export enum ModalName {
channelCreationModal = 'channelCreationModal',
duplicatedUsernameModal = 'duplicatedUsernameModal',
unregisteredUsernameModal = 'unregisteredUsernameModal',
possibleImpersonationAttackModal = 'possibleImpersonationAttackModal',
}
Loading

0 comments on commit d6242b9

Please sign in to comment.