diff --git a/res/css/_components.scss b/res/css/_components.scss
index 25b7c9342e..6e644802cc 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -137,6 +137,7 @@
@import "./views/settings/_ProfileSettings.scss";
@import "./views/settings/tabs/_GeneralSettingsTab.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 7b09970f56..addd16bdf0 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";
@@ -74,7 +75,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..55bc538a55
--- /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";
+const Modal = require("../../../../Modal");
+const sdk = require("../../../../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 ab4e8b74c7..50539e84dd 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -450,6 +450,21 @@
"Timeline": "Timeline",
"Advanced": "Advanced",
"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.",
@@ -510,8 +525,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",
@@ -1056,7 +1069,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",
"Help & About": "Help & About",
"Visit old settings": "Visit old settings",
"Unable to load backup status": "Unable to load backup status",
@@ -1288,18 +1300,10 @@
"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",
"If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.": "If you've submitted a bug via GitHub, debug logs can help us track down the problem. Debug logs contain application usage data including your username, the IDs or aliases of the rooms or groups you have visited and the usernames of other users. They do not contain messages.",
- "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",
@@ -1308,7 +1312,6 @@
"Clear Cache and Reload": "Clear Cache and Reload",
"Updates": "Updates",
"Check for update": "Check for update",
- "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.",