mirror of
https://github.com/element-hq/element-web
synced 2024-11-28 12:28:50 +03:00
Merge pull request #2499 from matrix-org/travis/usettings/tab/security
Implement the "Security & Privacy" tab of new user settings
This commit is contained in:
commit
41bc2a3d0c
7 changed files with 324 additions and 21 deletions
|
@ -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";
|
||||
|
|
53
res/css/views/settings/tabs/_SecuritySettingsTab.scss
Normal file
53
res/css/views/settings/tabs/_SecuritySettingsTab.scss
Normal file
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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",
|
||||
<div>Security Test</div>,
|
||||
<SecuritySettingsTab />,
|
||||
));
|
||||
if (SettingsStore.getLabsFeatures().length > 0) {
|
||||
tabs.push(new Tab(
|
||||
|
|
|
@ -257,20 +257,17 @@ export default class KeyBackupPanel extends React.PureComponent {
|
|||
{uploadStatus}
|
||||
<div>{backupSigStatuses}</div><br />
|
||||
<br />
|
||||
<AccessibleButton className="mx_UserSettings_button"
|
||||
onClick={this._restoreBackup}>
|
||||
<AccessibleButton kind="primary" onClick={this._restoreBackup}>
|
||||
{ _t("Restore backup") }
|
||||
</AccessibleButton>
|
||||
<AccessibleButton className="mx_UserSettings_button danger"
|
||||
onClick={this._deleteBackup}>
|
||||
<AccessibleButton kind="danger" onClick={this._deleteBackup}>
|
||||
{ _t("Delete backup") }
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
} else {
|
||||
return <div>
|
||||
{_t("No backup is present")}<br /><br />
|
||||
<AccessibleButton className="mx_UserSettings_button"
|
||||
onClick={this._startNewBackup}>
|
||||
<AccessibleButton kind="primary" onClick={this._startNewBackup}>
|
||||
{ _t("Start a new backup") }
|
||||
</AccessibleButton>
|
||||
</div>;
|
||||
|
|
242
src/components/views/settings/tabs/SecuritySettingsTab.js
Normal file
242
src/components/views/settings/tabs/SecuritySettingsTab.js
Normal file
|
@ -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 (
|
||||
<div className='mx_SecuritySettingsTab_ignoredUser'>
|
||||
<AccessibleButton onClick={this._onUnignoreClicked} kind='primary_sm'>
|
||||
{_t('Unignore')}
|
||||
</AccessibleButton>
|
||||
<span>{this.props.userId}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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("<not supported>");
|
||||
} else {
|
||||
identityKey = FormattingUtils.formatCryptoKey(identityKey);
|
||||
}
|
||||
|
||||
let importExportButtons = null;
|
||||
if (client.isCryptoEnabled()) {
|
||||
importExportButtons = (
|
||||
<div className='mx_SecuritySettingsTab_importExportButtons'>
|
||||
<AccessibleButton kind='primary' onClick={this._onExportE2eKeysClicked}>
|
||||
{_t("Export E2E room keys")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton kind='primary' onClick={this._onImportE2eKeysClicked}>
|
||||
{_t("Import E2E room keys")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t("Cryptography")}</span>
|
||||
<ul className='mx_SettingsTab_subsectionText mx_SecuritySettingsTab_deviceInfo'>
|
||||
<li>
|
||||
<label>{_t("Device ID:")}</label>
|
||||
<span><code>{deviceId}</code></span>
|
||||
</li>
|
||||
<li>
|
||||
<label>{_t("Device key:")}</label>
|
||||
<span><code><b>{identityKey}</b></code></span>
|
||||
</li>
|
||||
</ul>
|
||||
{importExportButtons}
|
||||
<SettingsFlag name='blacklistUnverifiedDevices' level={SettingLevel.DEVICE}
|
||||
onChange={this._updateBlacklistDevicesFlag} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderIgnoredUsers() {
|
||||
if (!this.state.ignoredUserIds || this.state.ignoredUserIds.length === 0) return null;
|
||||
|
||||
const userIds = this.state.ignoredUserIds
|
||||
.map((u) => <IgnoredUser userId={u} onUnignored={this._onUserUnignored} key={u} />);
|
||||
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Ignored users')}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{userIds}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderRejectInvites() {
|
||||
const invitedRooms = MatrixClientPeg.get().getRooms().filter((r) => {
|
||||
return r.hasMembershipState(MatrixClientPeg.get().getUserId(), "invite");
|
||||
});
|
||||
if (invitedRooms.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onClick = this._onRejectAllInvitesClicked.bind(this, invitedRooms);
|
||||
return (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className='mx_SettingsTab_subheading'>{_t('Bulk options')}</span>
|
||||
<AccessibleButton onClick={onClick} kind='danger' disabled={this.state.rejectingInvites}>
|
||||
{_t("Reject all %(invitedRooms)s invites", {invitedRooms: invitedRooms.length})}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const DevicesPanel = sdk.getComponent('views.settings.DevicesPanel');
|
||||
const SettingsFlag = sdk.getComponent('views.elements.SettingsFlag');
|
||||
|
||||
let keyBackup = null;
|
||||
if (SettingsStore.isFeatureEnabled("feature_keybackup")) {
|
||||
const KeyBackupPanel = sdk.getComponent('views.settings.KeyBackupPanel');
|
||||
keyBackup = (
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Key backup")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<KeyBackupPanel />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx_SettingsTab mx_SecuritySettingsTab">
|
||||
<div className="mx_SettingsTab_heading">{_t("Security & Privacy")}</div>
|
||||
<div className="mx_SettingsTab_section">
|
||||
<span className="mx_SettingsTab_subheading">{_t("Devices")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
<DevicesPanel />
|
||||
</div>
|
||||
</div>
|
||||
{keyBackup}
|
||||
{this._renderCurrentDeviceInfo()}
|
||||
<div className='mx_SettingsTab_section'>
|
||||
<span className="mx_SettingsTab_subheading">{_t("Analytics")}</span>
|
||||
<div className='mx_SettingsTab_subsectionText'>
|
||||
{_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.")}
|
||||
<AccessibleButton className="mx_SettingsTab_linkBtn" onClick={Analytics.showDetailsModal}>
|
||||
{_t("Learn more about how we use analytics.")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<SettingsFlag name='analyticsOptIn' level={SettingLevel.DEVICE}
|
||||
onChange={this._updateAnalytics} />
|
||||
</div>
|
||||
{this._renderIgnoredUsers()}
|
||||
{this._renderRejectInvites()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -469,6 +469,21 @@
|
|||
"Room list": "Room list",
|
||||
"Timeline": "Timeline",
|
||||
"Autocomplete delay (ms)": "Autocomplete delay (ms)",
|
||||
"Unignore": "Unignore",
|
||||
"<not supported>": "<not supported>",
|
||||
"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):",
|
||||
"<not supported>": "<not supported>",
|
||||
"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.",
|
||||
|
|
Loading…
Reference in a new issue