diff --git a/res/css/_components.scss b/res/css/_components.scss
index 91e6fa8685..0603296ef5 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -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";
diff --git a/res/css/views/elements/_HexVerify.scss b/res/css/views/elements/_HexVerify.scss
new file mode 100644
index 0000000000..3f3ee4b7ea
--- /dev/null
+++ b/res/css/views/elements/_HexVerify.scss
@@ -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;
+}
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 9a77901d2e..0cf67a3551 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -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);
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index 6803a7e6d8..b8be3e017a 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -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");
diff --git a/src/components/views/dialogs/DeviceVerifyDialog.js b/src/components/views/dialogs/DeviceVerifyDialog.js
index 6bec933389..e84010726d 100644
--- a/src/components/views/dialogs/DeviceVerifyDialog.js
+++ b/src/components/views/dialogs/DeviceVerifyDialog.js
@@ -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 = (
-
-
- { _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:") }
-
-
-
-
{ props.device.getDisplayName() }
-
{ props.device.deviceId }
-
{ key }
-
-
-
- { _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.") }
-
-
- { _t("In future this verification process will be more sophisticated.") }
-
+ { _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:") }
+
+
+
+
{ this.props.device.getDisplayName() }
+
{ this.props.device.deviceId }
+
{ key }
+
+
+
+ { _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.") }
+
;
+ }
+ }
}
-DeviceVerifyDialog.propTypes = {
- userId: PropTypes.string.isRequired,
- device: PropTypes.object.isRequired,
- onFinished: PropTypes.func.isRequired,
-};
diff --git a/src/components/views/dialogs/IncomingSasDialog.js b/src/components/views/dialogs/IncomingSasDialog.js
new file mode 100644
index 0000000000..2a76e8a904
--- /dev/null
+++ b/src/components/views/dialogs/IncomingSasDialog.js
@@ -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 (
+
+
{this.props.verifier.userId}
+
{_t(
+ "Verify this user to mark them as trusted. " +
+ "Trusting users gives you extra peace of mind when using " +
+ "end-to-end encrypted messages.",
+ )}
+
{_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.",
+ )}
+ );
+ }
+
+ _renderPhaseVerified() {
+ const VerificationComplete = sdk.getComponent('views.verification.VerificationComplete');
+ return ;
+ }
+
+ _renderPhaseCancelled() {
+ const VerificationCancelled = sdk.getComponent('views.verification.VerificationCancelled');
+ return ;
+ }
+
+ 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 (
+
+ {body}
+
+ );
+ }
+}
+
diff --git a/src/components/views/elements/HexVerify.js b/src/components/views/elements/HexVerify.js
new file mode 100644
index 0000000000..86ead3adc1
--- /dev/null
+++ b/src/components/views/elements/HexVerify.js
@@ -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
+ {this.props.text}
+ ;
+ }
+}
+
+/*
+ * 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();
+ }
+ return
+ {pairs}
+
;
+ }
+}
diff --git a/src/components/views/verification/VerificationCancelled.js b/src/components/views/verification/VerificationCancelled.js
new file mode 100644
index 0000000000..b21153f2cc
--- /dev/null
+++ b/src/components/views/verification/VerificationCancelled.js
@@ -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
+
{_t(
+ "The other party cancelled the verification.",
+ )}
+
+
;
+ }
+}
diff --git a/src/components/views/verification/VerificationComplete.js b/src/components/views/verification/VerificationComplete.js
new file mode 100644
index 0000000000..59f7ff924a
--- /dev/null
+++ b/src/components/views/verification/VerificationComplete.js
@@ -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
+
{_t("Verified!")}
+
{_t("You've successfully verified this user.")}
+
{_t(
+ "Secure messages with this user are end-to-end encrypted and not able to be " +
+ "read by third parties.",
+ )}
+
+
;
+ }
+}
diff --git a/src/components/views/verification/VerificationShowSas.js b/src/components/views/verification/VerificationShowSas.js
new file mode 100644
index 0000000000..cd4ae59b76
--- /dev/null
+++ b/src/components/views/verification/VerificationShowSas.js
@@ -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
+
{_t(
+ "Verify this user by confirming the following number appears on their screen.",
+ )}
+
{_t(
+ "For maximum security, we reccommend you do this in person or use another " +
+ "trusted means of communication.",
+ )}
+
+
{_t(
+ "To continue, click on each pair to confirm it's correct.",
+ )}
+
+
;
+ }
+}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 1d88db5397..d8b8b12479 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -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 (Warning: this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (Warning: 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):",
diff --git a/src/settings/Settings.js b/src/settings/Settings.js
index 4871ee92f9..c1ced35689 100644
--- a/src/settings/Settings.js
+++ b/src/settings/Settings.js
@@ -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'),