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

* add verify current session button

* i18n

* strict type issues
t3chguy/dedup-icons-17oct
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 {
device?: DeviceWithVerification;
isLoading: boolean;
onVerifyCurrentDevice: () => void;
}
const CurrentDeviceSection: React.FC<Props> = ({
device, isLoading,
device, isLoading, onVerifyCurrentDevice,
}) => {
const [isExpanded, setIsExpanded] = useState(false);
@ -52,7 +53,7 @@ const CurrentDeviceSection: React.FC<Props> = ({
</DeviceTile>
{ isExpanded && <DeviceDetails device={device} /> }
<br />
<DeviceVerificationStatusCard device={device} />
<DeviceVerificationStatusCard device={device} onVerifyDevice={onVerifyCurrentDevice} />
</>
}
</SettingsSubsection>;

View File

@ -17,6 +17,7 @@ limitations under the License.
import React from 'react';
import { _t } from '../../../../languageHandler';
import AccessibleButton from '../../elements/AccessibleButton';
import DeviceSecurityCard from './DeviceSecurityCard';
import {
DeviceSecurityVariation,
@ -25,12 +26,14 @@ import {
interface Props {
device: DeviceWithVerification;
onVerifyDevice?: () => void;
}
export const DeviceVerificationStatusCard: React.FC<Props> = ({
device,
onVerifyDevice,
}) => {
const securityCardProps = device?.isVerified ? {
const securityCardProps = device.isVerified ? {
variation: DeviceSecurityVariation.Verified,
heading: _t('Verified session'),
description: _t('This session is ready for secure messaging.'),
@ -41,5 +44,15 @@ export const DeviceVerificationStatusCard: React.FC<Props> = ({
};
return <DeviceSecurityCard
{...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.
*/
import { useContext, useEffect, useState } from "react";
import { useCallback, useContext, useEffect, useState } from "react";
import { IMyDevice, MatrixClient } from "matrix-js-sdk/src/matrix";
import { CrossSigningInfo } from "matrix-js-sdk/src/crypto/CrossSigning";
import { logger } from "matrix-js-sdk/src/logger";
@ -64,6 +64,7 @@ type DevicesState = {
devices: DevicesDictionary;
currentDeviceId: string;
isLoading: boolean;
refreshDevices: () => Promise<void>;
error?: OwnDevicesError;
};
export const useOwnDevices = (): DevicesState => {
@ -75,30 +76,32 @@ export const useOwnDevices = (): DevicesState => {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<OwnDevicesError>();
useEffect(() => {
const getDevicesAsync = async () => {
setIsLoading(true);
try {
const devices = await fetchDevicesWithVerification(matrixClient);
setDevices(devices);
setIsLoading(false);
} catch (error) {
if (error.httpStatus == 404) {
// 404 probably means the HS doesn't yet support the API.
setError(OwnDevicesError.Unsupported);
} else {
logger.error("Error loading sessions:", error);
setError(OwnDevicesError.Default);
}
setIsLoading(false);
const refreshDevices = useCallback(async () => {
setIsLoading(true);
try {
const devices = await fetchDevicesWithVerification(matrixClient);
setDevices(devices);
setIsLoading(false);
} catch (error) {
if (error.httpStatus == 404) {
// 404 probably means the HS doesn't yet support the API.
setError(OwnDevicesError.Unsupported);
} else {
logger.error("Error loading sessions:", error);
setError(OwnDevicesError.Default);
}
};
getDevicesAsync();
setIsLoading(false);
}
}, [matrixClient]);
useEffect(() => {
refreshDevices();
}, [refreshDevices]);
return {
devices,
currentDeviceId,
refreshDevices,
isLoading,
error,
};

View File

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

View File

@ -1720,6 +1720,7 @@
"This session is ready for secure messaging.": "This session is ready for secure messaging.",
"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 session": "Verify session",
"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.",
"Unverified sessions": "Unverified sessions",
@ -2766,7 +2767,6 @@
"Session name": "Session name",
"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.",
"Verify session": "Verify session",
"Your homeserver doesn't seem to support this feature.": "Your homeserver doesn't seem to support this feature.",
"Message edits": "Message edits",
"Modal Widget": "Modal Widget",

View File

@ -34,6 +34,7 @@ describe('<CurrentDeviceSection />', () => {
const defaultProps = {
device: alicesVerifiedDevice,
onVerifyCurrentDevice: jest.fn(),
isLoading: false,
};
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.
</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>
@ -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.
</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>

View File

@ -28,6 +28,7 @@ import {
getMockClientWithEventEmitter,
mockClientMethodsUser,
} from '../../../../../test-utils';
import Modal from '../../../../../../src/Modal';
jest.useFakeTimers();
@ -154,6 +155,21 @@ describe('<SessionManagerTab />', () => {
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 () => {
mockClient.getDevices.mockResolvedValue({ devices: [alicesDevice, alicesMobileDevice] });
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.
</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>