From cdc8032f25798ca1806bace790e600b3d569eeb8 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 29 Jul 2022 12:27:56 +0200 Subject: [PATCH 1/8] add session manager tab to user settings --- src/components/views/dialogs/UserSettingsDialog.tsx | 1 + src/components/views/settings/tabs/user/SessionManagerTab.tsx | 2 +- src/i18n/strings/en_EN.json | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index e15144969d1..3e6991cb003 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -37,6 +37,7 @@ import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab"; import SessionManagerTab from '../settings/tabs/user/SessionManagerTab'; import { UserTab } from "./UserTab"; +import SessionManagerTab from '../settings/tabs/user/SessionManagerTab'; interface IProps extends IDialogProps { initialTabId?: UserTab; diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index 17c09aeb7a3..33c00f95c38 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -1,5 +1,5 @@ /* -Copyright 2022 The Matrix.org Foundation C.I.C. +Copyright 2019 - 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e601003ecb4..1fc72beed88 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2845,6 +2845,7 @@ "Upload %(count)s other files|one": "Upload %(count)s other file", "Cancel All": "Cancel All", "Upload Error": "Upload Error", + "Sessions": "Sessions", "Verify other device": "Verify other device", "Verification Request": "Verification Request", "Approve widget permissions": "Approve widget permissions", From 5c982763d3c24787c281fef6baf6a5ae004694ba Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Fri, 29 Jul 2022 14:21:14 +0200 Subject: [PATCH 2/8] fussy import ordering --- src/components/views/dialogs/UserSettingsDialog.tsx | 1 - src/components/views/settings/tabs/user/SessionManagerTab.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/dialogs/UserSettingsDialog.tsx b/src/components/views/dialogs/UserSettingsDialog.tsx index 3e6991cb003..e15144969d1 100644 --- a/src/components/views/dialogs/UserSettingsDialog.tsx +++ b/src/components/views/dialogs/UserSettingsDialog.tsx @@ -37,7 +37,6 @@ import SidebarUserSettingsTab from "../settings/tabs/user/SidebarUserSettingsTab import KeyboardUserSettingsTab from "../settings/tabs/user/KeyboardUserSettingsTab"; import SessionManagerTab from '../settings/tabs/user/SessionManagerTab'; import { UserTab } from "./UserTab"; -import SessionManagerTab from '../settings/tabs/user/SessionManagerTab'; interface IProps extends IDialogProps { initialTabId?: UserTab; diff --git a/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/src/components/views/settings/tabs/user/SessionManagerTab.tsx index 33c00f95c38..17c09aeb7a3 100644 --- a/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -1,5 +1,5 @@ /* -Copyright 2019 - 2022 The Matrix.org Foundation C.I.C. +Copyright 2022 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From d6feed16f1ec20a09a9560b421c59b78ca4d2903 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 8 Aug 2022 08:49:03 +0200 Subject: [PATCH 3/8] i18n --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1fc72beed88..e601003ecb4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2845,7 +2845,6 @@ "Upload %(count)s other files|one": "Upload %(count)s other file", "Cancel All": "Cancel All", "Upload Error": "Upload Error", - "Sessions": "Sessions", "Verify other device": "Verify other device", "Verification Request": "Verification Request", "Approve widget permissions": "Approve widget permissions", From a542361670b03b6bb9be3da440f2aca3f9ab9255 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 8 Aug 2022 17:10:00 +0200 Subject: [PATCH 4/8] extract device fetching logic into hook --- .../views/settings/devices/useOwnDevices.ts | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/components/views/settings/devices/useOwnDevices.ts diff --git a/src/components/views/settings/devices/useOwnDevices.ts b/src/components/views/settings/devices/useOwnDevices.ts new file mode 100644 index 00000000000..ad9523cc14f --- /dev/null +++ b/src/components/views/settings/devices/useOwnDevices.ts @@ -0,0 +1,105 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { 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"; + +import MatrixClientContext from "../../../../contexts/MatrixClientContext"; + +export type DeviceWithVerification = IMyDevice & { isVerified: boolean | null }; + +const isDeviceVerified = ( + matrixClient: MatrixClient, + crossSigningInfo: CrossSigningInfo, + device: IMyDevice, +): boolean | null => { + try { + const deviceInfo = matrixClient.getStoredDevice(matrixClient.getUserId(), device.device_id); + return crossSigningInfo.checkDeviceTrust( + crossSigningInfo, + deviceInfo, + false, + true, + ).isCrossSigningVerified(); + } catch (error) { + logger.error("Error getting device cross-signing info", error); + return null; + } +}; + +const fetchDevicesWithVerification = async (matrixClient: MatrixClient): Promise => { + const { devices } = await matrixClient.getDevices(); + const crossSigningInfo = matrixClient.getStoredCrossSigningForUser(matrixClient.getUserId()); + + const devicesDict = devices.reduce((acc, device: IMyDevice) => ({ + ...acc, + [device.device_id]: { + ...device, + isVerified: isDeviceVerified(matrixClient, crossSigningInfo, device), + }, + }), {}); + + return devicesDict; +}; +export enum OwnDevicesError { + Unsupported = 'Unsupported', + Default = 'Default', +} +type DevicesState = { + devices: Record; + currentDeviceId: string; + isLoading: boolean; + error?: OwnDevicesError; +}; +export const useOwnDevices = (): DevicesState => { + const matrixClient = useContext(MatrixClientContext); + + const currentDeviceId = matrixClient.getDeviceId(); + + const [devices, setDevices] = useState({}); + 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); + } + }; + getDevicesAsync(); + }, [matrixClient]); + + return { + devices, + currentDeviceId, + isLoading, + error, + }; +}; From 66ccbb0bcae4f68d593adc0323d09ccb86566934 Mon Sep 17 00:00:00 2001 From: Kerry Archibald Date: Mon, 8 Aug 2022 17:10:42 +0200 Subject: [PATCH 5/8] use new extended device type in device tile, add verified metadata --- .../views/settings/DevicesPanelEntry.tsx | 9 +++- .../views/settings/devices/DeviceTile.tsx | 10 ++-- .../settings/devices/DeviceTile-test.tsx | 6 +++ .../__snapshots__/DeviceTile-test.tsx.snap | 54 +++++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/components/views/settings/DevicesPanelEntry.tsx b/src/components/views/settings/DevicesPanelEntry.tsx index b0301214b9b..beb7a8e86e6 100644 --- a/src/components/views/settings/DevicesPanelEntry.tsx +++ b/src/components/views/settings/DevicesPanelEntry.tsx @@ -154,12 +154,17 @@ export default class DevicesPanelEntry extends React.Component { ; + const deviceWithVerification = { + ...this.props.device, + isVerified: this.props.verified, + }; + if (this.props.isOwnDevice) { return
- + { buttons }
; @@ -167,7 +172,7 @@ export default class DevicesPanelEntry extends React.Component { return (
- + { buttons }
diff --git a/src/components/views/settings/devices/DeviceTile.tsx b/src/components/views/settings/devices/DeviceTile.tsx index 33f9fc40a85..9e9a520fcea 100644 --- a/src/components/views/settings/devices/DeviceTile.tsx +++ b/src/components/views/settings/devices/DeviceTile.tsx @@ -15,21 +15,21 @@ limitations under the License. */ import React, { Fragment } from "react"; -import { IMyDevice } from "matrix-js-sdk/src/matrix"; import { _t } from "../../../../languageHandler"; import { formatDate, formatRelativeTime } from "../../../../DateUtils"; import TooltipTarget from "../../elements/TooltipTarget"; import { Alignment } from "../../elements/Tooltip"; import Heading from "../../typography/Heading"; +import { DeviceWithVerification } from "./useOwnDevices"; export interface DeviceTileProps { - device: IMyDevice; + device: DeviceWithVerification; children?: React.ReactNode; onClick?: () => void; } -const DeviceTileName: React.FC<{ device: IMyDevice }> = ({ device }) => { +const DeviceTileName: React.FC<{ device: DeviceWithVerification }> = ({ device }) => { if (device.display_name) { return = ({ value, id }) const DeviceTile: React.FC = ({ device, children, onClick }) => { const lastActivity = device.last_seen_ts && `${_t('Last activity')} ${formatLastActivity(device.last_seen_ts)}`; + const verificationStatus = device.isVerified ? _t('Verified') : _t('Unverified'); const metadata = [ + { id: 'isVerified', value: verificationStatus }, { id: 'lastActivity', value: lastActivity }, { id: 'lastSeenIp', value: device.last_seen_ip }, ]; - return
+ return
diff --git a/test/components/views/settings/devices/DeviceTile-test.tsx b/test/components/views/settings/devices/DeviceTile-test.tsx index d688eca9135..4083945fd61 100644 --- a/test/components/views/settings/devices/DeviceTile-test.tsx +++ b/test/components/views/settings/devices/DeviceTile-test.tsx @@ -24,6 +24,7 @@ describe('', () => { const defaultProps = { device: { device_id: '123', + isVerified: false, }, }; const getComponent = (props = {}) => ( @@ -43,6 +44,11 @@ describe('', () => { expect(container).toMatchSnapshot(); }); + it('renders a verified device with no metadata', () => { + const { container } = render(getComponent()); + expect(container).toMatchSnapshot(); + }); + it('renders display name with a tooltip', () => { const device: IMyDevice = { device_id: '123', diff --git a/test/components/views/settings/devices/__snapshots__/DeviceTile-test.tsx.snap b/test/components/views/settings/devices/__snapshots__/DeviceTile-test.tsx.snap index 299d72348c8..cafd47a8a74 100644 --- a/test/components/views/settings/devices/__snapshots__/DeviceTile-test.tsx.snap +++ b/test/components/views/settings/devices/__snapshots__/DeviceTile-test.tsx.snap @@ -4,6 +4,7 @@ exports[` renders a device with no metadata 1`] = `
renders a device with no metadata 1`] = ` +
+
+
+
+`; + +exports[` renders a verified device with no metadata 1`] = ` +
+
+
+

+ 123 +

+
@@ -30,6 +70,7 @@ exports[` renders display name with a tooltip 1`] = `
renders display name with a tooltip 1`] = `
@@ -60,6 +107,7 @@ exports[` separates metadata with a dot 1`] = `
separates metadata with a dot 1`] = `