Merge branches 'develop' and 't3chguy/cmds' of github.com:matrix-org/matrix-react-sdk into t3chguy/cmds

 Conflicts:
	src/SlashCommands.tsx
This commit is contained in:
Michael Telatynski 2020-03-31 11:49:53 +01:00
commit 6e61761012
21 changed files with 498 additions and 111 deletions

View file

@ -1,3 +1,159 @@
Changes in [2.3.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0) (2020-03-30)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.3.0-rc.1...v2.3.0)
* Upgrade JS SDK to 5.2.0
Changes in [2.3.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.3.0-rc.1) (2020-03-26)
=============================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.2.3...v2.3.0-rc.1)
* Upgrade JS SDK to 5.2.0-rc.1
* Add a flag to control whether cross-signing signatures are trusted
[\#4277](https://github.com/matrix-org/matrix-react-sdk/pull/4277)
* Update from Weblate
[\#4282](https://github.com/matrix-org/matrix-react-sdk/pull/4282)
* Update copy on SSSS symmetric upgrade toast
[\#4281](https://github.com/matrix-org/matrix-react-sdk/pull/4281)
* Wait for SSSS upgrade to complete
[\#4270](https://github.com/matrix-org/matrix-react-sdk/pull/4270)
* Update cross-signing verification copy and fix i18n
[\#4278](https://github.com/matrix-org/matrix-react-sdk/pull/4278)
* Fix soft-crash on bad permalinks
[\#4280](https://github.com/matrix-org/matrix-react-sdk/pull/4280)
* Fix: make self-verification wait for incoming request
[\#4267](https://github.com/matrix-org/matrix-react-sdk/pull/4267)
* Fall back to non-standard persisted api for Safari
[\#4272](https://github.com/matrix-org/matrix-react-sdk/pull/4272)
* Respond to backup key sharing requests
[\#4275](https://github.com/matrix-org/matrix-react-sdk/pull/4275)
* Log and display secret sharing cache state
[\#4268](https://github.com/matrix-org/matrix-react-sdk/pull/4268)
* Support sending config and ready events to capable widgets (Jitsi)
[\#4266](https://github.com/matrix-org/matrix-react-sdk/pull/4266)
* If cached keys are present in the key backup dialog, use them
[\#4273](https://github.com/matrix-org/matrix-react-sdk/pull/4273)
* Fix formatbar not hidden on highlighted message sent
[\#4265](https://github.com/matrix-org/matrix-react-sdk/pull/4265)
* Support Jitsi conferences sent/received on Riot Mobile and older Riot Webs
[\#4252](https://github.com/matrix-org/matrix-react-sdk/pull/4252)
* Use unified function to check cross-signing is ready
[\#4263](https://github.com/matrix-org/matrix-react-sdk/pull/4263)
* Migrate SSSS to symmetric
[\#4224](https://github.com/matrix-org/matrix-react-sdk/pull/4224)
* Migration to symmetric SSSS
[\#4242](https://github.com/matrix-org/matrix-react-sdk/pull/4242)
* Always display verification request toasts on top
[\#4262](https://github.com/matrix-org/matrix-react-sdk/pull/4262)
* Fix: assume SAS is supported when starting request with .start
[\#4249](https://github.com/matrix-org/matrix-react-sdk/pull/4249)
* Fix logout when Olm failed to load.
[\#4261](https://github.com/matrix-org/matrix-react-sdk/pull/4261)
* Improve naming of Jitsi conferences
[\#4251](https://github.com/matrix-org/matrix-react-sdk/pull/4251)
* Handle matrix.to user permalink in-room rather than solo
[\#4245](https://github.com/matrix-org/matrix-react-sdk/pull/4245)
* Fix: filter room list (again) by canonical and alternative aliases
[\#4260](https://github.com/matrix-org/matrix-react-sdk/pull/4260)
* EventIndex: Add some logging to the file panel populating.
[\#4250](https://github.com/matrix-org/matrix-react-sdk/pull/4250)
* Update from Weblate
[\#4259](https://github.com/matrix-org/matrix-react-sdk/pull/4259)
* Migrate RoomView to React Contexts in the hope for better temporal stability
[\#4258](https://github.com/matrix-org/matrix-react-sdk/pull/4258)
* Update WidgetUtils.js fix Jitsi path
[\#4256](https://github.com/matrix-org/matrix-react-sdk/pull/4256)
* Fix local jitsi build url fail and missing argument
[\#4255](https://github.com/matrix-org/matrix-react-sdk/pull/4255)
* Add shortcut CmdOrCtrl+. to toggle right panel
[\#4244](https://github.com/matrix-org/matrix-react-sdk/pull/4244)
* Improve Keyboard Shortcuts. Add alt-arrows & alt-shift-arrows
[\#4241](https://github.com/matrix-org/matrix-react-sdk/pull/4241)
* Bring back legacy verification by comparing public device keys
[\#4240](https://github.com/matrix-org/matrix-react-sdk/pull/4240)
* Searching: Return an empty result if the search term is an empty string.
[\#4248](https://github.com/matrix-org/matrix-react-sdk/pull/4248)
* Break continuation on showHiddenEvents-rendered events
[\#4247](https://github.com/matrix-org/matrix-react-sdk/pull/4247)
* Watch for show-RR settings changes, use room-specific and fix margins
[\#4246](https://github.com/matrix-org/matrix-react-sdk/pull/4246)
* Register Mac electron specific Cmd+, shortcut to User Settings
[\#4243](https://github.com/matrix-org/matrix-react-sdk/pull/4243)
* Use a local wrapper for Jitsi calls
[\#4234](https://github.com/matrix-org/matrix-react-sdk/pull/4234)
* Invite Dialog fixes
[\#4233](https://github.com/matrix-org/matrix-react-sdk/pull/4233)
* RoomPreviewBar word-break the sender name too
[\#4239](https://github.com/matrix-org/matrix-react-sdk/pull/4239)
* Report to the user when a key signature upload fails
[\#4229](https://github.com/matrix-org/matrix-react-sdk/pull/4229)
* pre-send megolm keys when possible when a user starts typing
[\#4235](https://github.com/matrix-org/matrix-react-sdk/pull/4235)
* we don't do mx_fadable anymore so get rid of broken RightPanel disabling
[\#4238](https://github.com/matrix-org/matrix-react-sdk/pull/4238)
* Fix left left panel overflowing vertically
[\#4237](https://github.com/matrix-org/matrix-react-sdk/pull/4237)
* Fix custom tags causing left panel to over-expand
[\#4236](https://github.com/matrix-org/matrix-react-sdk/pull/4236)
* Add Keyboard shortcuts dialog
[\#4231](https://github.com/matrix-org/matrix-react-sdk/pull/4231)
* Don't use buildkite agent to upload logs
[\#4232](https://github.com/matrix-org/matrix-react-sdk/pull/4232)
* Remove Gemini Scrollbars
[\#4217](https://github.com/matrix-org/matrix-react-sdk/pull/4217)
* Room Directory Explore Servers redesign
[\#4209](https://github.com/matrix-org/matrix-react-sdk/pull/4209)
* Fix redo keyboard shortcut on macOS
[\#4110](https://github.com/matrix-org/matrix-react-sdk/pull/4110)
* Fix: ensure local state for aliases doesn't get garbled up
[\#4230](https://github.com/matrix-org/matrix-react-sdk/pull/4230)
* Rename 'jump to bottom' to avoid ublock block
[\#4208](https://github.com/matrix-org/matrix-react-sdk/pull/4208)
* Restore key backup in background after complete security
[\#4225](https://github.com/matrix-org/matrix-react-sdk/pull/4225)
* Fix key backup trust text for cross-signing
[\#4223](https://github.com/matrix-org/matrix-react-sdk/pull/4223)
* Add default on config setting to control call button in composer
[\#4227](https://github.com/matrix-org/matrix-react-sdk/pull/4227)
* Fix: make alternative addresses UX less confusing
[\#4221](https://github.com/matrix-org/matrix-react-sdk/pull/4221)
* Wait for verification request on login
[\#4222](https://github.com/matrix-org/matrix-react-sdk/pull/4222)
* EventIndex: Add support to delete events from the index.
[\#4204](https://github.com/matrix-org/matrix-react-sdk/pull/4204)
* EventIndex: Remove a checkpoint if the HTTP request returns a 403.
[\#4214](https://github.com/matrix-org/matrix-react-sdk/pull/4214)
* Move to composer when typing letters with Shift held
[\#4216](https://github.com/matrix-org/matrix-react-sdk/pull/4216)
* Wrap large room names when previewing them
[\#4213](https://github.com/matrix-org/matrix-react-sdk/pull/4213)
* Rename Review Devices to Review Sessions
[\#4219](https://github.com/matrix-org/matrix-react-sdk/pull/4219)
* Fix typo in tabIndex to make React happy
[\#4215](https://github.com/matrix-org/matrix-react-sdk/pull/4215)
* Proof of concept for custom theme adding
[\#4148](https://github.com/matrix-org/matrix-react-sdk/pull/4148)
* Remove stuff that yarn install doesn't think we need
[\#4205](https://github.com/matrix-org/matrix-react-sdk/pull/4205)
* Declare jsx in tsconfig for IDEs
[\#4207](https://github.com/matrix-org/matrix-react-sdk/pull/4207)
* Fix: best-effort to join room without canonical alias over federation from
room directory
[\#4210](https://github.com/matrix-org/matrix-react-sdk/pull/4210)
* Test for cross-signing homeserver support during login, toasts
[\#4206](https://github.com/matrix-org/matrix-react-sdk/pull/4206)
* Send verification request to a single device in a way compatible with non-
cross-signing
[\#4202](https://github.com/matrix-org/matrix-react-sdk/pull/4202)
* Fixes for removing local alias
[\#4199](https://github.com/matrix-org/matrix-react-sdk/pull/4199)
* yarn upgrade
[\#4201](https://github.com/matrix-org/matrix-react-sdk/pull/4201)
* Support TypeScript for React components
[\#4203](https://github.com/matrix-org/matrix-react-sdk/pull/4203)
* When room name is changed, show both the old and new name
[\#4183](https://github.com/matrix-org/matrix-react-sdk/pull/4183)
Changes in [2.2.3](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v2.2.3) (2020-03-17)
===================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v2.2.3-rc.1...v2.2.3)

View file

@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
"version": "2.2.3",
"version": "2.3.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {

View file

@ -137,7 +137,7 @@ limitations under the License.
top: -8px;
border-radius: 8px;
background-color: $neutral-badge-color;
color: #ffffff;
color: #000;
font-weight: 600;
font-size: 10px;
text-align: center;

View file

@ -430,7 +430,7 @@ async function _startCallApp(roomId, type) {
return;
}
const confId = `JitsiConference_${generateHumanReadableId()}`;
const confId = `JitsiConference${generateHumanReadableId()}`;
const jitsiDomain = SdkConfig.get()['jitsi']['preferredDomain'];
let widgetUrl = WidgetUtils.getLocalJitsiWrapperUrl();

View file

@ -85,7 +85,7 @@ class Command {
aliases = [],
args = '',
description,
runFn=undefined,
runFn = undefined,
category = CommandCategories.other,
hideCompletionAfterSpace = false,
}: {
@ -160,6 +160,15 @@ export const Commands = [
},
category: CommandCategories.messages,
}),
new Command({
command: 'html',
args: '<message>',
description: _td('Sends a message as html, without interpreting it as markdown'),
runFn: function(roomId, messages) {
return success(MatrixClientPeg.get().sendHtmlMessage(roomId, messages, messages));
},
category: CommandCategories.messages,
}),
new Command({
command: 'ddg',
args: '<query>',

View file

@ -67,8 +67,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
constructor(props) {
super(props);
this._keyInfo = null;
this._encodedRecoveryKey = null;
this._recoveryKey = null;
this._recoveryKeyNode = null;
this._setZxcvbnResultTimeout = null;
@ -180,7 +179,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
}
_onDownloadClick = () => {
const blob = new Blob([this._encodedRecoveryKey], {
const blob = new Blob([this._recoveryKey.encodedPrivateKey], {
type: 'text/plain;charset=us-ascii',
});
FileSaver.saveAs(blob, 'recovery-key.txt');
@ -234,14 +233,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
if (force) {
await cli.bootstrapSecretStorage({
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
createSecretStorageKey: async () => this._keyInfo,
createSecretStorageKey: async () => this._recoveryKey,
setupNewKeyBackup: true,
setupNewSecretStorage: true,
});
} else {
await cli.bootstrapSecretStorage({
authUploadDeviceSigningKeys: this._doBootstrapUIAuth,
createSecretStorageKey: async () => this._keyInfo,
createSecretStorageKey: async () => this._recoveryKey,
keyBackupInfo: this.state.backupInfo,
setupNewKeyBackup: !this.state.backupInfo && this.state.useKeyBackup,
getKeyBackupPassphrase: promptForBackupPassphrase,
@ -299,10 +298,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
}
_onSkipPassPhraseClick = async () => {
const [keyInfo, encodedRecoveryKey] =
this._recoveryKey =
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase();
this._keyInfo = keyInfo;
this._encodedRecoveryKey = encodedRecoveryKey;
this.setState({
copied: false,
downloaded: false,
@ -335,10 +332,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
if (this.state.passPhrase !== this.state.passPhraseConfirm) return;
const [keyInfo, encodedRecoveryKey] =
this._recoveryKey =
await MatrixClientPeg.get().createRecoveryKeyFromPassphrase(this.state.passPhrase);
this._keyInfo = keyInfo;
this._encodedRecoveryKey = encodedRecoveryKey;
this.setState({
copied: false,
downloaded: false,
@ -613,7 +608,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent {
</div>
<div className="mx_CreateSecretStorageDialog_recoveryKeyContainer">
<div className="mx_CreateSecretStorageDialog_recoveryKey">
<code ref={this._collectRecoveryKeyNode}>{this._encodedRecoveryKey}</code>
<code ref={this._collectRecoveryKeyNode}>{this._recoveryKey.encodedPrivateKey}</code>
</div>
<div className="mx_CreateSecretStorageDialog_recoveryKeyButtons">
<AccessibleButton kind='primary' className="mx_Dialog_primary" onClick={this._onCopyClick}>

View file

@ -37,6 +37,8 @@ export default class EmbeddedPage extends React.PureComponent {
className: PropTypes.string,
// Whether to wrap the page in a scrollbar
scrollbar: PropTypes.bool,
// Map of keys to replace with values, e.g {$placeholder: "value"}
replaceMap: PropTypes.object,
};
static contextType = MatrixClientContext;
@ -81,6 +83,13 @@ export default class EmbeddedPage extends React.PureComponent {
}
body = body.replace(/_t\(['"]([\s\S]*?)['"]\)/mg, (match, g1)=>this.translate(g1));
if (this.props.replaceMap) {
Object.keys(this.props.replaceMap).forEach(key => {
body = body.split(key).join(this.props.replaceMap[key]);
});
}
this.setState({ page: body });
},
);

View file

@ -2022,7 +2022,7 @@ export default createReactClass({
}
} else if (this.state.view === VIEWS.WELCOME) {
const Welcome = sdk.getComponent('auth.Welcome');
view = <Welcome />;
view = <Welcome {...this.getServerProperties()} />;
} else if (this.state.view === VIEWS.REGISTER) {
const Registration = sdk.getComponent('structures.auth.Registration');
view = (

View file

@ -55,6 +55,7 @@ import RightPanelStore from "../../stores/RightPanelStore";
import {haveTileForEvent} from "../views/rooms/EventTile";
import RoomContext from "../../contexts/RoomContext";
import MatrixClientContext from "../../contexts/MatrixClientContext";
import { shieldStatusForRoom } from '../../utils/ShieldUtils';
const DEBUG = false;
let debuglog = function() {};
@ -817,40 +818,9 @@ export default createReactClass({
return;
}
// Duplication between here and _updateE2eStatus in RoomTile
/* At this point, the user has encryption on and cross-signing on */
const e2eMembers = await room.getEncryptionTargetMembers();
const verified = [];
const unverified = [];
e2eMembers.map(({userId}) => userId)
.filter((userId) => userId !== this.context.getUserId())
.forEach((userId) => {
(this.context.checkUserTrust(userId).isCrossSigningVerified() ?
verified : unverified).push(userId);
});
debuglog("e2e verified", verified, "unverified", unverified);
/* Check all verified user devices. */
/* Don't alarm if no other users are verified */
const targets = (verified.length > 0) ? [...verified, this.context.getUserId()] : verified;
for (const userId of targets) {
const devices = await this.context.getStoredDevicesForUser(userId);
const anyDeviceNotVerified = devices.some(({deviceId}) => {
return !this.context.checkDeviceTrust(userId, deviceId).isVerified();
});
if (anyDeviceNotVerified) {
this.setState({
e2eStatus: "warning",
});
debuglog("e2e status set to warning as not all users trust all of their sessions." +
" Aborted on user", userId);
return;
}
}
this.setState({
e2eStatus: unverified.length === 0 ? "verified" : "normal",
e2eStatus: await shieldStatusForRoom(this.context, room),
});
},

View file

@ -18,6 +18,12 @@ import React from 'react';
import * as sdk from '../../../index';
import SdkConfig from '../../../SdkConfig';
import AuthPage from "./AuthPage";
import * as Matrix from "matrix-js-sdk";
import {_td} from "../../../languageHandler";
import PlatformPeg from "../../../PlatformPeg";
// translatable strings for Welcome pages
_td("Sign in with SSO");
export default class Welcome extends React.PureComponent {
render() {
@ -33,11 +39,24 @@ export default class Welcome extends React.PureComponent {
pageUrl = 'welcome.html';
}
const {hsUrl, isUrl} = this.props.serverConfig;
const tmpClient = Matrix.createClient({
baseUrl: hsUrl,
idBaseUrl: isUrl,
});
const plaf = PlatformPeg.get();
const callbackUrl = plaf.getSSOCallbackUrl(tmpClient.getHomeserverUrl(), tmpClient.getIdentityServerUrl());
return (
<AuthPage>
<div className="mx_Welcome">
<EmbeddedPage className="mx_WelcomePage"
<EmbeddedPage
className="mx_WelcomePage"
url={pageUrl}
replaceMap={{
"$riot:ssoUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "sso"),
"$riot:casUrl": tmpClient.getSsoLoginUrl(callbackUrl.toString(), "cas"),
}}
/>
<LanguageSelector />
</div>

View file

@ -20,6 +20,7 @@ import MemberAvatar from '../avatars/MemberAvatar';
import { _t } from '../../../languageHandler';
import {MatrixEvent, RoomMember} from "matrix-js-sdk";
import {useStateToggle} from "../../../hooks/useStateToggle";
import AccessibleButton from "./AccessibleButton";
const EventListSummary = ({events, children, threshold=3, onToggle, startExpanded, summaryMembers=[], summaryText}) => {
const [expanded, toggleExpanded] = useStateToggle(startExpanded);
@ -42,24 +43,15 @@ const EventListSummary = ({events, children, threshold=3, onToggle, startExpande
);
}
let body;
if (expanded) {
return (
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
<div className={"mx_EventListSummary_toggle"} onClick={toggleExpanded}>
{ _t('collapse') }
</div>
<div className="mx_EventListSummary_line">&nbsp;</div>
{ children }
</div>
);
}
const avatars = summaryMembers.map((m) => <MemberAvatar key={m.userId} member={m} width={14} height={14} />);
return (
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
<div className={"mx_EventListSummary_toggle"} onClick={toggleExpanded}>
{ _t('expand') }
</div>
body = <React.Fragment>
<div className="mx_EventListSummary_line">&nbsp;</div>
{ children }
</React.Fragment>;
} else {
const avatars = summaryMembers.map((m) => <MemberAvatar key={m.userId} member={m} width={14} height={14} />);
body = (
<div className="mx_EventTile_line">
<div className="mx_EventTile_info">
<span className="mx_EventListSummary_avatars" onClick={toggleExpanded}>
@ -70,6 +62,15 @@ const EventListSummary = ({events, children, threshold=3, onToggle, startExpande
</span>
</div>
</div>
);
}
return (
<div className="mx_EventListSummary" data-scroll-tokens={eventIds}>
<AccessibleButton className="mx_EventListSummary_toggle" onClick={toggleExpanded} aria-expanded={expanded}>
{ expanded ? _t('collapse') : _t('expand') }
</AccessibleButton>
{ body }
</div>
);
};

View file

@ -32,7 +32,7 @@ function getId() {
export default class Field extends React.PureComponent {
static propTypes = {
// The field's ID, which binds the input and label together. Immutable.
id: PropTypes.string.isRequired,
id: PropTypes.string,
// The element to create. Defaults to "input".
// To define options for a select, use <Field><option ... /></Field>
element: PropTypes.oneOf(["input", "select", "textarea"]),

View file

@ -68,8 +68,10 @@ export const getE2EStatus = (cli, userId, devices) => {
return hasUnverifiedDevice ? "warning" : "verified";
}
const isMe = userId === cli.getUserId();
const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified();
if (!userVerified) return "normal";
const userTrust = cli.checkUserTrust(userId);
if (!userTrust.isCrossSigningVerified()) {
return userTrust.wasCrossSigningVerified() ? "warning" : "normal";
}
const anyDeviceUnverified = devices.some(device => {
const { deviceId } = device;

View file

@ -121,10 +121,10 @@ export default createReactClass({
const cli = MatrixClientPeg.get();
const { userId } = this.props.member;
const isMe = userId === cli.getUserId();
const userVerified = cli.checkUserTrust(userId).isCrossSigningVerified();
if (!userVerified) {
const userTrust = cli.checkUserTrust(userId);
if (!userTrust.isCrossSigningVerified()) {
this.setState({
e2eStatus: "normal",
e2eStatus: userTrust.wasCrossSigningVerified() ? "warning" : "normal",
});
return;
}

View file

@ -19,12 +19,13 @@ import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import * as sdk from '../../../index';
import classNames from 'classnames';
import AccessibleButton from "../elements/AccessibleButton";
export default class MessageComposerFormatBar extends React.PureComponent {
static propTypes = {
onAction: PropTypes.func.isRequired,
shortcuts: PropTypes.object.isRequired,
}
};
constructor(props) {
super(props);
@ -64,7 +65,7 @@ class FormatButton extends React.PureComponent {
icon: PropTypes.string.isRequired,
shortcut: PropTypes.string,
visible: PropTypes.bool,
}
};
render() {
const InteractiveTooltip = sdk.getComponent('elements.InteractiveTooltip');
@ -82,11 +83,12 @@ class FormatButton extends React.PureComponent {
return (
<InteractiveTooltip content={tooltipContent} forceHidden={!this.props.visible}>
<span aria-label={this.props.label}
role="button"
onClick={this.props.onClick}
className={className}>
</span>
<AccessibleButton
as="span"
role="button"
onClick={this.props.onClick}
aria-label={this.props.label}
className={className} />
</InteractiveTooltip>
);
}

View file

@ -37,6 +37,7 @@ import E2EIcon from './E2EIcon';
import InviteOnlyIcon from './InviteOnlyIcon';
// eslint-disable-next-line camelcase
import rate_limited_func from '../../../ratelimitedfunc';
import { shieldStatusForRoom } from '../../../utils/ShieldUtils';
export default createReactClass({
displayName: 'RoomTile',
@ -154,35 +155,9 @@ export default createReactClass({
return;
}
// Duplication between here and _updateE2eStatus in RoomView
const e2eMembers = await this.props.room.getEncryptionTargetMembers();
const verified = [];
const unverified = [];
e2eMembers.map(({userId}) => userId)
.filter((userId) => userId !== cli.getUserId())
.forEach((userId) => {
(cli.checkUserTrust(userId).isCrossSigningVerified() ?
verified : unverified).push(userId);
});
/* Check all verified user devices. */
/* Don't alarm if no other users are verified */
const targets = (verified.length > 0) ? [...verified, cli.getUserId()] : verified;
for (const userId of targets) {
const devices = await cli.getStoredDevicesForUser(userId);
const allDevicesVerified = devices.every(({deviceId}) => {
return cli.checkDeviceTrust(userId, deviceId).isVerified();
});
if (!allDevicesVerified) {
this.setState({
e2eStatus: "warning",
});
return;
}
}
/* At this point, the user has encryption on and cross-signing on */
this.setState({
e2eStatus: unverified.length === 0 ? "verified" : "normal",
e2eStatus: await shieldStatusForRoom(cli, this.props.room),
});
},

View file

@ -153,6 +153,7 @@
"Usage": "Usage",
"Prepends ¯\\_(ツ)_/¯ to a plain-text message": "Prepends ¯\\_(ツ)_/¯ to a plain-text message",
"Sends a message as plain text, without interpreting it as markdown": "Sends a message as plain text, without interpreting it as markdown",
"Sends a message as html, without interpreting it as markdown": "Sends a message as html, without interpreting it as markdown",
"Searches DuckDuckGo for results": "Searches DuckDuckGo for results",
"/ddg is not a command": "/ddg is not a command",
"To use it, just wait for autocomplete results to load and tab through them.": "To use it, just wait for autocomplete results to load and tab through them.",
@ -1880,6 +1881,7 @@
"Find other public servers or use a custom server": "Find other public servers or use a custom server",
"Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s",
"Sign in to your Matrix account on <underlinedServerName />": "Sign in to your Matrix account on <underlinedServerName />",
"Sign in with SSO": "Sign in with SSO",
"Sorry, your browser is <b>not</b> able to run Riot.": "Sorry, your browser is <b>not</b> able to run Riot.",
"Riot uses many advanced browser features, some of which are not available or experimental in your current browser.": "Riot uses many advanced browser features, some of which are not available or experimental in your current browser.",
"Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.": "Please install <chromeLink>Chrome</chromeLink>, <firefoxLink>Firefox</firefoxLink>, or <safariLink>Safari</safariLink> for the best experience.",

View file

@ -469,6 +469,9 @@ export default class EventIndex extends EventEmitter {
// decryption keys, do we want to retry this checkpoint at a later
// stage?
const filteredEvents = matrixEvents.filter(this.isValidEvent);
const undecryptableEvents = matrixEvents.filter((ev) => {
return ev.isDecryptionFailure();
});
// Collect the redaction events so we can delete the redacted events
// from the index.
@ -503,7 +506,10 @@ export default class EventIndex extends EventEmitter {
console.log(
"EventIndex: Crawled room",
client.getRoom(checkpoint.roomId).name,
"and fetched", events.length, "events.",
"and fetched total", matrixEvents.length, "events of which",
events.length, "are being added,", redactionEvents.length,
"are redacted,", matrixEvents.length - events.length,
"are being skipped, undecryptable", undecryptableEvents.length,
);
try {

58
src/utils/ShieldUtils.ts Normal file
View file

@ -0,0 +1,58 @@
import DMRoomMap from './DMRoomMap';
/* For now, a cut-down type spec for the client */
interface Client {
getUserId: () => string;
checkUserTrust: (userId: string) => {
isCrossSigningVerified: () => boolean
wasCrossSigningVerified: () => boolean
};
getStoredDevicesForUser: (userId: string) => Promise<[{ deviceId: string }]>;
checkDeviceTrust: (userId: string, deviceId: string) => {
isVerified: () => boolean
}
}
interface Room {
getEncryptionTargetMembers: () => Promise<[{userId: string}]>;
roomId: string;
}
export async function shieldStatusForRoom(client: Client, room: Room): Promise<string> {
const members = (await room.getEncryptionTargetMembers()).map(({userId}) => userId);
const inDMMap = !!DMRoomMap.shared().getUserIdForRoomId(room.roomId);
const verified: string[] = [];
const unverified: string[] = [];
members.filter((userId) => userId !== client.getUserId())
.forEach((userId) => {
(client.checkUserTrust(userId).isCrossSigningVerified() ?
verified : unverified).push(userId);
});
/* Alarm if any unverified users were verified before. */
for (const userId of unverified) {
if (client.checkUserTrust(userId).wasCrossSigningVerified()) {
return "warning";
}
}
/* Check all verified user devices. */
/* Don't alarm if no other users are verified */
const includeUser = (verified.length > 0) && // Don't alarm for self in rooms where nobody else is verified
!inDMMap && // Don't alarm for self in DMs with other users
(members.length !== 2) || // Don't alarm for self in 1:1 chats with other users
(members.length === 1); // Do alarm for self if we're alone in a room
const targets = includeUser ? [...verified, client.getUserId()] : verified;
for (const userId of targets) {
const devices = await client.getStoredDevicesForUser(userId);
const anyDeviceNotVerified = devices.some(({deviceId}) => {
return !client.checkDeviceTrust(userId, deviceId).isVerified();
});
if (anyDeviceNotVerified) {
return "warning";
}
}
return unverified.length === 0 ? "verified" : "normal";
}

View file

@ -0,0 +1,183 @@
import { shieldStatusForRoom } from '../../src/utils/ShieldUtils';
import DMRoomMap from '../../src/utils/DMRoomMap';
function mkClient(selfTrust) {
return {
getUserId: () => "@self:localhost",
checkUserTrust: (userId) => ({
isCrossSigningVerified: () => userId[1] == "T",
wasCrossSigningVerified: () => userId[1] == "T" || userId[1] == "W",
}),
checkDeviceTrust: (userId, deviceId) => ({
isVerified: () => userId === "@self:localhost" ? selfTrust : userId[2] == "T",
}),
getStoredDevicesForUser: async (userId) => ["DEVICE"],
};
}
describe("mkClient self-test", function() {
test.each([true, false])("behaves well for self-trust=%s", (v) => {
const client = mkClient(v);
expect(client.checkDeviceTrust("@self:localhost", "DEVICE").isVerified()).toBe(v);
});
test.each([
["@TT:h", true],
["@TF:h", true],
["@FT:h", false],
["@FF:h", false]],
)("behaves well for user trust %s", (userId, trust) => {
expect(mkClient().checkUserTrust(userId).isCrossSigningVerified()).toBe(trust);
});
test.each([
["@TT:h", true],
["@TF:h", false],
["@FT:h", true],
["@FF:h", false]],
)("behaves well for device trust %s", (userId, trust) => {
expect(mkClient().checkDeviceTrust(userId, "device").isVerified()).toBe(trust);
});
});
describe("shieldStatusForMembership self-trust behaviour", function() {
beforeAll(() => {
DMRoomMap._sharedInstance = {
getUserIdForRoomId: (roomId) => roomId === "DM" ? "@any:h" : null,
};
});
it.each(
[[true, true], [true, false],
[false, true], [false, false]],
)("2 unverified: returns 'normal', self-trust = %s, DM = %s", async (trusted, dm) => {
const client = mkClient(trusted);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@FF1:h", "@FF2:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual("normal");
});
it.each(
[["verified", true, true], ["verified", true, false],
["verified", false, true], ["warning", false, false]],
)("2 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
const client = mkClient(trusted);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@TT1:h", "@TT2:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
it.each(
[["normal", true, true], ["normal", true, false],
["normal", false, true], ["warning", false, false]],
)("2 mixed: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
const client = mkClient(trusted);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@TT1:h", "@FF2:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
it.each(
[["verified", true, true], ["verified", true, false],
["warning", false, true], ["warning", false, false]],
)("0 others: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
const client = mkClient(trusted);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
it.each(
[["verified", true, true], ["verified", true, false],
["verified", false, true], ["verified", false, false]],
)("1 verified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
const client = mkClient(trusted);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@TT:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
it.each(
[["normal", true, true], ["normal", true, false],
["normal", false, true], ["normal", false, false]],
)("1 unverified: returns '%s', self-trust = %s, DM = %s", async (result, trusted, dm) => {
const client = mkClient(trusted);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@FF:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
});
describe("shieldStatusForMembership other-trust behaviour", function() {
beforeAll(() => {
DMRoomMap._sharedInstance = {
getUserIdForRoomId: (roomId) => roomId === "DM" ? "@any:h" : null,
};
});
it.each(
[["warning", true], ["warning", false]],
)("1 verified/untrusted: returns '%s', DM = %s", async (result, dm) => {
const client = mkClient(true);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@TF:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
it.each(
[["warning", true], ["warning", false]],
)("2 verified/untrusted: returns '%s', DM = %s", async (result, dm) => {
const client = mkClient(true);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@TF:h", "@TT:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
it.each(
[["normal", true], ["normal", false]],
)("2 unverified/untrusted: returns '%s', DM = %s", async (result, dm) => {
const client = mkClient(true);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@FF:h", "@FT:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
it.each(
[["warning", true], ["warning", false]],
)("2 was verified: returns '%s', DM = %s", async (result, dm) => {
const client = mkClient(true);
const room = {
roomId: dm ? "DM" : "other",
getEncryptionTargetMembers: () => ["@self:localhost", "@WF:h", "@FT:h"].map((userId) => ({userId})),
};
const status = await shieldStatusForRoom(client, room);
expect(status).toEqual(result);
});
});

View file

@ -5690,8 +5690,8 @@ mathml-tag-names@^2.0.1:
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop":
version "5.1.1"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/b2e154377a4268441a3b27b183dd7f7018187035"
version "5.2.0"
resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/223d37ffce674a23ca73702f04b9ba31cfd84196"
dependencies:
"@babel/runtime" "^7.8.3"
another-json "^0.2.0"