diff --git a/res/css/_components.scss b/res/css/_components.scss
index 454418f9b4..11baf7fee7 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -139,6 +139,7 @@
@import "./views/settings/tabs/_GeneralSettingsTab.scss";
@import "./views/settings/tabs/_HelpSettingsTab.scss";
@import "./views/settings/tabs/_PreferencesSettingsTab.scss";
+@import "./views/settings/tabs/_SecuritySettingsTab.scss";
@import "./views/settings/tabs/_SettingsTab.scss";
@import "./views/settings/tabs/_VoiceSettingsTab.scss";
@import "./views/voip/_CallView.scss";
diff --git a/res/css/views/settings/tabs/_SecuritySettingsTab.scss b/res/css/views/settings/tabs/_SecuritySettingsTab.scss
new file mode 100644
index 0000000000..2640df1383
--- /dev/null
+++ b/res/css/views/settings/tabs/_SecuritySettingsTab.scss
@@ -0,0 +1,53 @@
+/*
+Copyright 2019 New Vector Ltd
+
+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.
+*/
+
+.mx_SecuritySettingsTab .mx_DevicesPanel {
+ // Normally the panel is 880px, however this can easily overflow the container.
+ // TODO: Fix the table to not be squishy
+ width: auto;
+ max-width: 880px;
+}
+
+.mx_SecuritySettingsTab_deviceInfo {
+ display: table;
+ padding-left: 0;
+}
+
+.mx_SecuritySettingsTab_deviceInfo > li {
+ display: table-row;
+}
+
+.mx_SecuritySettingsTab_deviceInfo > li > label,
+.mx_SecuritySettingsTab_deviceInfo > li > span {
+ display: table-cell;
+ padding-right: 1em;
+}
+
+.mx_SecuritySettingsTab_importExportButtons .mx_AccessibleButton {
+ margin-right: 10px;
+}
+
+.mx_SecuritySettingsTab_importExportButtons {
+ margin-bottom: 15px;
+}
+
+.mx_SecuritySettingsTab_ignoredUser {
+ margin-bottom: 5px;
+}
+
+.mx_SecuritySettingsTab_ignoredUser .mx_AccessibleButton {
+ margin-right: 10px;
+}
\ No newline at end of file
diff --git a/res/css/views/settings/tabs/_SettingsTab.scss b/res/css/views/settings/tabs/_SettingsTab.scss
index 1a26c58c44..17f869b5b0 100644
--- a/res/css/views/settings/tabs/_SettingsTab.scss
+++ b/res/css/views/settings/tabs/_SettingsTab.scss
@@ -26,15 +26,15 @@ limitations under the License.
font-family: $font-family-semibold;
color: $primary-fg-color;
margin-bottom: 10px;
- margin-top: 10px;
+ margin-top: 12px;
}
.mx_SettingsTab_subsectionText {
color: $settings-subsection-fg-color;
font-size: 12px;
padding-bottom: 12px;
- margin: 0;
display: block;
+ margin: 0 100px 0 0; // Align with the rest of the view
}
.mx_SettingsTab_section .mx_SettingsFlag {
@@ -54,3 +54,9 @@ limitations under the License.
.mx_SettingsTab_section .mx_SettingsFlag .mx_ToggleSwitch {
float: right;
}
+
+.mx_SettingsTab_linkBtn {
+ cursor: pointer;
+ color: $accent-color;
+ word-break: break-all;
+}
\ No newline at end of file
diff --git a/src/components/views/dialogs/UserSettingsDialog.js b/src/components/views/dialogs/UserSettingsDialog.js
index fa7311b141..b47b2368f9 100644
--- a/src/components/views/dialogs/UserSettingsDialog.js
+++ b/src/components/views/dialogs/UserSettingsDialog.js
@@ -23,6 +23,7 @@ import GeneralSettingsTab from "../settings/tabs/GeneralSettingsTab";
import dis from '../../../dispatcher';
import SettingsStore from "../../../settings/SettingsStore";
import LabsSettingsTab from "../settings/tabs/LabsSettingsTab";
+import SecuritySettingsTab from "../settings/tabs/SecuritySettingsTab";
import NotificationSettingsTab from "../settings/tabs/NotificationSettingsTab";
import PreferencesSettingsTab from "../settings/tabs/PreferencesSettingsTab";
import VoiceSettingsTab from "../settings/tabs/VoiceSettingsTab";
@@ -75,7 +76,7 @@ export default class UserSettingsDialog extends React.Component {
tabs.push(new Tab(
_td("Security & Privacy"),
"mx_UserSettingsDialog_securityIcon",
-
;
diff --git a/src/components/views/settings/tabs/SecuritySettingsTab.js b/src/components/views/settings/tabs/SecuritySettingsTab.js
new file mode 100644
index 0000000000..a2a30a2190
--- /dev/null
+++ b/src/components/views/settings/tabs/SecuritySettingsTab.js
@@ -0,0 +1,242 @@
+/*
+Copyright 2019 New Vector Ltd
+
+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 React from 'react';
+import PropTypes from 'prop-types';
+import {_t} from "../../../../languageHandler";
+import SettingsStore, {SettingLevel} from "../../../../settings/SettingsStore";
+import MatrixClientPeg from "../../../../MatrixClientPeg";
+import * as FormattingUtils from "../../../../utils/FormattingUtils";
+import AccessibleButton from "../../elements/AccessibleButton";
+import Analytics from "../../../../Analytics";
+import Promise from "bluebird";
+import Modal from "../../../../Modal";
+import sdk from "../../../../index";
+
+export class IgnoredUser extends React.Component {
+ static propTypes = {
+ userId: PropTypes.string.isRequired,
+ onUnignored: PropTypes.func.isRequired,
+ };
+
+ _onUnignoreClicked = (e) => {
+ this.props.onUnignored(this.props.userId);
+ };
+
+ render() {
+ return (
+
+
+ {_t('Unignore')}
+
+ {this.props.userId}
+
+ );
+ }
+}
+
+export default class SecuritySettingsTab extends React.Component {
+ constructor() {
+ super();
+
+ this.state = {
+ ignoredUserIds: MatrixClientPeg.get().getIgnoredUsers(),
+ rejectingInvites: false,
+ };
+ }
+
+ _updateBlacklistDevicesFlag = (checked) => {
+ MatrixClientPeg.get().setGlobalBlacklistUnverifiedDevices(checked);
+ };
+
+ _updateAnalytics = (checked) => {
+ checked ? Analytics.enable() : Analytics.disable();
+ };
+
+ _onExportE2eKeysClicked = () => {
+ Modal.createTrackedDialogAsync('Export E2E Keys', '',
+ import('../../../../async-components/views/dialogs/ExportE2eKeysDialog'),
+ {matrixClient: MatrixClientPeg.get()},
+ );
+ };
+
+ _onImportE2eKeysClicked = () => {
+ Modal.createTrackedDialogAsync('Import E2E Keys', '',
+ import('../../../../async-components/views/dialogs/ImportE2eKeysDialog'),
+ {matrixClient: MatrixClientPeg.get()},
+ );
+ };
+
+ _onUserUnignored = async (userId) => {
+ // Don't use this.state to get the ignored user list as it might be
+ // ever so slightly outdated. Instead, prefer to get a fresh list and
+ // update that.
+ const ignoredUsers = MatrixClientPeg.get().getIgnoredUsers();
+ const index = ignoredUsers.indexOf(userId);
+ if (index !== -1) {
+ ignoredUsers.splice(index, 1);
+ MatrixClientPeg.get().setIgnoredUsers(ignoredUsers);
+ }
+ this.setState({ignoredUsers});
+ };
+
+ _onRejectAllInvitesClicked = (rooms, ev) => {
+ this.setState({
+ rejectingInvites: true,
+ });
+ // reject the invites
+ const promises = rooms.map((room) => {
+ return MatrixClientPeg.get().leave(room.roomId).catch((e) => {
+ // purposefully drop errors to the floor: we'll just have a non-zero number on the UI
+ // after trying to reject all the invites.
+ });
+ });
+ Promise.all(promises).then(() => {
+ this.setState({
+ rejectingInvites: false,
+ });
+ });
+ };
+
+ _renderCurrentDeviceInfo() {
+ const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
+
+ const client = MatrixClientPeg.get();
+ const deviceId = client.deviceId;
+ let identityKey = client.getDeviceEd25519Key();
+ if (!identityKey) {
+ identityKey = _t("");
+ } else {
+ identityKey = FormattingUtils.formatCryptoKey(identityKey);
+ }
+
+ let importExportButtons = null;
+ if (client.isCryptoEnabled()) {
+ importExportButtons = (
+
+ {_t("Riot collects anonymous analytics to allow us to improve the application.")}
+
+ {_t("Privacy is important to us, so we don't collect any personal or " +
+ "identifiable data for our analytics.")}
+
+ {_t("Learn more about how we use analytics.")}
+
+
+ );
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 94c2528c64..10506d8513 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -469,6 +469,21 @@
"Room list": "Room list",
"Timeline": "Timeline",
"Autocomplete delay (ms)": "Autocomplete delay (ms)",
+ "Unignore": "Unignore",
+ "": "",
+ "Import E2E room keys": "Import E2E room keys",
+ "Cryptography": "Cryptography",
+ "Device ID:": "Device ID:",
+ "Device key:": "Device key:",
+ "Ignored users": "Ignored users",
+ "Bulk options": "Bulk options",
+ "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
+ "Key backup": "Key backup",
+ "Security & Privacy": "Security & Privacy",
+ "Devices": "Devices",
+ "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.",
+ "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.",
+ "Learn more about how we use analytics.": "Learn more about how we use analytics.",
"No media permissions": "No media permissions",
"You may need to manually permit Riot to access your microphone/webcam": "You may need to manually permit Riot to access your microphone/webcam",
"Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.",
@@ -529,8 +544,6 @@
"Failed to change power level": "Failed to change power level",
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "You will not be able to undo this change as you are promoting the user to have the same power level as yourself.",
"No devices with registered encryption keys": "No devices with registered encryption keys",
- "Devices": "Devices",
- "Unignore": "Unignore",
"Ignore": "Ignore",
"Jump to read receipt": "Jump to read receipt",
"Mention": "Mention",
@@ -1074,7 +1087,6 @@
"Room contains unknown devices": "Room contains unknown devices",
"\"%(RoomName)s\" contains devices that you haven't seen before.": "\"%(RoomName)s\" contains devices that you haven't seen before.",
"Unknown devices": "Unknown devices",
- "Security & Privacy": "Security & Privacy",
"Visit old settings": "Visit old settings",
"Unable to load backup status": "Unable to load backup status",
"Unable to restore backup": "Unable to restore backup",
@@ -1305,23 +1317,14 @@
"Interface Language": "Interface Language",
"User Interface": "User Interface",
"Autocomplete Delay (ms):": "Autocomplete Delay (ms):",
- "": "",
- "Import E2E room keys": "Import E2E room keys",
"Key Backup": "Key Backup",
- "Cryptography": "Cryptography",
- "Device ID:": "Device ID:",
- "Device key:": "Device key:",
"Ignored Users": "Ignored Users",
"Submit Debug Logs": "Submit Debug Logs",
- "Riot collects anonymous analytics to allow us to improve the application.": "Riot collects anonymous analytics to allow us to improve the application.",
- "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.": "Privacy is important to us, so we don't collect any personal or identifiable data for our analytics.",
- "Learn more about how we use analytics.": "Learn more about how we use analytics.",
"These are experimental features that may break in unexpected ways": "These are experimental features that may break in unexpected ways",
"Use with caution": "Use with caution",
"Deactivate my account": "Deactivate my account",
"Clear Cache": "Clear Cache",
"Updates": "Updates",
- "Reject all %(invitedRooms)s invites": "Reject all %(invitedRooms)s invites",
"Bulk Options": "Bulk Options",
"Desktop specific": "Desktop specific",
"Missing Media Permissions, click here to request.": "Missing Media Permissions, click here to request.",