mirror of https://github.com/vector-im/riot-web
Merge branch 'travis/soft-logout-rehydrate' into t1
commit
ca6563dce4
|
@ -79,3 +79,22 @@ limitations under the License.
|
||||||
color: $button-danger-disabled-fg-color;
|
color: $button-danger-disabled-fg-color;
|
||||||
background-color: $button-danger-disabled-bg-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;
|
||||||
|
}
|
||||||
|
|
|
@ -143,6 +143,8 @@ $button-danger-fg-color: #ffffff;
|
||||||
$button-danger-bg-color: $notice-primary-color;
|
$button-danger-bg-color: $notice-primary-color;
|
||||||
$button-danger-disabled-fg-color: #ffffff;
|
$button-danger-disabled-fg-color: #ffffff;
|
||||||
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
$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;
|
$room-warning-bg-color: $header-panel-bg-color;
|
||||||
|
|
||||||
|
|
|
@ -244,6 +244,8 @@ $button-danger-fg-color: #ffffff;
|
||||||
$button-danger-bg-color: $notice-primary-color;
|
$button-danger-bg-color: $notice-primary-color;
|
||||||
$button-danger-disabled-fg-color: #ffffff;
|
$button-danger-disabled-fg-color: #ffffff;
|
||||||
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
|
||||||
|
$button-link-fg-color: $accent-color;
|
||||||
|
$button-link-bg-color: transparent;
|
||||||
|
|
||||||
// Toggle switch
|
// Toggle switch
|
||||||
$togglesw-off-color: #c1c9d6;
|
$togglesw-off-color: #c1c9d6;
|
||||||
|
|
|
@ -340,6 +340,25 @@ export function setLoggedIn(credentials) {
|
||||||
return _doSetLoggedIn(credentials, true);
|
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
|
* fires on_logging_in, optionally clears localstorage, persists new credentials
|
||||||
* to localstorage, starts the new client.
|
* to localstorage, starts the new client.
|
||||||
|
@ -541,6 +560,7 @@ async function startMatrixClient(startSyncing=true) {
|
||||||
await MatrixClientPeg.start();
|
await MatrixClientPeg.start();
|
||||||
} else {
|
} else {
|
||||||
console.warn("Caller requested only auxiliary services be started");
|
console.warn("Caller requested only auxiliary services be started");
|
||||||
|
await MatrixClientPeg.assign();
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispatch that we finished starting up to wire up any other bits
|
// dispatch that we finished starting up to wire up any other bits
|
||||||
|
|
|
@ -120,7 +120,7 @@ class MatrixClientPeg {
|
||||||
this._createClient(creds);
|
this._createClient(creds);
|
||||||
}
|
}
|
||||||
|
|
||||||
async start() {
|
async assign() {
|
||||||
for (const dbType of ['indexeddb', 'memory']) {
|
for (const dbType of ['indexeddb', 'memory']) {
|
||||||
try {
|
try {
|
||||||
const promise = this.matrixClient.store.startup();
|
const promise = this.matrixClient.store.startup();
|
||||||
|
@ -131,7 +131,7 @@ class MatrixClientPeg {
|
||||||
if (dbType === 'indexeddb') {
|
if (dbType === 'indexeddb') {
|
||||||
console.error('Error starting matrixclient store - falling back to memory store', err);
|
console.error('Error starting matrixclient store - falling back to memory store', err);
|
||||||
this.matrixClient.store = new Matrix.MemoryStore({
|
this.matrixClient.store = new Matrix.MemoryStore({
|
||||||
localStorage: global.localStorage,
|
localStorage: global.localStorage,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to start memory store!', err);
|
console.error('Failed to start memory store!', err);
|
||||||
|
@ -172,6 +172,12 @@ class MatrixClientPeg {
|
||||||
MatrixActionCreators.start(this.matrixClient);
|
MatrixActionCreators.start(this.matrixClient);
|
||||||
MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient;
|
MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient;
|
||||||
|
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
const opts = await this.assign();
|
||||||
|
|
||||||
console.log(`MatrixClientPeg: really starting MatrixClient`);
|
console.log(`MatrixClientPeg: really starting MatrixClient`);
|
||||||
await this.get().startClient(opts);
|
await this.get().startClient(opts);
|
||||||
console.log(`MatrixClientPeg: MatrixClient started`);
|
console.log(`MatrixClientPeg: MatrixClient started`);
|
||||||
|
|
|
@ -450,6 +450,10 @@ export default React.createClass({
|
||||||
startAnyRegistrationFlow(payload);
|
startAnyRegistrationFlow(payload);
|
||||||
break;
|
break;
|
||||||
case 'start_registration':
|
case 'start_registration':
|
||||||
|
if (Lifecycle.isSoftLogout()) {
|
||||||
|
this._onSoftLogout();
|
||||||
|
break;
|
||||||
|
}
|
||||||
// This starts the full registration flow
|
// This starts the full registration flow
|
||||||
if (payload.screenAfterLogin) {
|
if (payload.screenAfterLogin) {
|
||||||
this._screenAfterLogin = payload.screenAfterLogin;
|
this._screenAfterLogin = payload.screenAfterLogin;
|
||||||
|
@ -457,6 +461,10 @@ export default React.createClass({
|
||||||
this._startRegistration(payload.params || {});
|
this._startRegistration(payload.params || {});
|
||||||
break;
|
break;
|
||||||
case 'start_login':
|
case 'start_login':
|
||||||
|
if (Lifecycle.isSoftLogout()) {
|
||||||
|
this._onSoftLogout();
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (payload.screenAfterLogin) {
|
if (payload.screenAfterLogin) {
|
||||||
this._screenAfterLogin = payload.screenAfterLogin;
|
this._screenAfterLogin = payload.screenAfterLogin;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,21 @@ import Modal from '../../../Modal';
|
||||||
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
|
||||||
import SdkConfig from "../../../SdkConfig";
|
import SdkConfig from "../../../SdkConfig";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
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 {
|
export default class SoftLogout extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -48,7 +63,14 @@ export default class SoftLogout extends React.Component {
|
||||||
domainName,
|
domainName,
|
||||||
userId,
|
userId,
|
||||||
displayName,
|
displayName,
|
||||||
|
loginView: LOGIN_VIEW.LOADING,
|
||||||
|
|
||||||
|
busy: false,
|
||||||
|
password: "",
|
||||||
|
errorText: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._initLogin();
|
||||||
}
|
}
|
||||||
|
|
||||||
onClearAll = () => {
|
onClearAll = () => {
|
||||||
|
@ -63,10 +85,122 @@ export default class SoftLogout extends React.Component {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onLogin = () => {
|
async _initLogin() {
|
||||||
dis.dispatch({action: 'start_login'});
|
// 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>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={this.onPasswordLogin}>
|
||||||
|
<p>{_t("Enter your password to sign in and regain access to your account.")}</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() {
|
render() {
|
||||||
const AuthPage = sdk.getComponent("auth.AuthPage");
|
const AuthPage = sdk.getComponent("auth.AuthPage");
|
||||||
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
const AuthHeader = sdk.getComponent("auth.AuthHeader");
|
||||||
|
@ -107,14 +241,7 @@ export default class SoftLogout extends React.Component {
|
||||||
|
|
||||||
<h3>{_t("Sign in")}</h3>
|
<h3>{_t("Sign in")}</h3>
|
||||||
<div>
|
<div>
|
||||||
{_t(
|
{this._renderSignInSection()}
|
||||||
"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>
|
</div>
|
||||||
</AuthBody>
|
</AuthBody>
|
||||||
</AuthPage>
|
</AuthPage>
|
||||||
|
|
|
@ -1586,12 +1586,15 @@
|
||||||
"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.",
|
"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",
|
"Registration Successful": "Registration Successful",
|
||||||
"Create your account": "Create your account",
|
"Create your account": "Create your account",
|
||||||
|
"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.",
|
||||||
|
"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",
|
"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).",
|
"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",
|
"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.",
|
"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",
|
"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",
|
"Commands": "Commands",
|
||||||
"Results from DuckDuckGo": "Results from DuckDuckGo",
|
"Results from DuckDuckGo": "Results from DuckDuckGo",
|
||||||
"Emoji": "Emoji",
|
"Emoji": "Emoji",
|
||||||
|
|
Loading…
Reference in New Issue