Merge pull request #2461 from matrix-org/dbkr/sas

Short-Authentication-String Verification
This commit is contained in:
David Baker 2019-01-28 09:45:59 +00:00 committed by GitHub
commit eb7112cbc0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 753 additions and 53 deletions

View file

@ -72,6 +72,7 @@
@import "./views/elements/_Dropdown.scss";
@import "./views/elements/_EditableItemList.scss";
@import "./views/elements/_Field.scss";
@import "./views/elements/_HexVerify.scss";
@import "./views/elements/_ImageView.scss";
@import "./views/elements/_InlineSpinner.scss";
@import "./views/elements/_MemberEventListSummary.scss";

View file

@ -0,0 +1,34 @@
/*
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_HexVerify {
text-align: center;
}
.mx_HexVerify_pair {
display: inline-block;
font-weight: bold;
padding-left: 3px;
padding-right: 3px;
}
.mx_HexVerify_pair_verified {
color: $accent-color;
}
.mx_HexVerify_pair:hover{
color: $accent-color;
}

View file

@ -29,6 +29,7 @@ import SettingsStore from './settings/SettingsStore';
import MatrixActionCreators from './actions/MatrixActionCreators';
import {phasedRollOutExpiredForUser} from "./PhasedRollOut";
import Modal from './Modal';
import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
interface MatrixClientCreds {
homeserverUrl: string,
@ -184,6 +185,7 @@ class MatrixClientPeg {
deviceId: creds.deviceId,
timelineSupport: true,
forceTURN: SettingsStore.getValue('webRtcForceTURN', false),
verificationMethods: [verificationMethods.SAS]
};
this.matrixClient = createMatrixClient(opts, useIndexedDb);

View file

@ -1232,6 +1232,7 @@ export default React.createClass({
this.firstSyncComplete = false;
this.firstSyncPromise = Promise.defer();
const cli = MatrixClientPeg.get();
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');
// Allow the JS SDK to reap timeline events. This reduces the amount of
// memory consumed as the JS SDK stores multiple distinct copies of room
@ -1431,6 +1432,12 @@ export default React.createClass({
}
});
cli.on("crypto.verification.start", (verifier) => {
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
verifier,
});
});
// Fire the tinter right on startup to ensure the default theme is applied
// A later sync can/will correct the tint to be the right value for the user
const colorScheme = SettingsStore.getValue("roomColor");

View file

@ -1,6 +1,7 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
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.
@ -21,58 +22,258 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import sdk from '../../../index';
import * as FormattingUtils from '../../../utils/FormattingUtils';
import { _t } from '../../../languageHandler';
import SettingsStore from '../../../settings/SettingsStore';
import {verificationMethods} from 'matrix-js-sdk/lib/crypto';
export default function DeviceVerifyDialog(props) {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const MODE_LEGACY = 'legacy';
const MODE_SAS = 'sas';
const key = FormattingUtils.formatCryptoKey(props.device.getFingerprint());
const body = (
<div>
<p>
{ _t("To verify that this device can be trusted, please contact its " +
"owner using some other means (e.g. in person or a phone call) " +
"and ask them whether the key they see in their User Settings " +
"for this device matches the key below:") }
</p>
<div className="mx_UserSettings_cryptoSection">
<ul>
<li><label>{ _t("Device name") }:</label> <span>{ props.device.getDisplayName() }</span></li>
<li><label>{ _t("Device ID") }:</label> <span><code>{ props.device.deviceId }</code></span></li>
<li><label>{ _t("Device key") }:</label> <span><code><b>{ key }</b></code></span></li>
</ul>
</div>
<p>
{ _t("If it matches, press the verify button below. " +
"If it doesn't, then someone else is intercepting this device " +
"and you probably want to press the blacklist button instead.") }
</p>
<p>
{ _t("In future this verification process will be more sophisticated.") }
</p>
</div>
);
const PHASE_START = 0;
const PHASE_WAIT_FOR_PARTNER_TO_ACCEPT = 1;
const PHASE_SHOW_SAS = 2;
const PHASE_WAIT_FOR_PARTNER_TO_CONFIRM = 3;
const PHASE_VERIFIED = 4;
const PHASE_CANCELLED = 5;
function onFinished(confirm) {
if (confirm) {
MatrixClientPeg.get().setDeviceVerified(
props.userId, props.device.deviceId, true,
);
}
props.onFinished(confirm);
export default class DeviceVerifyDialog extends React.Component {
static propTypes = {
userId: PropTypes.string.isRequired,
device: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired,
};
constructor() {
super();
this._verifier = null;
this._showSasEvent = null;
this.state = {
phase: PHASE_START,
mode: SettingsStore.isFeatureEnabled("feature_sas") ? MODE_SAS : MODE_LEGACY,
sasVerified: false,
};
}
return (
<QuestionDialog
title={_t("Verify device")}
description={body}
button={_t("I verify that the keys match")}
onFinished={onFinished}
/>
);
componentWillUnmount() {
if (this._verifier) {
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
this._verifier.cancel('User cancel');
}
}
_onSwitchToLegacyClick = () => {
this.setState({mode: MODE_LEGACY});
}
_onSwitchToSasClick = () => {
this.setState({mode: MODE_SAS});
}
_onCancelClick = () => {
this.props.onFinished(false);
}
_onLegacyFinished = (confirm) => {
if (confirm) {
MatrixClientPeg.get().setDeviceVerified(
this.props.userId, this.props.device.deviceId, true,
);
}
this.props.onFinished(confirm);
}
_onSasRequestClick = () => {
this.setState({
phase: PHASE_WAIT_FOR_PARTNER_TO_ACCEPT,
});
this._verifier = MatrixClientPeg.get().beginKeyVerification(
verificationMethods.SAS, this.props.userId, this.props.device.deviceId,
);
this._verifier.on('show_sas', this._onVerifierShowSas);
this._verifier.verify().then(() => {
this.setState({phase: PHASE_VERIFIED});
this._verifier.removeListener('show_sas', this._onVerifierShowSas);
this._verifier = null;
}).catch((e) => {
console.log("Verification failed", e);
this.setState({
phase: PHASE_CANCELLED,
});
this._verifier = null;
});
}
_onSasMatchesClick = () => {
this._showSasEvent.confirm();
this.setState({
phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM,
});
}
_onVerifiedDoneClick = () => {
this.props.onFinished(true);
}
_onVerifierShowSas = (e) => {
this._showSasEvent = e;
this.setState({
phase: PHASE_SHOW_SAS,
});
}
_renderSasVerification() {
let body;
switch (this.state.phase) {
case PHASE_START:
body = this._renderSasVerificationPhaseStart();
break;
case PHASE_WAIT_FOR_PARTNER_TO_ACCEPT:
body = this._renderSasVerificationPhaseWaitAccept();
break;
case PHASE_SHOW_SAS:
body = this._renderSasVerificationPhaseShowSas();
break;
case PHASE_WAIT_FOR_PARTNER_TO_CONFIRM:
body = this._renderSasVerificationPhaseWaitForPartnerToConfirm();
break;
case PHASE_VERIFIED:
body = this._renderSasVerificationPhaseVerified();
break;
case PHASE_CANCELLED:
body = this._renderSasVerificationPhaseCancelled();
break;
}
const BaseDialog = sdk.getComponent("dialogs.BaseDialog");
return (
<BaseDialog
title={_t("Verify device")}
onFinished={this._onCancelClick}
>
{body}
</BaseDialog>
);
}
_renderSasVerificationPhaseStart() {
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<div>
<AccessibleButton
element="span" className="mx_linkButton" onClick={this._onSwitchToLegacyClick}
>
{_t("Use Legacy Verification (for older clients)")}
</AccessibleButton>
<p>
{ _t("Verify by comparing a short text string.") }
</p>
<p>
{_t(
"For maximum security, we recommend you do this in person or " +
"use another trusted means of communication.",
)}
</p>
<DialogButtons
primaryButton={_t('Begin Verifying')}
hasCancel={true}
onPrimaryButtonClick={this._onSasRequestClick}
onCancel={this._onCancelClick}
/>
</div>
);
}
_renderSasVerificationPhaseWaitAccept() {
const Spinner = sdk.getComponent("views.elements.Spinner");
return (
<div>
<Spinner />
<p>{_t("Waiting for partner to accept...")}</p>
</div>
);
}
_renderSasVerificationPhaseShowSas() {
const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas');
return <VerificationShowSas
sas={this._showSasEvent.sas}
onCancel={this._onCancelClick}
onDone={this._onSasMatchesClick}
/>;
}
_renderSasVerificationPhaseWaitForPartnerToConfirm() {
const Spinner = sdk.getComponent('views.elements.Spinner');
return <div>
<Spinner />
<p>{_t(
"Waiting for %(userId)s to confirm...", {userId: this.props.userId},
)}</p>
</div>;
}
_renderSasVerificationPhaseVerified() {
const VerificationComplete = sdk.getComponent('views.verification.VerificationComplete');
return <VerificationComplete onDone={this._onVerifiedDoneClick} />;
}
_renderSasVerificationPhaseCancelled() {
const VerificationCancelled = sdk.getComponent('views.verification.VerificationCancelled');
return <VerificationCancelled onDone={this._onCancelClick} />;
}
_renderLegacyVerification() {
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
const key = FormattingUtils.formatCryptoKey(this.props.device.getFingerprint());
const body = (
<div>
<AccessibleButton
element="span" className="mx_linkButton" onClick={this._onSwitchToSasClick}
>
{_t("Use two-way text verification")}
</AccessibleButton>
<p>
{ _t("To verify that this device can be trusted, please contact its " +
"owner using some other means (e.g. in person or a phone call) " +
"and ask them whether the key they see in their User Settings " +
"for this device matches the key below:") }
</p>
<div className="mx_UserSettings_cryptoSection">
<ul>
<li><label>{ _t("Device name") }:</label> <span>{ this.props.device.getDisplayName() }</span></li>
<li><label>{ _t("Device ID") }:</label> <span><code>{ this.props.device.deviceId }</code></span></li>
<li><label>{ _t("Device key") }:</label> <span><code><b>{ key }</b></code></span></li>
</ul>
</div>
<p>
{ _t("If it matches, press the verify button below. " +
"If it doesn't, then someone else is intercepting this device " +
"and you probably want to press the blacklist button instead.") }
</p>
</div>
);
return (
<QuestionDialog
title={_t("Verify device")}
description={body}
button={_t("I verify that the keys match")}
onFinished={this._onLegacyFinished}
/>
);
}
render() {
if (this.state.mode === MODE_LEGACY) {
return this._renderLegacyVerification();
} else {
return <div>
{this._renderSasVerification()}
</div>;
}
}
}
DeviceVerifyDialog.propTypes = {
userId: PropTypes.string.isRequired,
device: PropTypes.object.isRequired,
onFinished: PropTypes.func.isRequired,
};

View file

@ -0,0 +1,182 @@
/*
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 sdk from '../../../index';
import { _t } from '../../../languageHandler';
const PHASE_START = 0;
const PHASE_SHOW_SAS = 1;
const PHASE_WAIT_FOR_PARTNER_TO_CONFIRM = 2;
const PHASE_VERIFIED = 3;
const PHASE_CANCELLED = 4;
export default class IncomingSasDialog extends React.Component {
static propTypes = {
verifier: PropTypes.object.isRequired,
};
constructor(props) {
super(props);
this._showSasEvent = null;
this.state = {
phase: PHASE_START,
sasVerified: false,
};
this.props.verifier.on('show_sas', this._onVerifierShowSas);
this.props.verifier.on('cancel', this._onVerifierCancel);
}
componentWillUnmount() {
if (this.state.phase !== PHASE_CANCELLED && this.state.phase !== PHASE_VERIFIED) {
this.props.verifier.cancel('User cancel');
}
this.props.verifier.removeListener('show_sas', this._onVerifierShowSas);
}
_onFinished = () => {
this.props.onFinished(this.state.phase === PHASE_VERIFIED);
}
_onCancelClick = () => {
this.props.onFinished(this.state.phase === PHASE_VERIFIED);
}
_onContinueClick = () => {
this.setState({phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM});
this.props.verifier.verify().then(() => {
this.setState({phase: PHASE_VERIFIED});
}).catch((e) => {
console.log("Verification failed", e);
});
}
_onVerifierShowSas = (e) => {
this._showSasEvent = e;
this.setState({
phase: PHASE_SHOW_SAS,
sas: e.sas,
});
}
_onVerifierCancel = (e) => {
this.setState({
phase: PHASE_CANCELLED,
});
}
_onSasMatchesClick = () => {
this._showSasEvent.confirm();
this.setState({
phase: PHASE_WAIT_FOR_PARTNER_TO_CONFIRM,
});
}
_onVerifiedDoneClick = () => {
this.props.onFinished(true);
}
_renderPhaseStart() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return (
<div>
<h2>{this.props.verifier.userId}</h2>
<p>{_t(
"Verify this user to mark them as trusted. " +
"Trusting users gives you extra peace of mind when using " +
"end-to-end encrypted messages.",
)}</p>
<p>{_t(
// NB. Below wording adjusted to singular 'device' until we have
// cross-signing
"Verifying this user will mark their device as trusted, and " +
"also mark your device as trusted to them.",
)}</p>
<DialogButtons
primaryButton={_t('Continue')}
hasCancel={true}
onPrimaryButtonClick={this._onContinueClick}
onCancel={this._onCancelClick}
/>
</div>
);
}
_renderPhaseShowSas() {
const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas');
return <VerificationShowSas
sas={this._showSasEvent.sas}
onCancel={this._onCancelClick}
onDone={this._onSasMatchesClick}
/>;
}
_renderPhaseWaitForPartnerToConfirm() {
const Spinner = sdk.getComponent("views.elements.Spinner");
return (
<div>
<Spinner />
<p>{_t("Waiting for partner to confirm...")}</p>
</div>
);
}
_renderPhaseVerified() {
const VerificationComplete = sdk.getComponent('views.verification.VerificationComplete');
return <VerificationComplete onDone={this._onVerifiedDoneClick} />;
}
_renderPhaseCancelled() {
const VerificationCancelled = sdk.getComponent('views.verification.VerificationCancelled');
return <VerificationCancelled onDone={this._onCancelClick} />;
}
render() {
let body;
switch (this.state.phase) {
case PHASE_START:
body = this._renderPhaseStart();
break;
case PHASE_SHOW_SAS:
body = this._renderPhaseShowSas();
break;
case PHASE_WAIT_FOR_PARTNER_TO_CONFIRM:
body = this._renderPhaseWaitForPartnerToConfirm();
break;
case PHASE_VERIFIED:
body = this._renderPhaseVerified();
break;
case PHASE_CANCELLED:
body = this._renderPhaseCancelled();
break;
}
const BaseDialog = sdk.getComponent("dialogs.BaseDialog");
return (
<BaseDialog
title={_t("Incoming Verification Request")}
onFinished={this._onFinished}
>
{body}
</BaseDialog>
);
}
}

View file

@ -0,0 +1,103 @@
/*
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 classnames from 'classnames';
import sdk from '../../../index';
class HexVerifyPair extends React.Component {
static propTypes = {
text: PropTypes.string.isRequired,
index: PropTypes.number,
verified: PropTypes.bool,
onChange: PropTypes.func.isRequired,
}
_onClick = () => {
this.setState({verified: !this.props.verified});
this.props.onChange(this.props.index, !this.props.verified);
}
render() {
const classNames = {
mx_HexVerify_pair: true,
mx_HexVerify_pair_verified: this.props.verified,
};
const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
return <AccessibleButton className={classnames(classNames)}
onClick={this._onClick}
>
{this.props.text}
</AccessibleButton>;
}
}
/*
* Helps a user verify a hexadecimal code matches one displayed
* elsewhere (eg. on a different device)
*/
export default class HexVerify extends React.Component {
static propTypes = {
text: PropTypes.string.isRequired,
onVerifiedStateChange: PropTypes.func,
}
static defaultProps = {
onVerifiedStateChange: function() {},
}
constructor(props) {
super(props);
this.state = {
pairsVerified: [],
};
for (let i = 0; i < props.text.length; i += 2) {
this.state.pairsVerified.push(false);
}
}
_onPairChange = (index, newVal) => {
const oldVerified = this.state.pairsVerified.reduce((acc, val) => {
return acc && val;
}, true);
const newPairsVerified = this.state.pairsVerified.slice(0);
newPairsVerified[index] = newVal;
const newVerified = newPairsVerified.reduce((acc, val) => {
return acc && val;
}, true);
this.setState({pairsVerified: newPairsVerified});
if (oldVerified !== newVerified) {
this.props.onVerifiedStateChange(newVerified);
}
}
render() {
const pairs = [];
for (let i = 0; i < this.props.text.length / 2; ++i) {
pairs.push(<HexVerifyPair key={i} index={i}
text={this.props.text.substr(i * 2, 2)}
verified={this.state.pairsVerified[i]}
onChange={this._onPairChange}
/>);
}
return <div className="mx_HexVerify">
{pairs}
</div>;
}
}

View file

@ -0,0 +1,40 @@
/*
Copyright 2019 Vector Creations 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 sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default class VerificationCancelled extends React.Component {
static propTypes = {
onDone: PropTypes.func.isRequired,
}
render() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div>
<p>{_t(
"The other party cancelled the verification.",
)}</p>
<DialogButtons
primaryButton={_t('Cancel')}
hasCancel={false}
onPrimaryButtonClick={this.props.onDone}
/>
</div>;
}
}

View file

@ -0,0 +1,42 @@
/*
Copyright 2019 Vector Creations 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 sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default class VerificationComplete extends React.Component {
static propTypes = {
onDone: PropTypes.func.isRequired,
}
render() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
return <div>
<h2>{_t("Verified!")}</h2>
<p>{_t("You've successfully verified this user.")}</p>
<p>{_t(
"Secure messages with this user are end-to-end encrypted and not able to be " +
"read by third parties.",
)}</p>
<DialogButtons onPrimaryButtonClick={this.props.onDone}
primaryButton={_t("Got It")}
hasCancel={false}
/>
</div>;
}
}

View file

@ -0,0 +1,65 @@
/*
Copyright 2019 Vector Creations 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 sdk from '../../../index';
import { _t } from '../../../languageHandler';
export default class VerificationShowSas extends React.Component {
static propTypes = {
onDone: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
sas: PropTypes.string.isRequired,
}
constructor() {
super();
this.state = {
sasVerified: false,
};
}
_onVerifiedStateChange = (newVal) => {
this.setState({sasVerified: newVal});
}
render() {
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
const HexVerify = sdk.getComponent('views.elements.HexVerify');
return <div>
<p>{_t(
"Verify this user by confirming the following number appears on their screen.",
)}</p>
<p>{_t(
"For maximum security, we reccommend you do this in person or use another " +
"trusted means of communication.",
)}</p>
<HexVerify text={this.props.sas}
onVerifiedStateChange={this._onVerifiedStateChange}
/>
<p>{_t(
"To continue, click on each pair to confirm it's correct.",
)}</p>
<DialogButtons onPrimaryButtonClick={this.props.onDone}
primaryButton={_t("Continue")}
primaryDisabled={!this.state.sasVerified}
hasCancel={true}
onCancel={this.props.onCancel}
/>
</div>;
}
}

View file

@ -267,6 +267,7 @@
"Increase performance by only loading room members on first view": "Increase performance by only loading room members on first view",
"Backup of encryption keys to server": "Backup of encryption keys to server",
"Render simple counters in room header": "Render simple counters in room header",
"Two-way device verification using short text": "Two-way device verification using short text",
"Disable Emoji suggestions while typing": "Disable Emoji suggestions while typing",
"Use compact timeline layout": "Use compact timeline layout",
"Hide removed messages": "Hide removed messages",
@ -320,6 +321,16 @@
"Incoming call from %(name)s": "Incoming call from %(name)s",
"Decline": "Decline",
"Accept": "Accept",
"The other party cancelled the verification.": "The other party cancelled the verification.",
"Cancel": "Cancel",
"Verified!": "Verified!",
"You've successfully verified this user.": "You've successfully verified this user.",
"Secure messages with this user are end-to-end encrypted and not able to be read by third parties.": "Secure messages with this user are end-to-end encrypted and not able to be read by third parties.",
"Got It": "Got It",
"Verify this user by confirming the following number appears on their screen.": "Verify this user by confirming the following number appears on their screen.",
"For maximum security, we reccommend you do this in person or use another trusted means of communication.": "For maximum security, we reccommend you do this in person or use another trusted means of communication.",
"To continue, click on each pair to confirm it's correct.": "To continue, click on each pair to confirm it's correct.",
"Continue": "Continue",
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains": "A text message has been sent to +%(msisdn)s. Please enter the verification code it contains",
"Incorrect verification code": "Incorrect verification code",
"Enter Code": "Enter Code",
@ -334,7 +345,6 @@
"Passwords can't be empty": "Passwords can't be empty",
"Warning!": "Warning!",
"Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Changing password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"Continue": "Continue",
"Export E2E room keys": "Export E2E room keys",
"Do you want to set an email address?": "Do you want to set an email address?",
"Current password": "Current password",
@ -740,7 +750,6 @@
"This Room": "This Room",
"All Rooms": "All Rooms",
"Search…": "Search…",
"Cancel": "Cancel",
"You don't currently have any stickerpacks enabled": "You don't currently have any stickerpacks enabled",
"Add some now": "Add some now",
"Stickerpack": "Stickerpack",
@ -994,11 +1003,17 @@
"Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)",
"To continue, please enter your password:": "To continue, please enter your password:",
"password": "password",
"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...",
"Waiting for %(userId)s to confirm...": "Waiting for %(userId)s to confirm...",
"Use two-way text verification": "Use two-way text verification",
"To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:": "To verify that this device can be trusted, please contact its owner using some other means (e.g. in person or a phone call) and ask them whether the key they see in their User Settings for this device matches the key below:",
"Device name": "Device name",
"Device key": "Device key",
"If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.": "If it matches, press the verify button below. If it doesn't, then someone else is intercepting this device and you probably want to press the blacklist button instead.",
"In future this verification process will be more sophisticated.": "In future this verification process will be more sophisticated.",
"I verify that the keys match": "I verify that the keys match",
"Back": "Back",
"Send Custom Event": "Send Custom Event",
@ -1015,6 +1030,10 @@
"Toolbox": "Toolbox",
"Developer Tools": "Developer Tools",
"An error has occurred.": "An error has occurred.",
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
"Verifying this user will mark their device as trusted, and also mark your device as trusted to them.": "Verifying this user will mark their device as trusted, and also mark your device as trusted to them.",
"Waiting for partner to confirm...": "Waiting for partner to confirm...",
"Incoming Verification Request": "Incoming Verification Request",
"You added a new device '%(displayName)s', which is requesting encryption keys.": "You added a new device '%(displayName)s', which is requesting encryption keys.",
"Your unverified device '%(displayName)s' is requesting encryption keys.": "Your unverified device '%(displayName)s' is requesting encryption keys.",
"Start verification": "Start verification",
@ -1173,7 +1192,6 @@
"Sign in": "Sign in",
"If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?",
"Email address (optional)": "Email address (optional)",
"You are registering with %(SelectedTeamName)s": "You are registering with %(SelectedTeamName)s",
"Mobile phone number (optional)": "Mobile phone number (optional)",
"Default server": "Default server",
"Custom server": "Custom server",
@ -1315,7 +1333,6 @@
"Server may be unavailable or overloaded": "Server may be unavailable or overloaded",
"Remove Contact Information?": "Remove Contact Information?",
"Remove %(threePid)s?": "Remove %(threePid)s?",
"Refer a friend to Riot:": "Refer a friend to Riot:",
"Interface Language": "Interface Language",
"User Interface": "User Interface",
"Autocomplete Delay (ms):": "Autocomplete Delay (ms):",

View file

@ -116,6 +116,12 @@ export const SETTINGS = {
supportedLevels: LEVELS_FEATURE,
default: false,
},
"feature_sas": {
isFeature: true,
displayName: _td("Two-way device verification using short text"),
supportedLevels: LEVELS_FEATURE,
default: false,
},
"MessageComposerInput.dontSuggestEmoji": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
displayName: _td('Disable Emoji suggestions while typing'),