mirror of https://github.com/vector-im/riot-web
Device manager - add verify current session button (PSG-527) (#9252)
* add verify current session button * i18n * strict type issuest3chguy/dedup-icons-17oct
parent
8bc03aabba
commit
61904778f5
|
@ -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>;
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -34,6 +34,7 @@ describe('<CurrentDeviceSection />', () => {
|
|||
|
||||
const defaultProps = {
|
||||
device: alicesVerifiedDevice,
|
||||
onVerifyCurrentDevice: jest.fn(),
|
||||
isLoading: false,
|
||||
};
|
||||
const getComponent = (props = {}): React.ReactElement =>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue