mirror of https://github.com/vector-im/riot-web
Merge pull request #5727 from matrix-org/matthew/rework-cross-signing-login
Rework cross-signing login flowpull/21833/head
commit
6a939c4de8
|
@ -26,7 +26,9 @@ limitations under the License.
|
||||||
padding: 7px 18px;
|
padding: 7px 18px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
font-size: $font-14px;
|
font-size: $font-14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,11 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.mx_UserInfo {
|
.mx_EncryptionInfo_spinner {
|
||||||
.mx_EncryptionInfo_spinner {
|
.mx_Spinner {
|
||||||
.mx_Spinner {
|
margin-top: 25px;
|
||||||
margin-top: 25px;
|
margin-bottom: 15px;
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { _t } from '../../../languageHandler';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {
|
import {
|
||||||
SetupEncryptionStore,
|
SetupEncryptionStore,
|
||||||
|
PHASE_LOADING,
|
||||||
PHASE_INTRO,
|
PHASE_INTRO,
|
||||||
PHASE_BUSY,
|
PHASE_BUSY,
|
||||||
PHASE_DONE,
|
PHASE_DONE,
|
||||||
|
@ -58,7 +59,9 @@ export default class CompleteSecurity extends React.Component {
|
||||||
let icon;
|
let icon;
|
||||||
let title;
|
let title;
|
||||||
|
|
||||||
if (phase === PHASE_INTRO) {
|
if (phase === PHASE_LOADING) {
|
||||||
|
return null;
|
||||||
|
} else if (phase === PHASE_INTRO) {
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
|
||||||
title = _t("Verify this login");
|
title = _t("Verify this login");
|
||||||
} else if (phase === PHASE_DONE) {
|
} else if (phase === PHASE_DONE) {
|
||||||
|
|
|
@ -17,11 +17,13 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
|
import Modal from '../../../Modal';
|
||||||
|
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
import {
|
import {
|
||||||
SetupEncryptionStore,
|
SetupEncryptionStore,
|
||||||
|
PHASE_LOADING,
|
||||||
PHASE_INTRO,
|
PHASE_INTRO,
|
||||||
PHASE_BUSY,
|
PHASE_BUSY,
|
||||||
PHASE_DONE,
|
PHASE_DONE,
|
||||||
|
@ -81,6 +83,22 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
store.usePassPhrase();
|
store.usePassPhrase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onVerifyClick = () => {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const userId = cli.getUserId();
|
||||||
|
const requestPromise = cli.requestVerification(userId);
|
||||||
|
|
||||||
|
this.props.onFinished(true);
|
||||||
|
Modal.createTrackedDialog('New Session Verification', 'Starting dialog', VerificationRequestDialog, {
|
||||||
|
verificationRequestPromise: requestPromise,
|
||||||
|
member: cli.getUser(userId),
|
||||||
|
onFinished: async () => {
|
||||||
|
const request = await requestPromise;
|
||||||
|
request.cancel();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onSkipClick = () => {
|
onSkipClick = () => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.skip();
|
store.skip();
|
||||||
|
@ -132,32 +150,22 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
</AccessibleButton>;
|
</AccessibleButton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const brand = SdkConfig.get().brand;
|
let verifyButton;
|
||||||
|
if (store.hasDevicesToVerifyAgainst) {
|
||||||
|
verifyButton = <AccessibleButton kind="primary" onClick={this._onVerifyClick}>
|
||||||
|
{ _t("Verify with another session") }
|
||||||
|
</AccessibleButton>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
"Confirm your identity by verifying this login from one of your other sessions, " +
|
"Verify this login to access your encrypted messages and " +
|
||||||
"granting it access to encrypted messages.",
|
"prove to others that this login is really you.",
|
||||||
)}</p>
|
)}</p>
|
||||||
<p>{_t(
|
|
||||||
"This requires the latest %(brand)s on your other devices:",
|
|
||||||
{ brand },
|
|
||||||
)}</p>
|
|
||||||
|
|
||||||
<div className="mx_CompleteSecurity_clients">
|
|
||||||
<div className="mx_CompleteSecurity_clients_desktop">
|
|
||||||
<div>{_t("%(brand)s Web", { brand })}</div>
|
|
||||||
<div>{_t("%(brand)s Desktop", { brand })}</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx_CompleteSecurity_clients_mobile">
|
|
||||||
<div>{_t("%(brand)s iOS", { brand })}</div>
|
|
||||||
<div>{_t("%(brand)s Android", { brand })}</div>
|
|
||||||
</div>
|
|
||||||
<p>{_t("or another cross-signing capable Matrix client")}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
|
{verifyButton}
|
||||||
{useRecoveryKeyButton}
|
{useRecoveryKeyButton}
|
||||||
<AccessibleButton kind="danger" onClick={this.onSkipClick}>
|
<AccessibleButton kind="danger" onClick={this.onSkipClick}>
|
||||||
{_t("Skip")}
|
{_t("Skip")}
|
||||||
|
@ -215,7 +223,7 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (phase === PHASE_BUSY) {
|
} else if (phase === PHASE_BUSY || phase === PHASE_LOADING) {
|
||||||
const Spinner = sdk.getComponent('views.elements.Spinner');
|
const Spinner = sdk.getComponent('views.elements.Spinner');
|
||||||
return <Spinner />;
|
return <Spinner />;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -66,6 +66,10 @@ export default class NewSessionReviewDialog extends React.PureComponent {
|
||||||
Modal.createTrackedDialog('New Session Verification', 'Starting dialog', VerificationRequestDialog, {
|
Modal.createTrackedDialog('New Session Verification', 'Starting dialog', VerificationRequestDialog, {
|
||||||
verificationRequestPromise: requestPromise,
|
verificationRequestPromise: requestPromise,
|
||||||
member: cli.getUser(userId),
|
member: cli.getUser(userId),
|
||||||
|
onFinished: async () => {
|
||||||
|
const request = await requestPromise;
|
||||||
|
request.cancel();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,11 @@ export default class VerificationRequestDialog extends React.Component {
|
||||||
verificationRequest: PropTypes.object,
|
verificationRequest: PropTypes.object,
|
||||||
verificationRequestPromise: PropTypes.object,
|
verificationRequestPromise: PropTypes.object,
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
member: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(...args) {
|
constructor(...args) {
|
||||||
super(...args);
|
super(...args);
|
||||||
this.onFinished = this.onFinished.bind(this);
|
|
||||||
this.state = {};
|
this.state = {};
|
||||||
if (this.props.verificationRequest) {
|
if (this.props.verificationRequest) {
|
||||||
this.state.verificationRequest = this.props.verificationRequest;
|
this.state.verificationRequest = this.props.verificationRequest;
|
||||||
|
@ -50,7 +50,7 @@ export default class VerificationRequestDialog extends React.Component {
|
||||||
const title = request && request.isSelfVerification ?
|
const title = request && request.isSelfVerification ?
|
||||||
_t("Verify other session") : _t("Verification Request");
|
_t("Verify other session") : _t("Verification Request");
|
||||||
|
|
||||||
return <BaseDialog className="mx_InfoDialog" onFinished={this.onFinished}
|
return <BaseDialog className="mx_InfoDialog" onFinished={this.props.onFinished}
|
||||||
contentId="mx_Dialog_content"
|
contentId="mx_Dialog_content"
|
||||||
title={title}
|
title={title}
|
||||||
hasCancel={true}
|
hasCancel={true}
|
||||||
|
@ -64,13 +64,4 @@ export default class VerificationRequestDialog extends React.Component {
|
||||||
/>
|
/>
|
||||||
</BaseDialog>;
|
</BaseDialog>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onFinished() {
|
|
||||||
this.props.onFinished();
|
|
||||||
let request = this.props.verificationRequest;
|
|
||||||
if (!request && this.props.verificationRequestPromise) {
|
|
||||||
request = await this.props.verificationRequestPromise;
|
|
||||||
}
|
|
||||||
request.cancel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ import EncryptionPanel from "./EncryptionPanel";
|
||||||
import {useAsyncMemo} from '../../../hooks/useAsyncMemo';
|
import {useAsyncMemo} from '../../../hooks/useAsyncMemo';
|
||||||
import {legacyVerifyUser, verifyDevice, verifyUser} from '../../../verification';
|
import {legacyVerifyUser, verifyDevice, verifyUser} from '../../../verification';
|
||||||
import {Action} from "../../../dispatcher/actions";
|
import {Action} from "../../../dispatcher/actions";
|
||||||
|
import { USER_SECURITY_TAB } from "../dialogs/UserSettingsDialog";
|
||||||
import {useIsEncrypted} from "../../../hooks/useIsEncrypted";
|
import {useIsEncrypted} from "../../../hooks/useIsEncrypted";
|
||||||
import BaseCard from "./BaseCard";
|
import BaseCard from "./BaseCard";
|
||||||
import {E2EStatus} from "../../../utils/ShieldUtils";
|
import {E2EStatus} from "../../../utils/ShieldUtils";
|
||||||
|
@ -1367,6 +1368,20 @@ const BasicUserInfo: React.FC<{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let editDevices;
|
||||||
|
if (member.userId == cli.getUserId()) {
|
||||||
|
editDevices = (<p>
|
||||||
|
<AccessibleButton className="mx_UserInfo_field" onClick={() => {
|
||||||
|
dis.dispatch({
|
||||||
|
action: Action.ViewUserSettings,
|
||||||
|
initialTabId: USER_SECURITY_TAB,
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
{ _t("Edit devices") }
|
||||||
|
</AccessibleButton>
|
||||||
|
</p>)
|
||||||
|
}
|
||||||
|
|
||||||
const securitySection = (
|
const securitySection = (
|
||||||
<div className="mx_UserInfo_container">
|
<div className="mx_UserInfo_container">
|
||||||
<h3>{ _t("Security") }</h3>
|
<h3>{ _t("Security") }</h3>
|
||||||
|
@ -1376,6 +1391,7 @@ const BasicUserInfo: React.FC<{
|
||||||
loading={showDeviceListSpinner}
|
loading={showDeviceListSpinner}
|
||||||
devices={devices}
|
devices={devices}
|
||||||
userId={member.userId} /> }
|
userId={member.userId} /> }
|
||||||
|
{ editDevices }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ interface IProps {
|
||||||
interface IState {
|
interface IState {
|
||||||
counter: number;
|
counter: number;
|
||||||
device?: DeviceInfo;
|
device?: DeviceInfo;
|
||||||
|
ip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class VerificationRequestToast extends React.PureComponent<IProps, IState> {
|
export default class VerificationRequestToast extends React.PureComponent<IProps, IState> {
|
||||||
|
@ -66,9 +67,15 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||||
// a toast hanging around after logging in if you did a verification as part of login).
|
// a toast hanging around after logging in if you did a verification as part of login).
|
||||||
this._checkRequestIsPending();
|
this._checkRequestIsPending();
|
||||||
|
|
||||||
|
|
||||||
if (request.isSelfVerification) {
|
if (request.isSelfVerification) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
this.setState({device: cli.getStoredDevice(cli.getUserId(), request.channel.deviceId)});
|
const device = await cli.getDevice(request.channel.deviceId);
|
||||||
|
const ip = device.last_seen_ip;
|
||||||
|
this.setState({
|
||||||
|
device: cli.getStoredDevice(cli.getUserId(), request.channel.deviceId),
|
||||||
|
ip,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +125,9 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||||
const VerificationRequestDialog = sdk.getComponent("views.dialogs.VerificationRequestDialog");
|
const VerificationRequestDialog = sdk.getComponent("views.dialogs.VerificationRequestDialog");
|
||||||
Modal.createTrackedDialog('Incoming Verification', '', VerificationRequestDialog, {
|
Modal.createTrackedDialog('Incoming Verification', '', VerificationRequestDialog, {
|
||||||
verificationRequest: request,
|
verificationRequest: request,
|
||||||
|
onFinished: () => {
|
||||||
|
request.cancel();
|
||||||
|
},
|
||||||
}, null, /* priority = */ false, /* static = */ true);
|
}, null, /* priority = */ false, /* static = */ true);
|
||||||
}
|
}
|
||||||
await request.accept();
|
await request.accept();
|
||||||
|
@ -131,9 +141,10 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
|
||||||
let nameLabel;
|
let nameLabel;
|
||||||
if (request.isSelfVerification) {
|
if (request.isSelfVerification) {
|
||||||
if (this.state.device) {
|
if (this.state.device) {
|
||||||
nameLabel = _t("From %(deviceName)s (%(deviceId)s)", {
|
nameLabel = _t("From %(deviceName)s (%(deviceId)s) at %(ip)s", {
|
||||||
deviceName: this.state.device.getDisplayName(),
|
deviceName: this.state.device.getDisplayName(),
|
||||||
deviceId: this.state.device.deviceId,
|
deviceId: this.state.device.deviceId,
|
||||||
|
ip: this.state.ip,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -728,7 +728,7 @@
|
||||||
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.",
|
"Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.": "Send <UsageDataLink>anonymous usage data</UsageDataLink> which helps us improve %(brand)s. This will use a <PolicyLink>cookie</PolicyLink>.",
|
||||||
"Yes": "Yes",
|
"Yes": "Yes",
|
||||||
"No": "No",
|
"No": "No",
|
||||||
"Review where you’re logged in": "Review where you’re logged in",
|
"You have unverified logins": "You have unverified logins",
|
||||||
"Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
|
"Verify all your sessions to ensure your account & messages are safe": "Verify all your sessions to ensure your account & messages are safe",
|
||||||
"Review": "Review",
|
"Review": "Review",
|
||||||
"Later": "Later",
|
"Later": "Later",
|
||||||
|
@ -753,7 +753,8 @@
|
||||||
"Safeguard against losing access to encrypted messages & data": "Safeguard against losing access to encrypted messages & data",
|
"Safeguard against losing access to encrypted messages & data": "Safeguard against losing access to encrypted messages & data",
|
||||||
"Other users may not trust it": "Other users may not trust it",
|
"Other users may not trust it": "Other users may not trust it",
|
||||||
"New login. Was this you?": "New login. Was this you?",
|
"New login. Was this you?": "New login. Was this you?",
|
||||||
"Verify the new login accessing your account: %(name)s": "Verify the new login accessing your account: %(name)s",
|
"A new login is accessing your account: %(name)s (%(deviceID)s) at %(ip)s": "A new login is accessing your account: %(name)s (%(deviceID)s) at %(ip)s",
|
||||||
|
"Check your devices": "Check your devices",
|
||||||
"What's new?": "What's new?",
|
"What's new?": "What's new?",
|
||||||
"What's New": "What's New",
|
"What's New": "What's New",
|
||||||
"Update": "Update",
|
"Update": "Update",
|
||||||
|
@ -980,7 +981,7 @@
|
||||||
"Folder": "Folder",
|
"Folder": "Folder",
|
||||||
"Pin": "Pin",
|
"Pin": "Pin",
|
||||||
"Your server isn't responding to some <a>requests</a>.": "Your server isn't responding to some <a>requests</a>.",
|
"Your server isn't responding to some <a>requests</a>.": "Your server isn't responding to some <a>requests</a>.",
|
||||||
"From %(deviceName)s (%(deviceId)s)": "From %(deviceName)s (%(deviceId)s)",
|
"From %(deviceName)s (%(deviceId)s) at %(ip)s": "From %(deviceName)s (%(deviceId)s) at %(ip)s",
|
||||||
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
"Decline (%(counter)s)": "Decline (%(counter)s)",
|
||||||
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
"Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
|
||||||
"Delete": "Delete",
|
"Delete": "Delete",
|
||||||
|
@ -1756,6 +1757,7 @@
|
||||||
"Failed to deactivate user": "Failed to deactivate user",
|
"Failed to deactivate user": "Failed to deactivate user",
|
||||||
"Role": "Role",
|
"Role": "Role",
|
||||||
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
|
"This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
|
||||||
|
"Edit devices": "Edit devices",
|
||||||
"Security": "Security",
|
"Security": "Security",
|
||||||
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.",
|
"The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.": "The session you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.",
|
||||||
"Verify by scanning": "Verify by scanning",
|
"Verify by scanning": "Verify by scanning",
|
||||||
|
@ -2720,13 +2722,8 @@
|
||||||
"Decide where your account is hosted": "Decide where your account is hosted",
|
"Decide where your account is hosted": "Decide where your account is hosted",
|
||||||
"Use Security Key or Phrase": "Use Security Key or Phrase",
|
"Use Security Key or Phrase": "Use Security Key or Phrase",
|
||||||
"Use Security Key": "Use Security Key",
|
"Use Security Key": "Use Security Key",
|
||||||
"Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.",
|
"Verify with another session": "Verify with another session",
|
||||||
"This requires the latest %(brand)s on your other devices:": "This requires the latest %(brand)s on your other devices:",
|
"Verify this login to access your encrypted messages and prove to others that this login is really you.": "Verify this login to access your encrypted messages and prove to others that this login is really you.",
|
||||||
"%(brand)s Web": "%(brand)s Web",
|
|
||||||
"%(brand)s Desktop": "%(brand)s Desktop",
|
|
||||||
"%(brand)s iOS": "%(brand)s iOS",
|
|
||||||
"%(brand)s Android": "%(brand)s Android",
|
|
||||||
"or another cross-signing capable Matrix client": "or another cross-signing capable Matrix client",
|
|
||||||
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
||||||
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
||||||
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
||||||
|
|
|
@ -19,11 +19,12 @@ import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import { accessSecretStorage, AccessCancelledError } from '../SecurityManager';
|
import { accessSecretStorage, AccessCancelledError } from '../SecurityManager';
|
||||||
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
|
|
||||||
export const PHASE_INTRO = 0;
|
export const PHASE_LOADING = 0;
|
||||||
export const PHASE_BUSY = 1;
|
export const PHASE_INTRO = 1;
|
||||||
export const PHASE_DONE = 2; //final done stage, but still showing UX
|
export const PHASE_BUSY = 2;
|
||||||
export const PHASE_CONFIRM_SKIP = 3;
|
export const PHASE_DONE = 3; //final done stage, but still showing UX
|
||||||
export const PHASE_FINISHED = 4; //UX can be closed
|
export const PHASE_CONFIRM_SKIP = 4;
|
||||||
|
export const PHASE_FINISHED = 5; //UX can be closed
|
||||||
|
|
||||||
export class SetupEncryptionStore extends EventEmitter {
|
export class SetupEncryptionStore extends EventEmitter {
|
||||||
static sharedInstance() {
|
static sharedInstance() {
|
||||||
|
@ -36,7 +37,7 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._started = true;
|
this._started = true;
|
||||||
this.phase = PHASE_BUSY;
|
this.phase = PHASE_LOADING;
|
||||||
this.verificationRequest = null;
|
this.verificationRequest = null;
|
||||||
this.backupInfo = null;
|
this.backupInfo = null;
|
||||||
|
|
||||||
|
@ -75,7 +76,8 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchKeyInfo() {
|
async fetchKeyInfo() {
|
||||||
const keys = await MatrixClientPeg.get().isSecretStored('m.cross_signing.master', false);
|
const cli = MatrixClientPeg.get();
|
||||||
|
const keys = await cli.isSecretStored('m.cross_signing.master', false);
|
||||||
if (keys === null || Object.keys(keys).length === 0) {
|
if (keys === null || Object.keys(keys).length === 0) {
|
||||||
this.keyId = null;
|
this.keyId = null;
|
||||||
this.keyInfo = null;
|
this.keyInfo = null;
|
||||||
|
@ -85,7 +87,20 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
this.keyInfo = keys[this.keyId];
|
this.keyInfo = keys[this.keyId];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.phase = PHASE_INTRO;
|
// do we have any other devices which are E2EE which we can verify against?
|
||||||
|
const dehydratedDevice = await cli.getDehydratedDevice();
|
||||||
|
this.hasDevicesToVerifyAgainst = cli.getStoredDevicesForUser(cli.getUserId()).some(
|
||||||
|
device =>
|
||||||
|
device.getIdentityKey() &&
|
||||||
|
(!dehydratedDevice || (device.deviceId != dehydratedDevice.device_id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this.hasDevicesToVerifyAgainst && !this.keyInfo) {
|
||||||
|
// skip before we can even render anything.
|
||||||
|
this.phase = PHASE_FINISHED;
|
||||||
|
} else {
|
||||||
|
this.phase = PHASE_INTRO;
|
||||||
|
}
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const showToast = (deviceIds: Set<string>) => {
|
||||||
|
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
key: TOAST_KEY,
|
key: TOAST_KEY,
|
||||||
title: _t("Review where you’re logged in"),
|
title: _t("You have unverified logins"),
|
||||||
icon: "verification_warning",
|
icon: "verification_warning",
|
||||||
props: {
|
props: {
|
||||||
description: _t("Verify all your sessions to ensure your account & messages are safe"),
|
description: _t("Verify all your sessions to ensure your account & messages are safe"),
|
||||||
|
|
|
@ -15,38 +15,34 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { _t } from '../languageHandler';
|
import { _t } from '../languageHandler';
|
||||||
|
import dis from "../dispatcher/dispatcher";
|
||||||
import { MatrixClientPeg } from '../MatrixClientPeg';
|
import { MatrixClientPeg } from '../MatrixClientPeg';
|
||||||
import Modal from '../Modal';
|
|
||||||
import DeviceListener from '../DeviceListener';
|
import DeviceListener from '../DeviceListener';
|
||||||
import NewSessionReviewDialog from '../components/views/dialogs/NewSessionReviewDialog';
|
|
||||||
import ToastStore from "../stores/ToastStore";
|
import ToastStore from "../stores/ToastStore";
|
||||||
import GenericToast from "../components/views/toasts/GenericToast";
|
import GenericToast from "../components/views/toasts/GenericToast";
|
||||||
|
import { Action } from "../dispatcher/actions";
|
||||||
|
import { USER_SECURITY_TAB } from "../components/views/dialogs/UserSettingsDialog";
|
||||||
|
|
||||||
function toastKey(deviceId: string) {
|
function toastKey(deviceId: string) {
|
||||||
return "unverified_session_" + deviceId;
|
return "unverified_session_" + deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const showToast = (deviceId: string) => {
|
export const showToast = async (deviceId: string) => {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
const onAccept = () => {
|
const onAccept = () => {
|
||||||
Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
|
DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
|
||||||
userId: cli.getUserId(),
|
dis.dispatch({
|
||||||
device: cli.getStoredDevice(cli.getUserId(), deviceId),
|
action: Action.ViewUserSettings,
|
||||||
onFinished: (r) => {
|
initialTabId: USER_SECURITY_TAB,
|
||||||
if (!r) {
|
});
|
||||||
/* This'll come back false if the user clicks "this wasn't me" and saw a warning dialog */
|
|
||||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, null, /* priority = */ false, /* static = */ true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReject = () => {
|
const onReject = () => {
|
||||||
DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
|
DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const device = cli.getStoredDevice(cli.getUserId(), deviceId);
|
const device = await cli.getDevice(deviceId);
|
||||||
|
|
||||||
ToastStore.sharedInstance().addOrReplaceToast({
|
ToastStore.sharedInstance().addOrReplaceToast({
|
||||||
key: toastKey(deviceId),
|
key: toastKey(deviceId),
|
||||||
|
@ -54,8 +50,13 @@ export const showToast = (deviceId: string) => {
|
||||||
icon: "verification_warning",
|
icon: "verification_warning",
|
||||||
props: {
|
props: {
|
||||||
description: _t(
|
description: _t(
|
||||||
"Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()}),
|
"A new login is accessing your account: %(name)s (%(deviceID)s) at %(ip)s", {
|
||||||
acceptLabel: _t("Verify"),
|
name: device.display_name,
|
||||||
|
deviceID: deviceId,
|
||||||
|
ip: device.last_seen_ip,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
acceptLabel: _t("Check your devices"),
|
||||||
onAccept,
|
onAccept,
|
||||||
rejectLabel: _t("Later"),
|
rejectLabel: _t("Later"),
|
||||||
onReject,
|
onReject,
|
||||||
|
|
Loading…
Reference in New Issue