diff --git a/src/components/views/settings/devices/CurrentDeviceSection.tsx b/src/components/views/settings/devices/CurrentDeviceSection.tsx index cebbed64e6..ca4e149490 100644 --- a/src/components/views/settings/devices/CurrentDeviceSection.tsx +++ b/src/components/views/settings/devices/CurrentDeviceSection.tsx @@ -28,10 +28,11 @@ import { DeviceWithVerification } from './types'; interface Props { device?: DeviceWithVerification; isLoading: boolean; + onVerifyCurrentDevice: () => void; } const CurrentDeviceSection: React.FC = ({ - device, isLoading, + device, isLoading, onVerifyCurrentDevice, }) => { const [isExpanded, setIsExpanded] = useState(false); @@ -52,7 +53,7 @@ const CurrentDeviceSection: React.FC = ({ { isExpanded && }
- + } ; diff --git a/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx b/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx index a59fd64d63..11e806e54e 100644 --- a/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx +++ b/src/components/views/settings/devices/DeviceVerificationStatusCard.tsx @@ -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 = ({ 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 = ({ }; return ; + > + { !device.isVerified && !!onVerifyDevice && + + { _t('Verify session') } + + } + ; }; diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts index ec5ee1ca18..7252b053b3 100644 --- a/src/components/views/settings/devices/useOwnDevices.ts +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -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; error?: OwnDevicesError; }; export const useOwnDevices = (): DevicesState => { @@ -75,30 +76,32 @@ export const useOwnDevices = (): DevicesState => { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(); - 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, }; diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index 07f60e2725..3b6cefc15b 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -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(); const [expandedDeviceIds, setExpandedDeviceIds] = useState([]); const filteredDeviceListRef = useRef(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 = () => { { shouldShowOtherSessions && diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index c2ee03575d..e0d68b5a87 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -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", diff --git a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx index 966db9f7f2..197e50a4bf 100644 --- a/test/components/views/settings/devices/CurrentDeviceSection-test.tsx +++ b/test/components/views/settings/devices/CurrentDeviceSection-test.tsx @@ -34,6 +34,7 @@ describe('', () => { const defaultProps = { device: alicesVerifiedDevice, + onVerifyCurrentDevice: jest.fn(), isLoading: false, }; const getComponent = (props = {}): React.ReactElement => diff --git a/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap index 043fadc016..d65122f82f 100644 --- a/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/CurrentDeviceSection-test.tsx.snap @@ -216,6 +216,18 @@ exports[` renders device and correct security card when > Verify or sign out from this session for best security and reliability.

+
+
+ Verify session +
+
@@ -316,6 +328,18 @@ exports[` renders device and correct security card when > Verify or sign out from this session for best security and reliability.

+
+
+ Verify session +
+
diff --git a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx index aad6fbcd38..183aba540d 100644 --- a/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx +++ b/test/components/views/settings/tabs/user/SessionManagerTab-test.tsx @@ -28,6 +28,7 @@ import { getMockClientWithEventEmitter, mockClientMethodsUser, } from '../../../../../test-utils'; +import Modal from '../../../../../../src/Modal'; jest.useFakeTimers(); @@ -154,6 +155,21 @@ describe('', () => { 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)); diff --git a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap index 468556195f..ad796130e6 100644 --- a/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap +++ b/test/components/views/settings/tabs/user/__snapshots__/SessionManagerTab-test.tsx.snap @@ -226,6 +226,18 @@ exports[` renders current session section with an unverifie > Verify or sign out from this session for best security and reliability.

+
+
+ Verify session +
+