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:") } -

-
- -
-

- { _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.") } -

-
- ); +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 ( - - ); + 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 ( + + {body} + + ); + } + + _renderSasVerificationPhaseStart() { + const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton'); + const DialogButtons = sdk.getComponent('views.elements.DialogButtons'); + return ( +
+ + {_t("Use Legacy Verification (for older clients)")} + +

+ { _t("Verify by comparing a short text string.") } +

+

+ {_t( + "For maximum security, we recommend you do this in person or " + + "use another trusted means of communication.", + )} +

+ +
+ ); + } + + _renderSasVerificationPhaseWaitAccept() { + const Spinner = sdk.getComponent("views.elements.Spinner"); + + return ( +
+ +

{_t("Waiting for partner to accept...")}

+
+ ); + } + + _renderSasVerificationPhaseShowSas() { + const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); + return ; + } + + _renderSasVerificationPhaseWaitForPartnerToConfirm() { + const Spinner = sdk.getComponent('views.elements.Spinner'); + return
+ +

{_t( + "Waiting for %(userId)s to confirm...", {userId: this.props.userId}, + )}

+
; + } + + _renderSasVerificationPhaseVerified() { + const VerificationComplete = sdk.getComponent('views.verification.VerificationComplete'); + return ; + } + + _renderSasVerificationPhaseCancelled() { + const VerificationCancelled = sdk.getComponent('views.verification.VerificationCancelled'); + return ; + } + + _renderLegacyVerification() { + const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); + const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton'); + + const key = FormattingUtils.formatCryptoKey(this.props.device.getFingerprint()); + const body = ( +
+ + {_t("Use two-way text verification")} + +

+ { _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.") } +

+
+ ); + + return ( + + ); + } + + render() { + if (this.state.mode === MODE_LEGACY) { + return this._renderLegacyVerification(); + } else { + return
+ {this._renderSasVerification()} +
; + } + } } -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.", + )}

+ +
+ ); + } + + _renderPhaseShowSas() { + const VerificationShowSas = sdk.getComponent('views.verification.VerificationShowSas'); + return ; + } + + _renderPhaseWaitForPartnerToConfirm() { + const Spinner = sdk.getComponent("views.elements.Spinner"); + + return ( +
+ +

{_t("Waiting for partner to confirm...")}

+
+ ); + } + + _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'),