Skip to content

Commit

Permalink
ThreatModal: Add user connection gate (#40204)
Browse files Browse the repository at this point in the history
* Add user connection gate

* Fix stories

* ThreatModal: Add credentials gate (#40205)

* Add credentials gate

* Fix stories

* Fix credentials type

* Fix build issues, and add early returns for gates
  • Loading branch information
dkmyta authored Nov 19, 2024
1 parent 8fedbe8 commit 5a2cac3
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Text, Button } from '@automattic/jetpack-components';
import { Notice } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import React, { ReactElement } from 'react';
import styles from './styles.module.scss';

const CredentialsGate = ( {
siteCredentialsNeeded,
credentialsIsFetching,
credentialsRedirectUrl,
children,
}: {
siteCredentialsNeeded: boolean;
credentialsIsFetching: boolean;
credentialsRedirectUrl: string;
children: ReactElement;
} ): JSX.Element => {
if ( ! siteCredentialsNeeded ) {
return children;
}

return (
<>
<Notice
status="warning"
isDismissible={ false }
children={
<Text>
{ __(
'Before Jetpack can auto-fix threats on your site, it needs your server credentials.',
'jetpack'
) }
</Text>
}
/>

<Text>
{ __(
'Your server credentials allow Jetpack to access the server that’s powering your website. This information is securely saved and only used to perform fix threats detected on your site.',
'jetpack'
) }
</Text>

<Text>
{ __(
'Once you’ve entered server credentials, Jetpack will be fixing the selected threats.',
'jetpack'
) }
</Text>

<div className={ styles[ 'modal-actions' ] }>
<Button
isExternalLink={ true }
weight="regular"
href={ credentialsRedirectUrl }
isLoading={ credentialsIsFetching }
>
{ __( 'Enter server credentials', 'jetpack' ) }
</Button>
</div>
</>
);
};

export default CredentialsGate;
144 changes: 98 additions & 46 deletions projects/js-packages/components/components/threat-modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@ import { Modal, Notice } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useMemo } from 'react';
import Text from '../text';
import CredentialsGate from './credentials-gate';
import styles from './styles.module.scss';
import ThreatActions from './threat-actions';
import ThreatFixDetails from './threat-fix-details';
import ThreatTechnicalDetails from './threat-technical-details';
import UserConnectionGate from './user-connection-gate';

/**
* ThreatModal component
*
* @param {object} props - The props.
* @param {object} props.threat - The threat.
* @param {boolean} props.isUserConnected - Whether the user is connected.
* @param {boolean} props.hasConnectedOwner - Whether the user has a connected owner.
* @param {boolean} props.userIsConnecting - Whether the user is connecting.
* @param {Function} props.handleConnectUser - The handleConnectUser function.
* @param {object} props.credentials - The credentials.
* @param {boolean} props.credentialsIsFetching - Whether the credentials are fetching.
* @param {string} props.credentialsRedirectUrl - The credentials redirect URL.
* @param {Function} props.handleUpgradeClick - The handleUpgradeClick function.
* @param {Function} props.handleFixThreatClick - The handleFixThreatClick function.
* @param {Function} props.handleIgnoreThreatClick - The handleIgnoreThreatClick function.
Expand All @@ -23,78 +32,121 @@ import ThreatTechnicalDetails from './threat-technical-details';
*/
export default function ThreatModal( {
threat,
isUserConnected,
hasConnectedOwner,
userIsConnecting,
handleConnectUser,
credentials,
credentialsIsFetching,
credentialsRedirectUrl,
handleUpgradeClick,
handleFixThreatClick,
handleIgnoreThreatClick,
handleUnignoreThreatClick,
...modalProps
}: {
threat: Threat;
isUserConnected: boolean;
hasConnectedOwner: boolean;
userIsConnecting: boolean;
handleConnectUser: () => void;
credentials: false | Record< string, unknown >[];
credentialsIsFetching: boolean;
credentialsRedirectUrl: string;
handleUpgradeClick?: () => void;
handleFixThreatClick?: ( threats: Threat[] ) => void;
handleIgnoreThreatClick?: ( threats: Threat[] ) => void;
handleUnignoreThreatClick?: ( threats: Threat[] ) => void;
} & React.ComponentProps< typeof Modal > ): JSX.Element {
const userConnectionNeeded = ! isUserConnected || ! hasConnectedOwner;
const siteCredentialsNeeded = ! credentials || credentials.length === 0;

const fixerState = useMemo( () => {
return getFixerState( threat.fixer );
}, [ threat.fixer ] );

const getModalTitle = useMemo( () => {
if ( userConnectionNeeded ) {
return <Text variant="title-small">{ __( 'User connection needed', 'jetpack' ) }</Text>;
}

if ( siteCredentialsNeeded ) {
return <Text variant="title-small">{ __( 'Site credentials needed', 'jetpack' ) }</Text>;
}

return (
<>
<Text variant="title-small">{ threat.title }</Text>
{ !! threat.severity && <ThreatSeverityBadge severity={ threat.severity } /> }
</>
);
}, [ userConnectionNeeded, siteCredentialsNeeded, threat.title, threat.severity ] );

return (
<Modal
size="large"
title={
<div className={ styles.title }>
<Text variant="title-small">{ threat.title }</Text>
{ !! threat.severity && <ThreatSeverityBadge severity={ threat.severity } /> }
</div>
}
title={ <div className={ styles.title }>{ getModalTitle }</div> }
{ ...modalProps }
>
<div className={ styles[ 'threat-details' ] }>
{ fixerState.error && (
<Notice isDismissible={ false } status="error">
<Text>{ __( 'An error occurred auto-fixing this threat.', 'jetpack' ) }</Text>
</Notice>
) }
{ fixerState.stale && (
<Notice isDismissible={ false } status="error">
<Text>{ __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) }</Text>
</Notice>
) }
{ fixerState.inProgress && ! fixerState.stale && (
<Notice isDismissible={ false } status="success">
<Text>{ __( 'The auto-fixer is in progress.', 'jetpack' ) }</Text>
</Notice>
) }
<div className={ styles.section }>
{ !! threat.description && <Text>{ threat.description }</Text> }
<UserConnectionGate
userConnectionNeeded={ userConnectionNeeded }
userIsConnecting={ userIsConnecting }
handleConnectUser={ handleConnectUser }
>
<CredentialsGate
siteCredentialsNeeded={ siteCredentialsNeeded }
credentialsIsFetching={ credentialsIsFetching }
credentialsRedirectUrl={ credentialsRedirectUrl }
>
<>
{ fixerState.error && (
<Notice isDismissible={ false } status="error">
<Text>{ __( 'An error occurred auto-fixing this threat.', 'jetpack' ) }</Text>
</Notice>
) }
{ fixerState.stale && (
<Notice isDismissible={ false } status="error">
<Text>{ __( 'The auto-fixer is taking longer than expected.', 'jetpack' ) }</Text>
</Notice>
) }
{ fixerState.inProgress && ! fixerState.stale && (
<Notice isDismissible={ false } status="success">
<Text>{ __( 'The auto-fixer is in progress.', 'jetpack' ) }</Text>
</Notice>
) }
<div className={ styles.section }>
{ !! threat.description && <Text>{ threat.description }</Text> }

{ !! threat.source && (
<div>
<Button
variant="link"
isExternalLink={ true }
weight="regular"
href={ threat.source }
>
{ __( 'See more technical details of this threat', 'jetpack' ) }
</Button>
</div>
) }
</div>
{ !! threat.source && (
<div>
<Button
variant="link"
isExternalLink={ true }
weight="regular"
href={ threat.source }
>
{ __( 'See more technical details of this threat', 'jetpack' ) }
</Button>
</div>
) }
</div>

<ThreatFixDetails threat={ threat } handleUpgradeClick={ handleUpgradeClick } />
<ThreatFixDetails threat={ threat } handleUpgradeClick={ handleUpgradeClick } />

<ThreatTechnicalDetails threat={ threat } />
<ThreatTechnicalDetails threat={ threat } />

<ThreatActions
threat={ threat }
closeModal={ modalProps.onRequestClose }
handleFixThreatClick={ handleFixThreatClick }
handleIgnoreThreatClick={ handleIgnoreThreatClick }
handleUnignoreThreatClick={ handleUnignoreThreatClick }
fixerState={ fixerState }
/>
<ThreatActions
threat={ threat }
closeModal={ modalProps.onRequestClose }
handleFixThreatClick={ handleFixThreatClick }
handleIgnoreThreatClick={ handleIgnoreThreatClick }
handleUnignoreThreatClick={ handleUnignoreThreatClick }
fixerState={ fixerState }
/>
</>
</CredentialsGate>
</UserConnectionGate>
</div>
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,71 @@ ThreatResult.args = {
marks: {},
},
},
isUserConnected: true,
hasConnectedOwner: true,
credentials: [ { type: 'managed', role: 'main', still_valid: true } ],
handleFixThreatClick: () => {},
handleIgnoreThreatClick: () => {},
handleUnignoreThreatClick: () => {},
};

export const UserConnectionNeeded = Base.bind( {} );
UserConnectionNeeded.args = {
threat: {
id: 185869885,
signature: 'EICAR_AV_Test',
title: 'Malicious code found in file: index.php',
description:
"This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.",
firstDetected: '2024-10-07T20:45:06.000Z',
fixedIn: null,
severity: 8,
fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' },
fixer: { status: 'not_started' },
status: 'current',
filename: '/var/www/html/wp-content/index.php',
context: {
'1': 'echo <<<HTML',
'2': 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*',
'3': 'HTML;',
marks: {},
},
},
isUserConnected: false,
hasConnectedOwner: false,
handleConnectUser: () => {},
handleFixThreatClick: () => {},
handleIgnoreThreatClick: () => {},
handleUnignoreThreatClick: () => {},
};

export const CredentialsNeeded = Base.bind( {} );
CredentialsNeeded.args = {
threat: {
id: 185869885,
signature: 'EICAR_AV_Test',
title: 'Malicious code found in file: index.php',
description:
"This is the standard EICAR antivirus test code, and not a real infection. If your site contains this code when you don't expect it to, contact Jetpack support for some help.",
firstDetected: '2024-10-07T20:45:06.000Z',
fixedIn: null,
severity: 8,
fixable: { fixer: 'rollback', target: 'January 26, 2024, 6:49 am', extensionStatus: '' },
fixer: { status: 'not_started' },
status: 'current',
filename: '/var/www/html/wp-content/index.php',
context: {
'1': 'echo <<<HTML',
'2': 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*',
'3': 'HTML;',
marks: {},
},
},
isUserConnected: true,
hasConnectedOwner: true,
credentials: false,
credentialsIsFetching: false,
credentialsRedirectUrl: '',
handleFixThreatClick: () => {},
handleIgnoreThreatClick: () => {},
handleUnignoreThreatClick: () => {},
Expand All @@ -63,5 +128,8 @@ VulnerableExtension.args = {
type: 'plugin',
},
},
isUserConnected: true,
hasConnectedOwner: true,
credentials: [ { type: 'managed', role: 'main', still_valid: true } ],
handleUpgradeClick: () => {},
};
Loading

0 comments on commit 5a2cac3

Please sign in to comment.