Implement basic soft logout handling
Fixes https://github.com/vector-im/riot-web/issues/10235 CSS and copy are left as an exercise for a later iteration. Login page handling is left for https://github.com/vector-im/riot-web/issues/10236 This implementation reuses as much of the Lifecycle flow as it can without causing problems. Most importantly, it requires https://github.com/matrix-org/matrix-js-sdk/pull/975 to be able to detect a soft logout and react to it. When it comes time to starting/stopping the Lifecycle, additional parameters are provided so that the auxiliary services can (re)start themselves without the client starting to sync.
This commit is contained in:
parent
668d24111c
commit
42e6287bdb
12 changed files with 286 additions and 8 deletions
|
@ -90,6 +90,10 @@ const VIEWS = {
|
|||
|
||||
// we are logged in with an active matrix client.
|
||||
LOGGED_IN: 7,
|
||||
|
||||
// We are logged out (invalid token) but have our local state again. The user
|
||||
// should log back in to rehydrate the client.
|
||||
SOFT_LOGOUT: 8,
|
||||
};
|
||||
|
||||
// Actions that are redirected through the onboarding process prior to being
|
||||
|
@ -432,6 +436,7 @@ export default React.createClass({
|
|||
|
||||
switch (payload.action) {
|
||||
case 'logout':
|
||||
console.log(payload);
|
||||
Lifecycle.logout();
|
||||
break;
|
||||
case 'require_registration':
|
||||
|
@ -615,7 +620,12 @@ export default React.createClass({
|
|||
});
|
||||
break;
|
||||
case 'on_logged_in':
|
||||
this._onLoggedIn();
|
||||
if (!Lifecycle.isSoftLogout()) {
|
||||
this._onLoggedIn();
|
||||
}
|
||||
break;
|
||||
case 'on_client_not_viable':
|
||||
this._onSoftLogout();
|
||||
break;
|
||||
case 'on_logged_out':
|
||||
this._onLoggedOut();
|
||||
|
@ -1258,6 +1268,22 @@ export default React.createClass({
|
|||
this._setPageSubtitle();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the session is softly logged out
|
||||
*/
|
||||
_onSoftLogout: function() {
|
||||
this.notifyNewScreen('soft_logout');
|
||||
this.setStateForNewView({
|
||||
view: VIEWS.SOFT_LOGOUT,
|
||||
ready: false,
|
||||
collapseLhs: false,
|
||||
collapsedRhs: false,
|
||||
currentRoomId: null,
|
||||
page_type: PageTypes.RoomDirectory,
|
||||
});
|
||||
this._setPageSubtitle();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called just before the matrix client is started
|
||||
* (useful for setting listeners)
|
||||
|
@ -1337,8 +1363,16 @@ export default React.createClass({
|
|||
call: call,
|
||||
}, true);
|
||||
});
|
||||
cli.on('Session.logged_out', function(call) {
|
||||
cli.on('Session.logged_out', function(errObj) {
|
||||
if (Lifecycle.isLoggingOut()) return;
|
||||
|
||||
if (errObj.httpStatus === 401 && errObj.data && errObj.data['soft_logout']) {
|
||||
console.warn("Soft logout issued by server - avoiding data deletion");
|
||||
Lifecycle.softLogout();
|
||||
dis.dispatch({actions: 'soft_logout'});
|
||||
return;
|
||||
}
|
||||
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Signed out', '', ErrorDialog, {
|
||||
title: _t('Signed Out'),
|
||||
|
@ -1908,6 +1942,13 @@ export default React.createClass({
|
|||
);
|
||||
}
|
||||
|
||||
if (this.state.view === VIEWS.SOFT_LOGOUT) {
|
||||
const SoftLogout = sdk.getComponent('structures.auth.SoftLogout');
|
||||
return (
|
||||
<SoftLogout />
|
||||
);
|
||||
}
|
||||
|
||||
console.error(`Unknown view ${this.state.view}`);
|
||||
},
|
||||
});
|
||||
|
|
123
src/components/structures/auth/SoftLogout.js
Normal file
123
src/components/structures/auth/SoftLogout.js
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {_t} from '../../../languageHandler';
|
||||
import sdk from '../../../index';
|
||||
import dis from '../../../dispatcher';
|
||||
import * as Lifecycle from '../../../Lifecycle';
|
||||
import Modal from '../../../Modal';
|
||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||
import SdkConfig from "../../../SdkConfig";
|
||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||
|
||||
export default class SoftLogout extends React.Component {
|
||||
static propTypes = {
|
||||
// Nothing.
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const defaultServerConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"];
|
||||
|
||||
const hsUrl = MatrixClientPeg.get().getHomeserverUrl();
|
||||
const domainName = hsUrl === defaultServerConfig.hsUrl
|
||||
? defaultServerConfig.hsName
|
||||
: MatrixClientPeg.get().getHomeServerName();
|
||||
|
||||
const userId = MatrixClientPeg.get().getUserId();
|
||||
const user = MatrixClientPeg.get().getUser(userId);
|
||||
|
||||
const displayName = user ? user.displayName : userId.substring(1).split(':')[0];
|
||||
|
||||
this.state = {
|
||||
domainName,
|
||||
userId,
|
||||
displayName,
|
||||
};
|
||||
}
|
||||
|
||||
onClearAll = () => {
|
||||
const ConfirmWipeDeviceDialog = sdk.getComponent('dialogs.ConfirmWipeDeviceDialog');
|
||||
Modal.createTrackedDialog('Clear Data', 'Soft Logout', ConfirmWipeDeviceDialog, {
|
||||
onFinished: (wipeData) => {
|
||||
if (!wipeData) return;
|
||||
|
||||
console.log("Clearing data from soft-logged-out device");
|
||||
Lifecycle.logout();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onLogin = () => {
|
||||
dis.dispatch({action: 'start_login'});
|
||||
};
|
||||
|
||||
render() {
|
||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
||||
const AuthBody = sdk.getComponent("auth.AuthBody");
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
return (
|
||||
<AuthPage>
|
||||
<AuthHeader />
|
||||
<AuthBody>
|
||||
<h2>
|
||||
{_t("You're signed out")}
|
||||
</h2>
|
||||
<div>
|
||||
{_t(
|
||||
"Your homeserver (%(domainName)s) admin has signed you out of your " +
|
||||
"account %(displayName)s (%(userId)s).",
|
||||
{
|
||||
domainName: this.state.domainName,
|
||||
displayName: this.state.displayName,
|
||||
userId: this.state.userId,
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3>{_t("I don't want to sign in")}</h3>
|
||||
<div>
|
||||
{_t(
|
||||
"If this is a shared device, or you don't want to access your account " +
|
||||
"again from it, clear all data stored locally on this device.",
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<AccessibleButton onClick={this.onClearAll} kind="primary">
|
||||
{_t("Clear all data")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
|
||||
<h3>{_t("Sign in")}</h3>
|
||||
<div>
|
||||
{_t(
|
||||
"Sign in again to regain access to your account, or a different one.",
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<AccessibleButton onClick={this.onLogin} kind="primary">
|
||||
{_t("Sign in")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
</AuthBody>
|
||||
</AuthPage>
|
||||
);
|
||||
}
|
||||
}
|
60
src/components/views/dialogs/ConfirmWipeDeviceDialog.js
Normal file
60
src/components/views/dialogs/ConfirmWipeDeviceDialog.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
|
||||
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 {_t} from "../../../languageHandler";
|
||||
import sdk from "../../../index";
|
||||
|
||||
export default class ConfirmWipeDeviceDialog extends React.Component {
|
||||
static propTypes = {
|
||||
onFinished: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
_onConfirm = () => {
|
||||
this.props.onFinished(true);
|
||||
};
|
||||
|
||||
_onDecline = () => {
|
||||
this.props.onFinished(false);
|
||||
};
|
||||
|
||||
render() {
|
||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||
const DialogButtons = sdk.getComponent('views.elements.DialogButtons');
|
||||
|
||||
return (
|
||||
<BaseDialog className='mx_ConfirmWipeDeviceDialog' hasCancel={true}
|
||||
onFinished={this.props.onFinished}
|
||||
title={_t("Clear all data on this device?")}>
|
||||
<div className='mx_ConfirmWipeDeviceDialog_content'>
|
||||
<p>
|
||||
{_t(
|
||||
"Deleting all data from this device is permanent. Encrypted messages will be lost " +
|
||||
"unless their keys have been backed up.",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<DialogButtons
|
||||
primaryButton={_t("Delete everything")}
|
||||
onPrimaryButtonClick={this._onConfirm}
|
||||
cancelButton={_t("Cancel")}
|
||||
onCancel={this._onDecline}
|
||||
/>
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue