{_t("Messages in this room are end-to-end encrypted.")}
-
{_t("Your messages are secured and only you and the recipient have the unique keys to unlock them.")}
-
-
-
- { content }
-
- );
- }
-
- _onStartVerification = async () => {
- const client = MatrixClientPeg.get();
- const {member} = this.props;
- const roomId = await ensureDMExists(client, member.userId);
- const verificationRequest = await client.requestVerificationDM(member.userId, roomId);
- this.setState({verificationRequest});
- };
-}
+export default EncryptionPanel;
diff --git a/src/components/views/right_panel/UserInfo.js b/src/components/views/right_panel/UserInfo.js
index a31e9a6ce0..41dbe49cc8 100644
--- a/src/components/views/right_panel/UserInfo.js
+++ b/src/components/views/right_panel/UserInfo.js
@@ -41,6 +41,7 @@ import {useEventEmitter} from "../../../hooks/useEventEmitter";
import {textualPowerLevel} from '../../../Roles';
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import {RIGHT_PANEL_PHASES} from "../../../stores/RightPanelStorePhases";
+import EncryptionPanel from "./EncryptionPanel";
const _disambiguateDevices = (devices) => {
const names = Object.create(null);
@@ -1047,7 +1048,256 @@ const PowerLevelEditor = ({user, room, roomPermissions, onFinished}) => {
);
};
-export const UserInfoPane = ({children, className, onClose, e2eStatus, member}) => {
+export const useDevices = (userId) => {
+ const cli = useContext(MatrixClientContext);
+
+ // undefined means yet to be loaded, null means failed to load, otherwise list of devices
+ const [devices, setDevices] = useState(undefined);
+ // Download device lists
+ useEffect(() => {
+ setDevices(undefined);
+
+ let cancelled = false;
+
+ async function _downloadDeviceList() {
+ try {
+ await cli.downloadKeys([userId], true);
+ const devices = await cli.getStoredDevicesForUser(userId);
+
+ if (cancelled) {
+ // we got cancelled - presumably a different user now
+ return;
+ }
+
+ _disambiguateDevices(devices);
+ setDevices(devices);
+ } catch (err) {
+ setDevices(null);
+ }
+ }
+ _downloadDeviceList();
+
+ // Handle being unmounted
+ return () => {
+ cancelled = true;
+ };
+ }, [cli, userId]);
+
+ // Listen to changes
+ useEffect(() => {
+ let cancel = false;
+ const onDeviceVerificationChanged = (_userId, device) => {
+ if (_userId === userId) {
+ // no need to re-download the whole thing; just update our copy of the list.
+
+ // Promise.resolve to handle transition from static result to promise; can be removed in future
+ Promise.resolve(cli.getStoredDevicesForUser(userId)).then((devices) => {
+ if (cancel) return;
+ console.log("setDevices 2", devices);
+ setDevices(devices);
+ });
+ }
+ };
+ cli.on("deviceVerificationChanged", onDeviceVerificationChanged);
+ // Handle being unmounted
+ return () => {
+ cancel = true;
+ cli.removeListener("deviceVerificationChanged", onDeviceVerificationChanged);
+ };
+ }, [cli, userId]);
+
+ return devices;
+};
+
+const BasicUserInfo = ({room, member, groupId, devices, isRoomEncrypted}) => {
+ const cli = useContext(MatrixClientContext);
+
+ const powerLevels = useRoomPowerLevels(cli, room);
+ // Load whether or not we are a Synapse Admin
+ const isSynapseAdmin = useIsSynapseAdmin(cli);
+
+ // Check whether the user is ignored
+ const [isIgnored, setIsIgnored] = useState(cli.isUserIgnored(member.userId));
+ // Recheck if the user or client changes
+ useEffect(() => {
+ setIsIgnored(cli.isUserIgnored(member.userId));
+ }, [cli, member.userId]);
+ // Recheck also if we receive new accountData m.ignored_user_list
+ const accountDataHandler = useCallback((ev) => {
+ if (ev.getType() === "m.ignored_user_list") {
+ setIsIgnored(cli.isUserIgnored(member.userId));
+ }
+ }, [cli, member.userId]);
+ useEventEmitter(cli, "accountData", accountDataHandler);
+
+ // Count of how many operations are currently in progress, if > 0 then show a Spinner
+ const [pendingUpdateCount, setPendingUpdateCount] = useState(0);
+ const startUpdating = useCallback(() => {
+ setPendingUpdateCount(pendingUpdateCount + 1);
+ }, [pendingUpdateCount]);
+ const stopUpdating = useCallback(() => {
+ setPendingUpdateCount(pendingUpdateCount - 1);
+ }, [pendingUpdateCount]);
+
+ const roomPermissions = useRoomPermissions(cli, room, member);
+
+ const onSynapseDeactivate = useCallback(async () => {
+ const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
+ const {finished} = Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, {
+ title: _t("Deactivate user?"),
+ description:
+
{ _t(
+ "Deactivating this user will log them out and prevent them from logging back in. Additionally, " +
+ "they will leave all the rooms they are in. This action cannot be reversed. Are you sure you " +
+ "want to deactivate this user?",
+ ) }
,
+ button: _t("Deactivate user"),
+ danger: true,
+ });
+
+ const [accepted] = await finished;
+ if (!accepted) return;
+ try {
+ await cli.deactivateSynapseUser(member.userId);
+ } catch (err) {
+ console.error("Failed to deactivate user");
+ console.error(err);
+
+ const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
+ Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, {
+ title: _t('Failed to deactivate user'),
+ description: ((err && err.message) ? err.message : _t("Operation failed")),
+ });
+ }
+ }, [cli, member.userId]);
+
+ let synapseDeactivateButton;
+ let spinner;
+
+ // We don't need a perfect check here, just something to pass as "probably not our homeserver". If
+ // someone does figure out how to bypass this check the worst that happens is an error.
+ // FIXME this should be using cli instead of MatrixClientPeg.matrixClient
+ if (isSynapseAdmin && member.userId.endsWith(`:${MatrixClientPeg.getHomeserverName()}`)) {
+ synapseDeactivateButton = (
+
+ {_t("Deactivate user")}
+
+ );
+ }
+
+ let adminToolsContainer;
+ if (room && member.roomId) {
+ adminToolsContainer = (
+
+ { synapseDeactivateButton }
+
+ );
+ } else if (groupId) {
+ adminToolsContainer = (
+
+ { synapseDeactivateButton }
+
+ );
+ } else if (synapseDeactivateButton) {
+ adminToolsContainer = (
+
+ { synapseDeactivateButton }
+
+ );
+ }
+
+ if (pendingUpdateCount > 0) {
+ const Loader = sdk.getComponent("elements.Spinner");
+ spinner = ;
+ }
+
+ const memberDetails = (
+
+ );
+
+ // only display the devices list if our client supports E2E
+ const _enableDevices = cli.isCryptoEnabled();
+
+ let text;
+ if (!isRoomEncrypted) {
+ if (!_enableDevices) {
+ text = _t("This client does not support end-to-end encryption.");
+ } else if (room) {
+ text = _t("Messages in this room are not end-to-end encrypted.");
+ } else {
+ // TODO what to render for GroupMember
+ }
+ } else {
+ text = _t("Messages in this room are end-to-end encrypted.");
+ }
+
+ const userTrust = cli.checkUserTrust(member.userId);
+ const userVerified = SettingsStore.isFeatureEnabled("feature_cross_signing") ?
+ userTrust.isCrossSigningVerified() :
+ userTrust.isVerified();
+ const isMe = member.userId === cli.getUserId();
+ let verifyButton;
+ if (isRoomEncrypted && !userVerified && !isMe) {
+ verifyButton = (
+ verifyUser(member)}>
+ {_t("Verify")}
+
+ );
+ }
+
+ let devicesSection;
+ if (isRoomEncrypted) {
+ devicesSection = ;
+ }
+
+ const securitySection = (
+
- );
+ ;
};
-const UserInfo = ({user, groupId, roomId, onClose}) => {
+const UserInfo = ({user, groupId, roomId, onClose, phase=RIGHT_PANEL_PHASES.RoomMemberInfo, ...props}) => {
const cli = useContext(MatrixClientContext);
// Load room if we are given a room id and memoize it
@@ -1166,246 +1409,46 @@ const UserInfo = ({user, groupId, roomId, onClose}) => {
// fetch latest room member if we have a room, so we don't show historical information, falling back to user
const member = useMemo(() => room ? (room.getMember(user.userId) || user) : user, [room, user]);
- // only display the devices list if our client supports E2E
- const _enableDevices = cli.isCryptoEnabled();
-
- const powerLevels = useRoomPowerLevels(cli, room);
- // Load whether or not we are a Synapse Admin
- const isSynapseAdmin = useIsSynapseAdmin(cli);
-
- // Check whether the user is ignored
- const [isIgnored, setIsIgnored] = useState(cli.isUserIgnored(user.userId));
- // Recheck if the user or client changes
- useEffect(() => {
- setIsIgnored(cli.isUserIgnored(user.userId));
- }, [cli, user.userId]);
- // Recheck also if we receive new accountData m.ignored_user_list
- const accountDataHandler = useCallback((ev) => {
- if (ev.getType() === "m.ignored_user_list") {
- setIsIgnored(cli.isUserIgnored(user.userId));
- }
- }, [cli, user.userId]);
- useEventEmitter(cli, "accountData", accountDataHandler);
-
- // Count of how many operations are currently in progress, if > 0 then show a Spinner
- const [pendingUpdateCount, setPendingUpdateCount] = useState(0);
- const startUpdating = useCallback(() => {
- setPendingUpdateCount(pendingUpdateCount + 1);
- }, [pendingUpdateCount]);
- const stopUpdating = useCallback(() => {
- setPendingUpdateCount(pendingUpdateCount - 1);
- }, [pendingUpdateCount]);
-
- const roomPermissions = useRoomPermissions(cli, room, member);
-
- const onSynapseDeactivate = useCallback(async () => {
- const QuestionDialog = sdk.getComponent('views.dialogs.QuestionDialog');
- const {finished} = Modal.createTrackedDialog('Synapse User Deactivation', '', QuestionDialog, {
- title: _t("Deactivate user?"),
- description:
-
{ _t(
- "Deactivating this user will log them out and prevent them from logging back in. Additionally, " +
- "they will leave all the rooms they are in. This action cannot be reversed. Are you sure you " +
- "want to deactivate this user?",
- ) }
,
- button: _t("Deactivate user"),
- danger: true,
- });
-
- const [accepted] = await finished;
- if (!accepted) return;
- try {
- await cli.deactivateSynapseUser(user.userId);
- } catch (err) {
- console.error("Failed to deactivate user");
- console.error(err);
-
- const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog');
- Modal.createTrackedDialog('Failed to deactivate Synapse user', '', ErrorDialog, {
- title: _t('Failed to deactivate user'),
- description: ((err && err.message) ? err.message : _t("Operation failed")),
- });
- }
- }, [cli, user.userId]);
-
- let synapseDeactivateButton;
- let spinner;
-
- // We don't need a perfect check here, just something to pass as "probably not our homeserver". If
- // someone does figure out how to bypass this check the worst that happens is an error.
- // FIXME this should be using cli instead of MatrixClientPeg.matrixClient
- if (isSynapseAdmin && user.userId.endsWith(`:${MatrixClientPeg.getHomeserverName()}`)) {
- synapseDeactivateButton = (
-
- {_t("Deactivate user")}
-
- );
- }
-
- let adminToolsContainer;
- if (room && member.roomId) {
- adminToolsContainer = (
-
- { synapseDeactivateButton }
-
- );
- } else if (groupId) {
- adminToolsContainer = (
-
- { synapseDeactivateButton }
-
- );
- } else if (synapseDeactivateButton) {
- adminToolsContainer = (
-
- { synapseDeactivateButton }
-
- );
- }
-
- if (pendingUpdateCount > 0) {
- const Loader = sdk.getComponent("elements.Spinner");
- spinner = ;
- }
-
- const memberDetails = (
-
- );
-
const isRoomEncrypted = useIsEncrypted(cli, room);
- // undefined means yet to be loaded, null means failed to load, otherwise list of devices
- const [devices, setDevices] = useState(undefined);
- // Download device lists
- useEffect(() => {
- setDevices(undefined);
-
- let cancelled = false;
-
- async function _downloadDeviceList() {
- try {
- await cli.downloadKeys([user.userId], true);
- const devices = await cli.getStoredDevicesForUser(user.userId);
-
- if (cancelled) {
- // we got cancelled - presumably a different user now
- return;
- }
-
- _disambiguateDevices(devices);
- setDevices(devices);
- } catch (err) {
- setDevices(null);
- }
- }
- _downloadDeviceList();
-
- // Handle being unmounted
- return () => {
- cancelled = true;
- };
- }, [cli, user.userId]);
-
- // Listen to changes
- useEffect(() => {
- let cancel = false;
- const onDeviceVerificationChanged = (_userId, device) => {
- if (_userId === user.userId) {
- // no need to re-download the whole thing; just update our copy of the list.
-
- // Promise.resolve to handle transition from static result to promise; can be removed in future
- Promise.resolve(cli.getStoredDevicesForUser(user.userId)).then((devices) => {
- if (cancel) return;
- setDevices(devices);
- });
- }
- };
- cli.on("deviceVerificationChanged", onDeviceVerificationChanged);
- // Handle being unmounted
- return () => {
- cancel = true;
- cli.removeListener("deviceVerificationChanged", onDeviceVerificationChanged);
- };
- }, [cli, user.userId]);
-
- let text;
- if (!isRoomEncrypted) {
- if (!_enableDevices) {
- text = _t("This client does not support end-to-end encryption.");
- } else if (room) {
- text = _t("Messages in this room are not end-to-end encrypted.");
- } else {
- // TODO what to render for GroupMember
- }
- } else {
- text = _t("Messages in this room are end-to-end encrypted.");
- }
-
- const userTrust = cli.checkUserTrust(user.userId);
- const userVerified = SettingsStore.isFeatureEnabled("feature_cross_signing") ?
- userTrust.isCrossSigningVerified() :
- userTrust.isVerified();
- const isMe = user.userId === cli.getUserId();
- let verifyButton;
- if (isRoomEncrypted && !userVerified && !isMe) {
- verifyButton = verifyUser(user)}>
- {_t("Verify")}
- ;
- }
-
- let devicesSection;
- if (isRoomEncrypted) {
- devicesSection = ;
- }
-
- const securitySection = (
-
+ );
};
UserInfo.propTypes = {
diff --git a/src/components/views/right_panel/VerificationPanel.js b/src/components/views/right_panel/VerificationPanel.js
index 0d28e1568f..a66ecd34fb 100644
--- a/src/components/views/right_panel/VerificationPanel.js
+++ b/src/components/views/right_panel/VerificationPanel.js
@@ -1,5 +1,5 @@
/*
-Copyright 2019 The Matrix.org Foundation C.I.C.
+Copyright 2019, 2020 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.
@@ -15,8 +15,11 @@ limitations under the License.
*/
import React from 'react';
+
import * as sdk from '../../../index';
import {verificationMethods} from 'matrix-js-sdk/src/crypto';
+import {_t} from "../../../languageHandler";
+import E2EIcon from "../rooms/E2EIcon";
export default class VerificationPanel extends React.PureComponent {
constructor(props) {
@@ -25,46 +28,81 @@ export default class VerificationPanel extends React.PureComponent {
this._hasVerifier = !!props.request.verifier;
}
- render() {
- return
+ renderQRPhase() {
+ const {member} = this.props;
+ // TODO change the button into a spinner when on click
+ const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+ return
- { this.renderStatus() }
+
Verify by scanning
+
{_t("Ask %(displayName)s to scan your code, or open your camera to scan theirs:", {
+ displayName: member.displayName || member.name || member.userId,
+ }, {
+ a: t => { t },
+ })}
+
QR Code
-
;
+
+
+
Verify by emoji
+
{_t("If you can't scan the code above, verify by comparing unique emoji.")}
{_t(
- "For maximum security, we recommend you do this in person or use another " +
- "trusted means of communication.",
- )}
+
{_t("For ultimate security, do this in person or use another way to communicate.")}
{sasDisplay}
-
+ {confirm}
;
}
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index c99b22f421..5b131b8750 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -451,7 +451,10 @@
"Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.",
"Unable to find a supported verification method.": "Unable to find a supported verification method.",
"Cancel": "Cancel",
- "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.",
+ "Waiting for %(displayName)s to verify…": "Waiting for %(displayName)s to verify…",
+ "They match": "They match",
+ "They don't match": "They don't match",
+ "For ultimate security, do this in person or use another way to communicate.": "For ultimate security, do this in person or use another way to communicate.",
"Dog": "Dog",
"Cat": "Cat",
"Lion": "Lion",
@@ -1124,12 +1127,13 @@
"URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
"In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.": "In encrypted rooms, like this one, URL previews are disabled by default to ensure that your homeserver (where the previews are generated) cannot gather information about links you see in this room.",
"When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.": "When someone puts a URL in their message, a URL preview can be shown to give more information about that link such as the title, description, and an image from the website.",
- "Verify User": "Verify User",
- "For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.",
- "For maximum security, do this in person.": "For maximum security, do this in person.",
+ "Waiting for %(displayName)s to accept…": "Waiting for %(displayName)s to accept…",
"Start Verification": "Start Verification",
"Messages in this room are end-to-end encrypted.": "Messages in this room are end-to-end encrypted.",
"Your messages are secured and only you and the recipient have the unique keys to unlock them.": "Your messages are secured and only you and the recipient have the unique keys to unlock them.",
+ "Verify User": "Verify User",
+ "For extra security, verify this user by checking a one-time code on both of your devices.": "For extra security, verify this user by checking a one-time code on both of your devices.",
+ "For maximum security, do this in person.": "For maximum security, do this in person.",
"Members": "Members",
"Files": "Files",
"Trusted": "Trusted",
@@ -1147,6 +1151,11 @@
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
"Messages in this room are not end-to-end encrypted.": "Messages in this room are not end-to-end encrypted.",
"Security": "Security",
+ "Ask %(displayName)s to scan your code, or open your camera to scan theirs:": "Ask %(displayName)s to scan your code, or open your camera to scan theirs:",
+ "If you can't scan the code above, verify by comparing unique emoji.": "If you can't scan the code above, verify by comparing unique emoji.",
+ "Verify by emoji": "Verify by emoji",
+ "You've successfully verified %(displayName)s!": "You've successfully verified %(displayName)s!",
+ "Got it": "Got it",
"Sunday": "Sunday",
"Monday": "Monday",
"Tuesday": "Tuesday",
@@ -1426,6 +1435,7 @@
"Verify device": "Verify device",
"Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)",
"Verify by comparing a short text string.": "Verify by comparing a short text string.",
+ "For maximum security, we recommend you do this in person or use another trusted means of communication.": "For maximum security, we recommend you do this in person or use another trusted means of communication.",
"Begin Verifying": "Begin Verifying",
"Waiting for partner to accept...": "Waiting for partner to accept...",
"Nothing appearing? Not all clients support interactive verification yet. .": "Nothing appearing? Not all clients support interactive verification yet. .",