Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/fix/15179
Conflicts: src/components/views/settings/tabs/user/SecurityUserSettingsTab.js src/settings/Settings.ts src/settings/UIFeature.ts
This commit is contained in:
commit
687a2e56b2
68 changed files with 880 additions and 732 deletions
|
@ -38,6 +38,8 @@ import {Action} from "../../../dispatcher/actions";
|
|||
import {DefaultTagID} from "../../../stores/room-list/models";
|
||||
import RoomListStore from "../../../stores/room-list/RoomListStore";
|
||||
import {CommunityPrototypeStore} from "../../../stores/CommunityPrototypeStore";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {UIFeature} from "../../../settings/UIFeature";
|
||||
|
||||
// we have a number of types defined from the Matrix spec which can't reasonably be altered here.
|
||||
/* eslint-disable camelcase */
|
||||
|
@ -549,7 +551,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
if (this.state.filterText.startsWith('@')) {
|
||||
// Assume mxid
|
||||
newMember = new DirectoryMember({user_id: this.state.filterText, display_name: null, avatar_url: null});
|
||||
} else {
|
||||
} else if (SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||
// Assume email
|
||||
newMember = new ThreepidMember(this.state.filterText);
|
||||
}
|
||||
|
@ -734,7 +736,7 @@ export default class InviteDialog extends React.PureComponent {
|
|||
this.setState({tryingIdentityServer: true});
|
||||
return;
|
||||
}
|
||||
if (term.indexOf('@') > 0 && Email.looksValid(term)) {
|
||||
if (term.indexOf('@') > 0 && Email.looksValid(term) && SettingsStore.getValue(UIFeature.IdentityServer)) {
|
||||
// Start off by suggesting the plain email while we try and resolve it
|
||||
// to a real account.
|
||||
this.setState({
|
||||
|
@ -1037,7 +1039,9 @@ export default class InviteDialog extends React.PureComponent {
|
|||
}
|
||||
|
||||
_renderIdentityServerWarning() {
|
||||
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer) {
|
||||
if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer ||
|
||||
!SettingsStore.getValue(UIFeature.IdentityServer)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1086,22 +1090,38 @@ export default class InviteDialog extends React.PureComponent {
|
|||
let buttonText;
|
||||
let goButtonFn;
|
||||
|
||||
const identityServersEnabled = SettingsStore.getValue(UIFeature.IdentityServer);
|
||||
|
||||
const userId = MatrixClientPeg.get().getUserId();
|
||||
if (this.props.kind === KIND_DM) {
|
||||
title = _t("Direct Messages");
|
||||
helpText = _t(
|
||||
"Start a conversation with someone using their name, username (like <userId/>) or email address.",
|
||||
{},
|
||||
{userId: () => {
|
||||
return <a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>;
|
||||
}},
|
||||
);
|
||||
|
||||
if (identityServersEnabled) {
|
||||
helpText = _t(
|
||||
"Start a conversation with someone using their name, username (like <userId/>) or email address.",
|
||||
{},
|
||||
{userId: () => {
|
||||
return (
|
||||
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
|
||||
);
|
||||
}},
|
||||
);
|
||||
} else {
|
||||
helpText = _t(
|
||||
"Start a conversation with someone using their name or username (like <userId/>).",
|
||||
{},
|
||||
{userId: () => {
|
||||
return (
|
||||
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>
|
||||
);
|
||||
}},
|
||||
);
|
||||
}
|
||||
|
||||
if (CommunityPrototypeStore.instance.getSelectedCommunityId()) {
|
||||
const communityName = CommunityPrototypeStore.instance.getSelectedCommunityName();
|
||||
helpText = _t(
|
||||
"Start a conversation with someone using their name, username (like <userId/>) or email address. " +
|
||||
"This won't invite them to %(communityName)s. To invite someone to %(communityName)s, click " +
|
||||
"<a>here</a>.",
|
||||
const inviteText = _t("This won't invite them to %(communityName)s. " +
|
||||
"To invite someone to %(communityName)s, click <a>here</a>",
|
||||
{communityName}, {
|
||||
userId: () => {
|
||||
return (
|
||||
|
@ -1122,21 +1142,40 @@ export default class InviteDialog extends React.PureComponent {
|
|||
},
|
||||
},
|
||||
);
|
||||
helpText = <React.Fragment>
|
||||
{ helpText } {inviteText}
|
||||
</React.Fragment>;
|
||||
}
|
||||
buttonText = _t("Go");
|
||||
goButtonFn = this._startDm;
|
||||
} else { // KIND_INVITE
|
||||
title = _t("Invite to this room");
|
||||
helpText = _t(
|
||||
"Invite someone using their name, username (like <userId/>), email address or <a>share this room</a>.",
|
||||
{},
|
||||
{
|
||||
userId: () =>
|
||||
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
||||
a: (sub) =>
|
||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||
},
|
||||
);
|
||||
|
||||
if (identityServersEnabled) {
|
||||
helpText = _t(
|
||||
"Invite someone using their name, username (like <userId/>), email address or " +
|
||||
"<a>share this room</a>.",
|
||||
{},
|
||||
{
|
||||
userId: () =>
|
||||
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
||||
a: (sub) =>
|
||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
helpText = _t(
|
||||
"Invite someone using their name, username (like <userId/>) or <a>share this room</a>.",
|
||||
{},
|
||||
{
|
||||
userId: () =>
|
||||
<a href={makeUserPermalink(userId)} rel="noreferrer noopener" target="_blank">{userId}</a>,
|
||||
a: (sub) =>
|
||||
<a href={makeRoomPermalink(this.props.roomId)} rel="noreferrer noopener" target="_blank">{sub}</a>,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
buttonText = _t("Invite");
|
||||
goButtonFn = this._inviteUsers;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,8 @@ import Modal from '../../../Modal';
|
|||
import * as sdk from '../../../index';
|
||||
import dis from '../../../dispatcher/dispatcher';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||
import RestoreKeyBackupDialog from './security/RestoreKeyBackupDialog';
|
||||
|
||||
export default class LogoutDialog extends React.Component {
|
||||
defaultProps = {
|
||||
|
@ -73,7 +74,7 @@ export default class LogoutDialog extends React.Component {
|
|||
|
||||
_onExportE2eKeysClicked() {
|
||||
Modal.createTrackedDialogAsync('Export E2E Keys', '',
|
||||
import('../../../async-components/views/dialogs/ExportE2eKeysDialog'),
|
||||
import('../../../async-components/views/dialogs/security/ExportE2eKeysDialog'),
|
||||
{
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
},
|
||||
|
@ -93,14 +94,13 @@ export default class LogoutDialog extends React.Component {
|
|||
// A key backup exists for this account, but the creating device is not
|
||||
// verified, so restore the backup which will give us the keys from it and
|
||||
// allow us to trust it (ie. upload keys to it)
|
||||
const RestoreKeyBackupDialog = sdk.getComponent('dialogs.keybackup.RestoreKeyBackupDialog');
|
||||
Modal.createTrackedDialog(
|
||||
'Restore Backup', '', RestoreKeyBackupDialog, null, null,
|
||||
/* priority = */ false, /* static = */ true,
|
||||
);
|
||||
} else {
|
||||
Modal.createTrackedDialogAsync("Key Backup", "Key Backup",
|
||||
import("../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog"),
|
||||
import("../../../async-components/views/dialogs/security/CreateKeyBackupDialog"),
|
||||
null, null, /* priority = */ false, /* static = */ true,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,8 @@ import {copyPlaintext, selectText} from "../../../utils/strings";
|
|||
import StyledCheckbox from '../elements/StyledCheckbox';
|
||||
import AccessibleTooltipButton from '../elements/AccessibleTooltipButton';
|
||||
import { IDialogProps } from "./IDialogProps";
|
||||
import SettingsStore from "../../../settings/SettingsStore";
|
||||
import {UIFeature} from "../../../settings/UIFeature";
|
||||
|
||||
const socials = [
|
||||
{
|
||||
|
@ -197,6 +199,35 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
|||
const matrixToUrl = this.getUrl();
|
||||
const encodedUrl = encodeURIComponent(matrixToUrl);
|
||||
|
||||
const showQrCode = SettingsStore.getValue(UIFeature.ShareQRCode);
|
||||
const showSocials = SettingsStore.getValue(UIFeature.ShareSocial);
|
||||
|
||||
let qrSocialSection;
|
||||
if (showQrCode || showSocials) {
|
||||
qrSocialSection = <>
|
||||
<hr />
|
||||
<div className="mx_ShareDialog_split">
|
||||
{ showQrCode && <div className="mx_ShareDialog_qrcode_container">
|
||||
<QRCode data={matrixToUrl} width={256} />
|
||||
</div> }
|
||||
{ showSocials && <div className="mx_ShareDialog_social_container">
|
||||
{ socials.map((social) => (
|
||||
<a
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
key={social.name}
|
||||
title={social.name}
|
||||
href={social.url(encodedUrl)}
|
||||
className="mx_ShareDialog_social_icon"
|
||||
>
|
||||
<img src={social.img} alt={social.name} height={64} width={64} />
|
||||
</a>
|
||||
)) }
|
||||
</div> }
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
return <BaseDialog
|
||||
title={title}
|
||||
|
@ -220,27 +251,7 @@ export default class ShareDialog extends React.PureComponent<IProps, IState> {
|
|||
/>
|
||||
</div>
|
||||
{ checkbox }
|
||||
<hr />
|
||||
|
||||
<div className="mx_ShareDialog_split">
|
||||
<div className="mx_ShareDialog_qrcode_container">
|
||||
<QRCode data={matrixToUrl} width={256} />
|
||||
</div>
|
||||
<div className="mx_ShareDialog_social_container">
|
||||
{ socials.map((social) => (
|
||||
<a
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
key={social.name}
|
||||
title={social.name}
|
||||
href={social.url(encodedUrl)}
|
||||
className="mx_ShareDialog_social_icon"
|
||||
>
|
||||
<img src={social.img} alt={social.name} height={64} width={64} />
|
||||
</a>
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
{ qrSocialSection }
|
||||
</div>
|
||||
</BaseDialog>;
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import FlairUserSettingsTab from "../settings/tabs/user/FlairUserSettingsTab";
|
|||
import * as sdk from "../../../index";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import MjolnirUserSettingsTab from "../settings/tabs/user/MjolnirUserSettingsTab";
|
||||
import {UIFeature} from "../../../settings/UIFeature";
|
||||
|
||||
export const USER_GENERAL_TAB = "USER_GENERAL_TAB";
|
||||
export const USER_APPEARANCE_TAB = "USER_APPEARANCE_TAB";
|
||||
|
@ -86,12 +87,14 @@ export default class UserSettingsDialog extends React.Component {
|
|||
"mx_UserSettingsDialog_appearanceIcon",
|
||||
<AppearanceUserSettingsTab />,
|
||||
));
|
||||
tabs.push(new Tab(
|
||||
USER_FLAIR_TAB,
|
||||
_td("Flair"),
|
||||
"mx_UserSettingsDialog_flairIcon",
|
||||
<FlairUserSettingsTab />,
|
||||
));
|
||||
if (SettingsStore.getValue(UIFeature.Flair)) {
|
||||
tabs.push(new Tab(
|
||||
USER_FLAIR_TAB,
|
||||
_td("Flair"),
|
||||
"mx_UserSettingsDialog_flairIcon",
|
||||
<FlairUserSettingsTab />,
|
||||
));
|
||||
}
|
||||
tabs.push(new Tab(
|
||||
USER_NOTIFICATIONS_TAB,
|
||||
_td("Notifications"),
|
||||
|
@ -104,12 +107,16 @@ export default class UserSettingsDialog extends React.Component {
|
|||
"mx_UserSettingsDialog_preferencesIcon",
|
||||
<PreferencesUserSettingsTab />,
|
||||
));
|
||||
tabs.push(new Tab(
|
||||
USER_VOICE_TAB,
|
||||
_td("Voice & Video"),
|
||||
"mx_UserSettingsDialog_voiceIcon",
|
||||
<VoiceUserSettingsTab />,
|
||||
));
|
||||
|
||||
if (SettingsStore.getValue(UIFeature.Voip)) {
|
||||
tabs.push(new Tab(
|
||||
USER_VOICE_TAB,
|
||||
_td("Voice & Video"),
|
||||
"mx_UserSettingsDialog_voiceIcon",
|
||||
<VoiceUserSettingsTab />,
|
||||
));
|
||||
}
|
||||
|
||||
tabs.push(new Tab(
|
||||
USER_SECURITY_TAB,
|
||||
_td("Security & Privacy"),
|
||||
|
|
|
@ -16,8 +16,8 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {_t} from "../../../languageHandler";
|
||||
import * as sdk from "../../../index";
|
||||
import {_t} from "../../../../languageHandler";
|
||||
import * as sdk from "../../../../index";
|
||||
|
||||
export default class ConfirmDestroyCrossSigningDialog extends React.Component {
|
||||
static propTypes = {
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
Copyright 2018, 2019 New Vector Ltd
|
||||
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.
|
||||
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 { MatrixClientPeg } from '../../../../MatrixClientPeg';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import Modal from '../../../../Modal';
|
||||
import { SSOAuthEntry } from '../../auth/InteractiveAuthEntryComponents';
|
||||
import DialogButtons from '../../elements/DialogButtons';
|
||||
import BaseDialog from '../BaseDialog';
|
||||
import Spinner from '../../elements/Spinner';
|
||||
import InteractiveAuthDialog from '../InteractiveAuthDialog';
|
||||
|
||||
/*
|
||||
* Walks the user through the process of creating a cross-signing keys. In most
|
||||
* cases, only a spinner is shown, but for more complex auth like SSO, the user
|
||||
* may need to complete some steps to proceed.
|
||||
*/
|
||||
export default class CreateCrossSigningDialog extends React.PureComponent {
|
||||
static propTypes = {
|
||||
accountPassword: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
error: null,
|
||||
// Does the server offer a UI auth flow with just m.login.password
|
||||
// for /keys/device_signing/upload?
|
||||
canUploadKeysWithPasswordOnly: null,
|
||||
accountPassword: props.accountPassword || "",
|
||||
};
|
||||
|
||||
if (this.state.accountPassword) {
|
||||
// If we have an account password in memory, let's simplify and
|
||||
// assume it means password auth is also supported for device
|
||||
// signing key upload as well. This avoids hitting the server to
|
||||
// test auth flows, which may be slow under high load.
|
||||
this.state.canUploadKeysWithPasswordOnly = true;
|
||||
} else {
|
||||
this._queryKeyUploadAuth();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._bootstrapCrossSigning();
|
||||
}
|
||||
|
||||
async _queryKeyUploadAuth() {
|
||||
try {
|
||||
await MatrixClientPeg.get().uploadDeviceSigningKeys(null, {});
|
||||
// We should never get here: the server should always require
|
||||
// UI auth to upload device signing keys. If we do, we upload
|
||||
// no keys which would be a no-op.
|
||||
console.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!");
|
||||
} catch (error) {
|
||||
if (!error.data || !error.data.flows) {
|
||||
console.log("uploadDeviceSigningKeys advertised no flows!");
|
||||
return;
|
||||
}
|
||||
const canUploadKeysWithPasswordOnly = error.data.flows.some(f => {
|
||||
return f.stages.length === 1 && f.stages[0] === 'm.login.password';
|
||||
});
|
||||
this.setState({
|
||||
canUploadKeysWithPasswordOnly,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_doBootstrapUIAuth = async (makeRequest) => {
|
||||
if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) {
|
||||
await makeRequest({
|
||||
type: 'm.login.password',
|
||||
identifier: {
|
||||
type: 'm.id.user',
|
||||
user: MatrixClientPeg.get().getUserId(),
|
||||
},
|
||||
// TODO: Remove `user` once servers support proper UIA
|
||||
// See https://github.com/matrix-org/synapse/issues/5665
|
||||
user: MatrixClientPeg.get().getUserId(),
|
||||
password: this.state.accountPassword,
|
||||
});
|
||||
} else {
|
||||
const dialogAesthetics = {
|
||||
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||
title: _t("Use Single Sign On to continue"),
|
||||
body: _t("To continue, use Single Sign On to prove your identity."),
|
||||
continueText: _t("Single Sign On"),
|
||||
continueKind: "primary",
|
||||
},
|
||||
[SSOAuthEntry.PHASE_POSTAUTH]: {
|
||||
title: _t("Confirm encryption setup"),
|
||||
body: _t("Click the button below to confirm setting up encryption."),
|
||||
continueText: _t("Confirm"),
|
||||
continueKind: "primary",
|
||||
},
|
||||
};
|
||||
|
||||
const { finished } = Modal.createTrackedDialog(
|
||||
'Cross-signing keys dialog', '', InteractiveAuthDialog,
|
||||
{
|
||||
title: _t("Setting up keys"),
|
||||
matrixClient: MatrixClientPeg.get(),
|
||||
makeRequest,
|
||||
aestheticsForStagePhases: {
|
||||
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||
},
|
||||
},
|
||||
);
|
||||
const [confirmed] = await finished;
|
||||
if (!confirmed) {
|
||||
throw new Error("Cross-signing key upload auth canceled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_bootstrapCrossSigning = async () => {
|
||||
this.setState({
|
||||
error: null,
|
||||
});
|
||||
|
||||
const cli = MatrixClientPeg.get();
|
||||
|
||||
try {
|
||||
await cli.bootstrapCrossSigning({
|
||||
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
|
||||
});
|
||||
this.props.onFinished(true);
|
||||
} catch (e) {
|
||||
this.setState({ error: e });
|
||||
console.error("Error bootstrapping cross-signing", e);
|
||||
}
|
||||
}
|
||||
|
||||
_onCancel = () => {
|
||||
this.props.onFinished(false);
|
||||
}
|
||||
|
||||
render() {
|
||||
let content;
|
||||
if (this.state.error) {
|
||||
content = <div>
|
||||
<p>{_t("Unable to set up keys")}</p>
|
||||
<div className="mx_Dialog_buttons">
|
||||
<DialogButtons primaryButton={_t('Retry')}
|
||||
onPrimaryButtonClick={this._bootstrapCrossSigning}
|
||||
onCancel={this._onCancel}
|
||||
/>
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
content = <div>
|
||||
<Spinner />
|
||||
</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseDialog className="mx_CreateCrossSigningDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Setting up keys")}
|
||||
hasCancel={false}
|
||||
fixedWidth={false}
|
||||
>
|
||||
<div>
|
||||
{content}
|
||||
</div>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -16,16 +16,16 @@ limitations under the License.
|
|||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import SetupEncryptionBody from '../../structures/auth/SetupEncryptionBody';
|
||||
import BaseDialog from './BaseDialog';
|
||||
import { _t } from '../../../languageHandler';
|
||||
import { SetupEncryptionStore, PHASE_DONE } from '../../../stores/SetupEncryptionStore';
|
||||
import SetupEncryptionBody from '../../../structures/auth/SetupEncryptionBody';
|
||||
import BaseDialog from '../BaseDialog';
|
||||
import { _t } from '../../../../languageHandler';
|
||||
import { SetupEncryptionStore, PHASE_DONE } from '../../../../stores/SetupEncryptionStore';
|
||||
|
||||
function iconFromPhase(phase) {
|
||||
if (phase === PHASE_DONE) {
|
||||
return require("../../../../res/img/e2e/verified.svg");
|
||||
return require("../../../../../res/img/e2e/verified.svg");
|
||||
} else {
|
||||
return require("../../../../res/img/e2e/warning.svg");
|
||||
return require("../../../../../res/img/e2e/warning.svg");
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue