Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Change soft logout rehydrate text if there's pending key backups #3187

Merged
merged 8 commits into from
Jul 9, 2019
19 changes: 19 additions & 0 deletions res/css/views/elements/_AccessibleButton.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,22 @@ limitations under the License.
color: $button-danger-disabled-fg-color;
background-color: $button-danger-disabled-bg-color;
}

.mx_AccessibleButton_kind_link {
color: $button-link-fg-color;
background-color: $button-link-bg-color;
}

.mx_AccessibleButton_kind_link.mx_AccessibleButton_disabled {
opacity: 0.4;
}

.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_link_sm {
padding: 5px 12px;
color: $button-link-fg-color;
background-color: $button-link-bg-color;
}

.mx_AccessibleButton_kind_link_sm.mx_AccessibleButton_disabled {
opacity: 0.4;
}
2 changes: 2 additions & 0 deletions res/themes/dark/css/_dark.scss
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ $button-danger-fg-color: #ffffff;
$button-danger-bg-color: $notice-primary-color;
$button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
$button-link-fg-color: $accent-color;
$button-link-bg-color: transparent;

$room-warning-bg-color: $header-panel-bg-color;

Expand Down
2 changes: 2 additions & 0 deletions res/themes/light/css/_light.scss
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ $button-danger-fg-color: #ffffff;
$button-danger-bg-color: $notice-primary-color;
$button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
$button-link-fg-color: $accent-color;
$button-link-bg-color: transparent;

// Toggle switch
$togglesw-off-color: #c1c9d6;
Expand Down
20 changes: 20 additions & 0 deletions src/Lifecycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,25 @@ export function setLoggedIn(credentials) {
return _doSetLoggedIn(credentials, true);
}

/**
* Hydrates an existing session by using the credentials provided. This will
* not clear any local storage, unlike setLoggedIn().
*
* Stops the existing Matrix client (without clearing its data) and starts a
* new one in its place. This additionally starts all other react-sdk services
* which use the new Matrix client.
*
* @param {MatrixClientCreds} credentials The credentials to use
*
* @returns {Promise} promise which resolves to the new MatrixClient once it has been started
*/
export function hydrateSession(credentials) {
stopMatrixClient();
localStorage.removeItem("mx_soft_logout");
_isLoggingOut = false;
return _doSetLoggedIn(credentials, false);
}

/**
* fires on_logging_in, optionally clears localstorage, persists new credentials
* to localstorage, starts the new client.
Expand Down Expand Up @@ -541,6 +560,7 @@ async function startMatrixClient(startSyncing=true) {
await MatrixClientPeg.start();
} else {
console.warn("Caller requested only auxiliary services be started");
await MatrixClientPeg.assign();
}

// dispatch that we finished starting up to wire up any other bits
Expand Down
10 changes: 8 additions & 2 deletions src/MatrixClientPeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class MatrixClientPeg {
this._createClient(creds);
}

async start() {
async assign() {
for (const dbType of ['indexeddb', 'memory']) {
try {
const promise = this.matrixClient.store.startup();
Expand All @@ -131,7 +131,7 @@ class MatrixClientPeg {
if (dbType === 'indexeddb') {
console.error('Error starting matrixclient store - falling back to memory store', err);
this.matrixClient.store = new Matrix.MemoryStore({
localStorage: global.localStorage,
localStorage: global.localStorage,
});
} else {
console.error('Failed to start memory store!', err);
Expand Down Expand Up @@ -172,6 +172,12 @@ class MatrixClientPeg {
MatrixActionCreators.start(this.matrixClient);
MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient;

return opts;
}

async start() {
const opts = await this.assign();

console.log(`MatrixClientPeg: really starting MatrixClient`);
await this.get().startClient(opts);
console.log(`MatrixClientPeg: MatrixClient started`);
Expand Down
8 changes: 8 additions & 0 deletions src/components/structures/MatrixChat.js
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,21 @@ export default React.createClass({
startAnyRegistrationFlow(payload);
break;
case 'start_registration':
if (Lifecycle.isSoftLogout()) {
this._onSoftLogout();
break;
}
// This starts the full registration flow
if (payload.screenAfterLogin) {
this._screenAfterLogin = payload.screenAfterLogin;
}
this._startRegistration(payload.params || {});
break;
case 'start_login':
if (Lifecycle.isSoftLogout()) {
this._onSoftLogout();
break;
}
if (payload.screenAfterLogin) {
this._screenAfterLogin = payload.screenAfterLogin;
}
Expand Down
161 changes: 151 additions & 10 deletions src/components/structures/auth/SoftLogout.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ import Modal from '../../../Modal';
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import SdkConfig from "../../../SdkConfig";
import MatrixClientPeg from "../../../MatrixClientPeg";
import {sendLoginRequest} from "../../../Login";

const LOGIN_VIEW = {
LOADING: 1,
PASSWORD: 2,
CAS: 3, // SSO, but old
SSO: 4,
UNSUPPORTED: 5,
};

const FLOWS_TO_VIEWS = {
"m.login.password": LOGIN_VIEW.PASSWORD,
"m.login.cas": LOGIN_VIEW.CAS,
"m.login.sso": LOGIN_VIEW.SSO,
};

export default class SoftLogout extends React.Component {
static propTypes = {
Expand All @@ -48,7 +63,21 @@ export default class SoftLogout extends React.Component {
domainName,
userId,
displayName,
loginView: LOGIN_VIEW.LOADING,
keyBackupNeeded: true, // assume we do while we figure it out (see componentWillMount)

busy: false,
password: "",
errorText: "",
};

this._initLogin();
}

componentWillMount(): void {
MatrixClientPeg.get().flagAllGroupSessionsForBackup().then(remaining => {
bwindels marked this conversation as resolved.
Show resolved Hide resolved
this.setState({keyBackupNeeded: remaining > 0});
});
}

onClearAll = () => {
Expand All @@ -63,10 +92,129 @@ export default class SoftLogout extends React.Component {
});
};

onLogin = () => {
dis.dispatch({action: 'start_login'});
async _initLogin() {
// Note: we don't use the existing Login class because it is heavily flow-based. We don't
// care about login flows here, unless it is the single flow we support.
const client = MatrixClientPeg.get();
const loginViews = (await client.loginFlows()).flows.map(f => FLOWS_TO_VIEWS[f.type]);

const chosenView = loginViews.filter(f => !!f)[0] || LOGIN_VIEW.UNSUPPORTED;
this.setState({loginView: chosenView});
}

onPasswordChange = (ev) => {
this.setState({password: ev.target.value});
};

onForgotPassword = () => {
dis.dispatch({action: 'start_password_recovery'});
};

onPasswordLogin = async (ev) => {
ev.preventDefault();
ev.stopPropagation();

this.setState({busy: true});

const hsUrl = MatrixClientPeg.get().getHomeserverUrl();
const isUrl = MatrixClientPeg.get().getIdentityServerUrl();
const loginType = "m.login.password";
const loginParams = {
identifier: {
type: "m.id.user",
user: MatrixClientPeg.get().getUserId(),
},
password: this.state.password,
device_id: MatrixClientPeg.get().getDeviceId(),
};

let credentials = null;
try {
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
} catch (e) {
let errorText = _t("Failed to re-authenticate due to a homeserver problem");
if (e.errcode === "M_FORBIDDEN" && (e.httpStatus === 401 || e.httpStatus === 403)) {
errorText = _t("Incorrect password");
}

this.setState({
busy: false,
errorText: errorText,
});
return;
}

Lifecycle.hydrateSession(credentials).catch((e) => {
console.error(e);
this.setState({busy: false, errorText: _t("Failed to re-authenticate")});
});
};

_renderSignInSection() {
if (this.state.loginView === LOGIN_VIEW.LOADING) {
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />;
}

if (this.state.loginView === LOGIN_VIEW.PASSWORD) {
const Field = sdk.getComponent("elements.Field");
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');

let error = null;
if (this.state.errorText) {
error = <span className='mx_Login_error'>{this.state.errorText}</span>;
}

let introText = _t("Enter your password to sign in and regain access to your account.");
if (this.state.keyBackupNeeded) {
introText = _t(
"Regain access your account and recover encryption keys stored on this device. " +
"Without them, you won’t be able to read all of your secure messages on any device.");
}

return (
<form onSubmit={this.onPasswordLogin}>
<p>{introText}</p>
{error}
<Field
id="softlogout_password"
type="password"
label={_t("Password")}
onChange={this.onPasswordChange}
value={this.state.password}
disabled={this.state.busy}
/>
<AccessibleButton
onClick={this.onPasswordLogin}
kind="primary"
type="submit"
disabled={this.state.busy}
>
{_t("Sign In")}
</AccessibleButton>
<AccessibleButton onClick={this.onForgotPassword} kind="link">
{_t("Forgotten your password?")}
</AccessibleButton>
</form>
);
}

if (this.state.loginView === LOGIN_VIEW.SSO || this.state.loginView === LOGIN_VIEW.CAS) {
// TODO: TravisR - https://github.com/vector-im/riot-web/issues/10238
return <p>PLACEHOLDER</p>;
}

// Default: assume unsupported
return (
<p>
{_t(
"Cannot re-authenticate with your account. Please contact your " +
"homeserver admin for more information.",
)}
</p>
);
}

render() {
const AuthPage = sdk.getComponent("auth.AuthPage");
const AuthHeader = sdk.getComponent("auth.AuthHeader");
Expand Down Expand Up @@ -107,14 +255,7 @@ export default class SoftLogout extends React.Component {

<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>
{this._renderSignInSection()}
</div>
</AuthBody>
</AuthPage>
Expand Down
7 changes: 6 additions & 1 deletion src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1586,12 +1586,17 @@
"You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.",
"Registration Successful": "Registration Successful",
"Create your account": "Create your account",
"Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem",
"Failed to re-authenticate": "Failed to re-authenticate",
"Enter your password to sign in and regain access to your account.": "Enter your password to sign in and regain access to your account.",
"Regain access your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.": "Regain access your account and recover encryption keys stored on this device. Without them, you won’t be able to read all of your secure messages on any device.",
"Forgotten your password?": "Forgotten your password?",
"Cannot re-authenticate with your account. Please contact your homeserver admin for more information.": "Cannot re-authenticate with your account. Please contact your homeserver admin for more information.",
"You're signed out": "You're signed out",
"Your homeserver (%(domainName)s) admin has signed you out of your account %(displayName)s (%(userId)s).": "Your homeserver (%(domainName)s) admin has signed you out of your account %(displayName)s (%(userId)s).",
"I don't want to sign in": "I don't want to sign in",
"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.": "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.",
"Clear all data": "Clear all data",
"Sign in again to regain access to your account, or a different one.": "Sign in again to regain access to your account, or a different one.",
"Commands": "Commands",
"Results from DuckDuckGo": "Results from DuckDuckGo",
"Emoji": "Emoji",
Expand Down