Device manager - add verify current session button (PSG-527) (#9252)
* add verify current session button * i18n * strict type issues
This commit is contained in:
parent
8bc03aabba
commit
61904778f5
9 changed files with 113 additions and 25 deletions
|
@ -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>;
|
||||||
|
|
|
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 &&
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 =>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue