Device manager - add verify current session button (PSG-527) (#9252)

* add verify current session button

* i18n

* strict type issues
This commit is contained in:
Kerry 2022-09-08 17:35:53 +02:00 committed by GitHub
parent 8bc03aabba
commit 61904778f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 113 additions and 25 deletions

View file

@ -28,10 +28,11 @@ import { DeviceWithVerification } from './types';
interface Props { interface Props {
device?: DeviceWithVerification; device?: DeviceWithVerification;
isLoading: boolean; isLoading: boolean;
onVerifyCurrentDevice: () => void;
} }
const CurrentDeviceSection: React.FC<Props> = ({ const CurrentDeviceSection: React.FC<Props> = ({
device, isLoading, device, isLoading, onVerifyCurrentDevice,
}) => { }) => {
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
@ -52,7 +53,7 @@ const CurrentDeviceSection: React.FC<Props> = ({
</DeviceTile> </DeviceTile>
{ isExpanded && <DeviceDetails device={device} /> } { isExpanded && <DeviceDetails device={device} /> }
<br /> <br />
<DeviceVerificationStatusCard device={device} /> <DeviceVerificationStatusCard device={device} onVerifyDevice={onVerifyCurrentDevice} />
</> </>
} }
</SettingsSubsection>; </SettingsSubsection>;

View file

@ -17,6 +17,7 @@ limitations under the License.
import React from 'react'; import React from 'react';
import { _t } from '../../../../languageHandler'; import { _t } from '../../../../languageHandler';
import AccessibleButton from '../../elements/AccessibleButton';
import DeviceSecurityCard from './DeviceSecurityCard'; import DeviceSecurityCard from './DeviceSecurityCard';
import { import {
DeviceSecurityVariation, DeviceSecurityVariation,
@ -25,12 +26,14 @@ import {
interface Props { interface Props {
device: DeviceWithVerification; device: DeviceWithVerification;
onVerifyDevice?: () => void;
} }
export const DeviceVerificationStatusCard: React.FC<Props> = ({ export const DeviceVerificationStatusCard: React.FC<Props> = ({
device, device,
onVerifyDevice,
}) => { }) => {
const securityCardProps = device?.isVerified ? { const securityCardProps = device.isVerified ? {
variation: DeviceSecurityVariation.Verified, variation: DeviceSecurityVariation.Verified,
heading: _t('Verified session'), heading: _t('Verified session'),
description: _t('This session is ready for secure messaging.'), description: _t('This session is ready for secure messaging.'),
@ -41,5 +44,15 @@ export const DeviceVerificationStatusCard: React.FC<Props> = ({
}; };
return <DeviceSecurityCard return <DeviceSecurityCard
{...securityCardProps} {...securityCardProps}
/>; >
{ !device.isVerified && !!onVerifyDevice &&
<AccessibleButton
kind='primary'
onClick={onVerifyDevice}
data-testid={`verification-status-button-${device.device_id}`}
>
{ _t('Verify session') }
</AccessibleButton>
}
</DeviceSecurityCard>;
}; };

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import { useContext, useEffect, useState } from "react"; import { useCallback, useContext, useEffect, useState } from "react";
import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix"; import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning"; import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
@ -64,6 +64,7 @@ type DevicesState = {
devices: DevicesDictionary; devices: DevicesDictionary;
currentDeviceId: string; currentDeviceId: string;
isLoading: boolean; isLoading: boolean;
refreshDevices: () => Promise<void>;
error?: OwnDevicesError; error?: OwnDevicesError;
}; };
export const useOwnDevices = (): DevicesState => { export const useOwnDevices = (): DevicesState => {
@ -75,8 +76,7 @@ export const useOwnDevices = (): DevicesState => {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<OwnDevicesError>(); const [error, setError] = useState<OwnDevicesError>();
useEffect(() => { const refreshDevices = useCallback(async () => {
const getDevicesAsync = async () => {
setIsLoading(true); setIsLoading(true);
try { try {
const devices = await fetchDevicesWithVerification(matrixClient); const devices = await fetchDevicesWithVerification(matrixClient);
@ -92,13 +92,16 @@ export const useOwnDevices = (): DevicesState => {
} }
setIsLoading(false); setIsLoading(false);
} }
};
getDevicesAsync();
}, [matrixClient]); }, [matrixClient]);
useEffect(() => {
refreshDevices();
}, [refreshDevices]);
return { return {
devices, devices,
currentDeviceId, currentDeviceId,
refreshDevices,
isLoading, isLoading,
error, error,
}; };

View file

@ -24,9 +24,16 @@ import CurrentDeviceSection from '../../devices/CurrentDeviceSection';
import SecurityRecommendations from '../../devices/SecurityRecommendations'; import SecurityRecommendations from '../../devices/SecurityRecommendations';
import { DeviceSecurityVariation, DeviceWithVerification } from '../../devices/types'; import { DeviceSecurityVariation, DeviceWithVerification } from '../../devices/types';
import SettingsTab from '../SettingsTab'; import SettingsTab from '../SettingsTab';
import Modal from '../../../../../Modal';
import SetupEncryptionDialog from '../../../dialogs/security/SetupEncryptionDialog';
const SessionManagerTab: React.FC = () => { const SessionManagerTab: React.FC = () => {
const { devices, currentDeviceId, isLoading } = useOwnDevices(); const {
devices,
currentDeviceId,
isLoading,
refreshDevices,
} = useOwnDevices();
const [filter, setFilter] = useState<DeviceSecurityVariation>(); const [filter, setFilter] = useState<DeviceSecurityVariation>();
const [expandedDeviceIds, setExpandedDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]); const [expandedDeviceIds, setExpandedDeviceIds] = useState<DeviceWithVerification['device_id'][]>([]);
const filteredDeviceListRef = useRef<HTMLDivElement>(null); const filteredDeviceListRef = useRef<HTMLDivElement>(null);
@ -57,6 +64,16 @@ const SessionManagerTab: React.FC = () => {
const { [currentDeviceId]: currentDevice, ...otherDevices } = devices; const { [currentDeviceId]: currentDevice, ...otherDevices } = devices;
const shouldShowOtherSessions = Object.keys(otherDevices).length > 0; const shouldShowOtherSessions = Object.keys(otherDevices).length > 0;
const onVerifyCurrentDevice = () => {
if (!currentDevice) {
return;
}
Modal.createDialog(
SetupEncryptionDialog as unknown as React.ComponentType,
{ onFinished: refreshDevices },
);
};
useEffect(() => () => { useEffect(() => () => {
clearTimeout(scrollIntoViewTimeoutRef.current); clearTimeout(scrollIntoViewTimeoutRef.current);
}, [scrollIntoViewTimeoutRef]); }, [scrollIntoViewTimeoutRef]);
@ -70,6 +87,7 @@ const SessionManagerTab: React.FC = () => {
<CurrentDeviceSection <CurrentDeviceSection
device={currentDevice} device={currentDevice}
isLoading={isLoading} isLoading={isLoading}
onVerifyCurrentDevice={onVerifyCurrentDevice}
/> />
{ {
shouldShowOtherSessions && shouldShowOtherSessions &&

View file

@ -1720,6 +1720,7 @@
"This session is ready for secure messaging.": "This session is ready for secure messaging.", "This session is ready for secure messaging.": "This session is ready for secure messaging.",
"Unverified session": "Unverified session", "Unverified session": "Unverified session",
"Verify or sign out from this session for best security and reliability.": "Verify or sign out from this session for best security and reliability.", "Verify or sign out from this session for best security and reliability.": "Verify or sign out from this session for best security and reliability.",
"Verify session": "Verify session",
"Verified sessions": "Verified sessions", "Verified sessions": "Verified sessions",
"For best security, sign out from any session that you don't recognize or use anymore.": "For best security, sign out from any session that you don't recognize or use anymore.", "For best security, sign out from any session that you don't recognize or use anymore.": "For best security, sign out from any session that you don't recognize or use anymore.",
"Unverified sessions": "Unverified sessions", "Unverified sessions": "Unverified sessions",
@ -2766,7 +2767,6 @@
"Session name": "Session name", "Session name": "Session name",
"Session key": "Session key", "Session key": "Session key",
"If they don't match, the security of your communication may be compromised.": "If they don't match, the security of your communication may be compromised.", "If they don't match, the security of your communication may be compromised.": "If they don't match, the security of your communication may be compromised.",
"Verify session": "Verify session",
"Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.", "Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.",
"Message edits": "Message edits", "Message edits": "Message edits",
"Modal Widget": "Modal Widget", "Modal Widget": "Modal Widget",

View file

@ -34,6 +34,7 @@ describe('<CurrentDeviceSection />', () => {
const defaultProps = { const defaultProps = {
device: alicesVerifiedDevice, device: alicesVerifiedDevice,
onVerifyCurrentDevice: jest.fn(),
isLoading: false, isLoading: false,
}; };
const getComponent = (props = {}): React.ReactElement => const getComponent = (props = {}): React.ReactElement =>

View file

@ -216,6 +216,18 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
> >
Verify or sign out from this session for best security and reliability. Verify or sign out from this session for best security and reliability.
</p> </p>
<div
class="mx_DeviceSecurityCard_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="verification-status-button-alices_device"
role="button"
tabindex="0"
>
Verify session
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -316,6 +328,18 @@ exports[`<CurrentDeviceSection /> renders device and correct security card when
> >
Verify or sign out from this session for best security and reliability. Verify or sign out from this session for best security and reliability.
</p> </p>
<div
class="mx_DeviceSecurityCard_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="verification-status-button-alices_device"
role="button"
tabindex="0"
>
Verify session
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -28,6 +28,7 @@ import {
getMockClientWithEventEmitter, getMockClientWithEventEmitter,
mockClientMethodsUser, mockClientMethodsUser,
} from '../../../../../test-utils'; } from '../../../../../test-utils';
import Modal from '../../../../../../src/Modal';
jest.useFakeTimers(); jest.useFakeTimers();
@ -154,6 +155,21 @@ describe('<SessionManagerTab />', () => {
expect(getByTestId('current-session-section')).toMatchSnapshot(); expect(getByTestId('current-session-section')).toMatchSnapshot();
}); });
it('opens encryption setup dialog when verifiying current session', async () => {
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
const { getByTestId } = render(getComponent());
const modalSpy = jest.spyOn(Modal, 'createDialog');
await act(async () => {
await flushPromisesWithFakeTimers();
});
// click verify button from current session section
fireEvent.click(getByTestId(`verification-status-button-${alicesDevice.device_id}`));
expect(modalSpy).toHaveBeenCalled();
});
it('renders current session section with a verified session', async () => { it('renders current session section with a verified session', async () => {
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] }); mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
mockClient.getStoredDevice.mockImplementation(() => new DeviceInfo(alicesDevice.device_id)); mockClient.getStoredDevice.mockImplementation(() => new DeviceInfo(alicesDevice.device_id));

View file

@ -226,6 +226,18 @@ exports[`<SessionManagerTab /> renders current session section with an unverifie
> >
Verify or sign out from this session for best security and reliability. Verify or sign out from this session for best security and reliability.
</p> </p>
<div
class="mx_DeviceSecurityCard_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="verification-status-button-alices_device"
role="button"
tabindex="0"
>
Verify session
</div>
</div>
</div> </div>
</div> </div>
</div> </div>