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;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    font-size: $font-14px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,13 +14,11 @@ See the License for the specific language governing permissions and
 | 
			
		|||
limitations under the License.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
.mx_UserInfo {
 | 
			
		||||
    .mx_EncryptionInfo_spinner {
 | 
			
		||||
        .mx_Spinner {
 | 
			
		||||
            margin-top: 25px;
 | 
			
		||||
            margin-bottom: 15px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        text-align: center;
 | 
			
		||||
.mx_EncryptionInfo_spinner {
 | 
			
		||||
    .mx_Spinner {
 | 
			
		||||
        margin-top: 25px;
 | 
			
		||||
        margin-bottom: 15px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,6 +20,7 @@ import { _t } from '../../../languageHandler';
 | 
			
		|||
import * as sdk from '../../../index';
 | 
			
		||||
import {
 | 
			
		||||
    SetupEncryptionStore,
 | 
			
		||||
    PHASE_LOADING,
 | 
			
		||||
    PHASE_INTRO,
 | 
			
		||||
    PHASE_BUSY,
 | 
			
		||||
    PHASE_DONE,
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +59,9 @@ export default class CompleteSecurity extends React.Component {
 | 
			
		|||
        let icon;
 | 
			
		||||
        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" />;
 | 
			
		||||
            title = _t("Verify this login");
 | 
			
		||||
        } else if (phase === PHASE_DONE) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,11 +17,13 @@ limitations under the License.
 | 
			
		|||
import React from 'react';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { _t } from '../../../languageHandler';
 | 
			
		||||
import SdkConfig from '../../../SdkConfig';
 | 
			
		||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
 | 
			
		||||
import Modal from '../../../Modal';
 | 
			
		||||
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
 | 
			
		||||
import * as sdk from '../../../index';
 | 
			
		||||
import {
 | 
			
		||||
    SetupEncryptionStore,
 | 
			
		||||
    PHASE_LOADING,
 | 
			
		||||
    PHASE_INTRO,
 | 
			
		||||
    PHASE_BUSY,
 | 
			
		||||
    PHASE_DONE,
 | 
			
		||||
| 
						 | 
				
			
			@ -81,6 +83,22 @@ export default class SetupEncryptionBody extends React.Component {
 | 
			
		|||
        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 = () => {
 | 
			
		||||
        const store = SetupEncryptionStore.sharedInstance();
 | 
			
		||||
        store.skip();
 | 
			
		||||
| 
						 | 
				
			
			@ -132,32 +150,22 @@ export default class SetupEncryptionBody extends React.Component {
 | 
			
		|||
                </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 (
 | 
			
		||||
                <div>
 | 
			
		||||
                    <p>{_t(
 | 
			
		||||
                        "Confirm your identity by verifying this login from one of your other sessions, " +
 | 
			
		||||
                        "granting it access to encrypted messages.",
 | 
			
		||||
                        "Verify this login to access your encrypted messages and " +
 | 
			
		||||
                        "prove to others that this login is really you.",
 | 
			
		||||
                    )}</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">
 | 
			
		||||
                        {verifyButton}
 | 
			
		||||
                        {useRecoveryKeyButton}
 | 
			
		||||
                        <AccessibleButton kind="danger" onClick={this.onSkipClick}>
 | 
			
		||||
                            {_t("Skip")}
 | 
			
		||||
| 
						 | 
				
			
			@ -215,7 +223,7 @@ export default class SetupEncryptionBody extends React.Component {
 | 
			
		|||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
        } else if (phase === PHASE_BUSY) {
 | 
			
		||||
        } else if (phase === PHASE_BUSY || phase === PHASE_LOADING) {
 | 
			
		||||
            const Spinner = sdk.getComponent('views.elements.Spinner');
 | 
			
		||||
            return <Spinner />;
 | 
			
		||||
        } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -66,6 +66,10 @@ export default class NewSessionReviewDialog extends React.PureComponent {
 | 
			
		|||
        Modal.createTrackedDialog('New Session Verification', 'Starting dialog', VerificationRequestDialog, {
 | 
			
		||||
            verificationRequestPromise: requestPromise,
 | 
			
		||||
            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,
 | 
			
		||||
        verificationRequestPromise: PropTypes.object,
 | 
			
		||||
        onFinished: PropTypes.func.isRequired,
 | 
			
		||||
        member: PropTypes.string,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constructor(...args) {
 | 
			
		||||
        super(...args);
 | 
			
		||||
        this.onFinished = this.onFinished.bind(this);
 | 
			
		||||
        this.state = {};
 | 
			
		||||
        if (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 ?
 | 
			
		||||
            _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"
 | 
			
		||||
                title={title}
 | 
			
		||||
                hasCancel={true}
 | 
			
		||||
| 
						 | 
				
			
			@ -64,13 +64,4 @@ export default class VerificationRequestDialog extends React.Component {
 | 
			
		|||
            />
 | 
			
		||||
        </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 {legacyVerifyUser, verifyDevice, verifyUser} from '../../../verification';
 | 
			
		||||
import {Action} from "../../../dispatcher/actions";
 | 
			
		||||
import { USER_SECURITY_TAB } from "../dialogs/UserSettingsDialog";
 | 
			
		||||
import {useIsEncrypted} from "../../../hooks/useIsEncrypted";
 | 
			
		||||
import BaseCard from "./BaseCard";
 | 
			
		||||
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 = (
 | 
			
		||||
        <div className="mx_UserInfo_container">
 | 
			
		||||
            <h3>{ _t("Security") }</h3>
 | 
			
		||||
| 
						 | 
				
			
			@ -1376,6 +1391,7 @@ const BasicUserInfo: React.FC<{
 | 
			
		|||
                loading={showDeviceListSpinner}
 | 
			
		||||
                devices={devices}
 | 
			
		||||
                userId={member.userId} /> }
 | 
			
		||||
            { editDevices }
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -38,6 +38,7 @@ interface IProps {
 | 
			
		|||
interface IState {
 | 
			
		||||
    counter: number;
 | 
			
		||||
    device?: DeviceInfo;
 | 
			
		||||
    ip?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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).
 | 
			
		||||
        this._checkRequestIsPending();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (request.isSelfVerification) {
 | 
			
		||||
            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");
 | 
			
		||||
                Modal.createTrackedDialog('Incoming Verification', '', VerificationRequestDialog, {
 | 
			
		||||
                    verificationRequest: request,
 | 
			
		||||
                    onFinished: () => {
 | 
			
		||||
                        request.cancel();
 | 
			
		||||
                    },
 | 
			
		||||
                }, null, /* priority = */ false, /* static = */ true);
 | 
			
		||||
            }
 | 
			
		||||
            await request.accept();
 | 
			
		||||
| 
						 | 
				
			
			@ -131,9 +141,10 @@ export default class VerificationRequestToast extends React.PureComponent<IProps
 | 
			
		|||
        let nameLabel;
 | 
			
		||||
        if (request.isSelfVerification) {
 | 
			
		||||
            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(),
 | 
			
		||||
                    deviceId: this.state.device.deviceId,
 | 
			
		||||
                    ip: this.state.ip,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        } 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>.",
 | 
			
		||||
    "Yes": "Yes",
 | 
			
		||||
    "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",
 | 
			
		||||
    "Review": "Review",
 | 
			
		||||
    "Later": "Later",
 | 
			
		||||
| 
						 | 
				
			
			@ -753,7 +753,8 @@
 | 
			
		|||
    "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",
 | 
			
		||||
    "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",
 | 
			
		||||
    "Update": "Update",
 | 
			
		||||
| 
						 | 
				
			
			@ -980,7 +981,7 @@
 | 
			
		|||
    "Folder": "Folder",
 | 
			
		||||
    "Pin": "Pin",
 | 
			
		||||
    "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)",
 | 
			
		||||
    "Accept <policyLink /> to continue:": "Accept <policyLink /> to continue:",
 | 
			
		||||
    "Delete": "Delete",
 | 
			
		||||
| 
						 | 
				
			
			@ -1756,6 +1757,7 @@
 | 
			
		|||
    "Failed to deactivate user": "Failed to deactivate user",
 | 
			
		||||
    "Role": "Role",
 | 
			
		||||
    "This client does not support end-to-end encryption.": "This client does not support end-to-end encryption.",
 | 
			
		||||
    "Edit devices": "Edit devices",
 | 
			
		||||
    "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.",
 | 
			
		||||
    "Verify by scanning": "Verify by scanning",
 | 
			
		||||
| 
						 | 
				
			
			@ -2720,13 +2722,8 @@
 | 
			
		|||
    "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": "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.",
 | 
			
		||||
    "This requires the latest %(brand)s on your other devices:": "This requires the latest %(brand)s on your other devices:",
 | 
			
		||||
    "%(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",
 | 
			
		||||
    "Verify with another session": "Verify with another session",
 | 
			
		||||
    "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.",
 | 
			
		||||
    "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.",
 | 
			
		||||
    "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 { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
 | 
			
		||||
 | 
			
		||||
export const PHASE_INTRO = 0;
 | 
			
		||||
export const PHASE_BUSY = 1;
 | 
			
		||||
export const PHASE_DONE = 2;    //final done stage, but still showing UX
 | 
			
		||||
export const PHASE_CONFIRM_SKIP = 3;
 | 
			
		||||
export const PHASE_FINISHED = 4; //UX can be closed
 | 
			
		||||
export const PHASE_LOADING = 0;
 | 
			
		||||
export const PHASE_INTRO = 1;
 | 
			
		||||
export const PHASE_BUSY = 2;
 | 
			
		||||
export const PHASE_DONE = 3;    //final done stage, but still showing UX
 | 
			
		||||
export const PHASE_CONFIRM_SKIP = 4;
 | 
			
		||||
export const PHASE_FINISHED = 5; //UX can be closed
 | 
			
		||||
 | 
			
		||||
export class SetupEncryptionStore extends EventEmitter {
 | 
			
		||||
    static sharedInstance() {
 | 
			
		||||
| 
						 | 
				
			
			@ -36,7 +37,7 @@ export class SetupEncryptionStore extends EventEmitter {
 | 
			
		|||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this._started = true;
 | 
			
		||||
        this.phase = PHASE_BUSY;
 | 
			
		||||
        this.phase = PHASE_LOADING;
 | 
			
		||||
        this.verificationRequest = null;
 | 
			
		||||
        this.backupInfo = null;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +76,8 @@ export class SetupEncryptionStore extends EventEmitter {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
            this.keyId = null;
 | 
			
		||||
            this.keyInfo = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +87,20 @@ export class SetupEncryptionStore extends EventEmitter {
 | 
			
		|||
            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");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@ export const showToast = (deviceIds: Set<string>) => {
 | 
			
		|||
 | 
			
		||||
    ToastStore.sharedInstance().addOrReplaceToast({
 | 
			
		||||
        key: TOAST_KEY,
 | 
			
		||||
        title: _t("Review where you’re logged in"),
 | 
			
		||||
        title: _t("You have unverified logins"),
 | 
			
		||||
        icon: "verification_warning",
 | 
			
		||||
        props: {
 | 
			
		||||
            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 dis from "../dispatcher/dispatcher";
 | 
			
		||||
import { MatrixClientPeg } from '../MatrixClientPeg';
 | 
			
		||||
import Modal from '../Modal';
 | 
			
		||||
import DeviceListener from '../DeviceListener';
 | 
			
		||||
import NewSessionReviewDialog from '../components/views/dialogs/NewSessionReviewDialog';
 | 
			
		||||
import ToastStore from "../stores/ToastStore";
 | 
			
		||||
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) {
 | 
			
		||||
    return "unverified_session_" + deviceId;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const showToast = (deviceId: string) => {
 | 
			
		||||
export const showToast = async (deviceId: string) => {
 | 
			
		||||
    const cli = MatrixClientPeg.get();
 | 
			
		||||
 | 
			
		||||
    const onAccept = () => {
 | 
			
		||||
        Modal.createTrackedDialog('New Session Review', 'Starting dialog', NewSessionReviewDialog, {
 | 
			
		||||
            userId: cli.getUserId(),
 | 
			
		||||
            device: cli.getStoredDevice(cli.getUserId(), deviceId),
 | 
			
		||||
            onFinished: (r) => {
 | 
			
		||||
                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);
 | 
			
		||||
        DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
 | 
			
		||||
        dis.dispatch({
 | 
			
		||||
            action: Action.ViewUserSettings,
 | 
			
		||||
            initialTabId: USER_SECURITY_TAB,
 | 
			
		||||
        });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const onReject = () => {
 | 
			
		||||
        DeviceListener.sharedInstance().dismissUnverifiedSessions([deviceId]);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const device = cli.getStoredDevice(cli.getUserId(), deviceId);
 | 
			
		||||
    const device = await cli.getDevice(deviceId);
 | 
			
		||||
 | 
			
		||||
    ToastStore.sharedInstance().addOrReplaceToast({
 | 
			
		||||
        key: toastKey(deviceId),
 | 
			
		||||
| 
						 | 
				
			
			@ -54,8 +50,13 @@ export const showToast = (deviceId: string) => {
 | 
			
		|||
        icon: "verification_warning",
 | 
			
		||||
        props: {
 | 
			
		||||
            description: _t(
 | 
			
		||||
                "Verify the new login accessing your account: %(name)s", { name: device.getDisplayName()}),
 | 
			
		||||
            acceptLabel: _t("Verify"),
 | 
			
		||||
                "A new login is accessing your account: %(name)s (%(deviceID)s) at %(ip)s", {
 | 
			
		||||
                    name: device.display_name,
 | 
			
		||||
                    deviceID: deviceId,
 | 
			
		||||
                    ip: device.last_seen_ip,
 | 
			
		||||
                },
 | 
			
		||||
            ),
 | 
			
		||||
            acceptLabel: _t("Check your devices"),
 | 
			
		||||
            onAccept,
 | 
			
		||||
            rejectLabel: _t("Later"),
 | 
			
		||||
            onReject,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue