Avoid transitioning to loggedIn state during token login

Fixes riot-web#4334

When we do a token login, don't carry on with the normal app startup
(transitioning to the loggedIn state etc) - instead tell the app about the
successful login and wait for it to redirect.

Replace onLoadCompleted with onTokenLoginCompleted so that the app can see what
it's supposed to be doing.
This commit is contained in:
Richard van der Hoff 2017-06-16 14:33:14 +01:00
parent 6a2c2d64fa
commit eb1fc9ae2d
2 changed files with 97 additions and 78 deletions

View File

@ -35,26 +35,20 @@ import { _t } from './languageHandler';
* Called at startup, to attempt to build a logged-in Matrix session. It tries * Called at startup, to attempt to build a logged-in Matrix session. It tries
* a number of things: * a number of things:
* *
* 1. if we have a loginToken in the (real) query params, it uses that to log
* in.
* *
* 2. if we have a guest access token in the fragment query params, it uses * 1. if we have a guest access token in the fragment query params, it uses
* that. * that.
* *
* 3. if an access token is stored in local storage (from a previous session), * 2. if an access token is stored in local storage (from a previous session),
* it uses that. * it uses that.
* *
* 4. it attempts to auto-register as a guest user. * 3. it attempts to auto-register as a guest user.
* *
* If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in * If any of steps 1-4 are successful, it will call {_doSetLoggedIn}, which in
* turn will raise on_logged_in and will_start_client events. * turn will raise on_logged_in and will_start_client events.
* *
* @param {object} opts * @param {object} opts
* *
* @param {object} opts.realQueryParams: string->string map of the
* query-parameters extracted from the real query-string of the starting
* URI.
*
* @param {object} opts.fragmentQueryParams: string->string map of the * @param {object} opts.fragmentQueryParams: string->string map of the
* query-parameters extracted from the #-fragment of the starting URI. * query-parameters extracted from the #-fragment of the starting URI.
* *
@ -70,7 +64,6 @@ import { _t } from './languageHandler';
* @returns {Promise} a promise which resolves when the above process completes. * @returns {Promise} a promise which resolves when the above process completes.
*/ */
export function loadSession(opts) { export function loadSession(opts) {
const realQueryParams = opts.realQueryParams || {};
const fragmentQueryParams = opts.fragmentQueryParams || {}; const fragmentQueryParams = opts.fragmentQueryParams || {};
let enableGuest = opts.enableGuest || false; let enableGuest = opts.enableGuest || false;
const guestHsUrl = opts.guestHsUrl; const guestHsUrl = opts.guestHsUrl;
@ -82,14 +75,6 @@ export function loadSession(opts) {
enableGuest = false; enableGuest = false;
} }
if (realQueryParams.loginToken) {
if (!realQueryParams.homeserver) {
console.warn("Cannot log in with token: can't determine HS URL to use");
} else {
return _loginWithToken(realQueryParams, defaultDeviceDisplayName);
}
}
if (enableGuest && if (enableGuest &&
fragmentQueryParams.guest_user_id && fragmentQueryParams.guest_user_id &&
fragmentQueryParams.guest_access_token fragmentQueryParams.guest_access_token
@ -117,7 +102,26 @@ export function loadSession(opts) {
}); });
} }
function _loginWithToken(queryParams, defaultDeviceDisplayName) { /**
* @param {Object} queryParams string->string map of the
* query-parameters extracted from the real query-string of the starting
* URI.
*
* @param {String} defaultDeviceDisplayName
*
* @returns {Promise} promise which resolves to true if we completed the token
* login, else false
*/
export function attemptTokenLogin(queryParams, defaultDeviceDisplayName) {
if (!queryParams.loginToken) {
return q(false);
}
if (!queryParams.homeserver) {
console.warn("Cannot log in with token: can't determine HS URL to use");
return q(false);
}
// create a temporary MatrixClient to do the login // create a temporary MatrixClient to do the login
const client = Matrix.createClient({ const client = Matrix.createClient({
baseUrl: queryParams.homeserver, baseUrl: queryParams.homeserver,
@ -130,17 +134,21 @@ function _loginWithToken(queryParams, defaultDeviceDisplayName) {
}, },
).then(function(data) { ).then(function(data) {
console.log("Logged in with token"); console.log("Logged in with token");
return _doSetLoggedIn({ return _clearStorage().then(() => {
userId: data.user_id, _persistCredentialsToLocalStorage({
deviceId: data.device_id, userId: data.user_id,
accessToken: data.access_token, deviceId: data.device_id,
homeserverUrl: queryParams.homeserver, accessToken: data.access_token,
identityServerUrl: queryParams.identityServer, homeserverUrl: queryParams.homeserver,
guest: false, identityServerUrl: queryParams.identityServer,
}, true); guest: false,
}, (err) => { });
return true;
});
}).catch((err) => {
console.error("Failed to log in with login token: " + err + " " + console.error("Failed to log in with login token: " + err + " " +
err.data); err.data);
return false;
}); });
} }
@ -322,23 +330,10 @@ async function _doSetLoggedIn(credentials, clearStorage) {
// Resolves by default // Resolves by default
let teamPromise = Promise.resolve(null); let teamPromise = Promise.resolve(null);
// persist the session
if (localStorage) { if (localStorage) {
try { try {
localStorage.setItem("mx_hs_url", credentials.homeserverUrl); _persistCredentialsToLocalStorage(credentials);
localStorage.setItem("mx_is_url", credentials.identityServerUrl);
localStorage.setItem("mx_user_id", credentials.userId);
localStorage.setItem("mx_access_token", credentials.accessToken);
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
// if we didn't get a deviceId from the login, leave mx_device_id unset,
// rather than setting it to "undefined".
//
// (in this case MatrixClient doesn't bother with the crypto stuff
// - that's fine for us).
if (credentials.deviceId) {
localStorage.setItem("mx_device_id", credentials.deviceId);
}
// The user registered as a PWLU (PassWord-Less User), the generated password // The user registered as a PWLU (PassWord-Less User), the generated password
// is cached here such that the user can change it at a later time. // is cached here such that the user can change it at a later time.
@ -349,8 +344,6 @@ async function _doSetLoggedIn(credentials, clearStorage) {
cachedPassword: credentials.password, cachedPassword: credentials.password,
}); });
} }
console.log("Session persisted for %s", credentials.userId);
} catch (e) { } catch (e) {
console.warn("Error using local storage: can't persist session!", e); console.warn("Error using local storage: can't persist session!", e);
} }
@ -379,6 +372,25 @@ async function _doSetLoggedIn(credentials, clearStorage) {
startMatrixClient(); startMatrixClient();
} }
function _persistCredentialsToLocalStorage(credentials) {
localStorage.setItem("mx_hs_url", credentials.homeserverUrl);
localStorage.setItem("mx_is_url", credentials.identityServerUrl);
localStorage.setItem("mx_user_id", credentials.userId);
localStorage.setItem("mx_access_token", credentials.accessToken);
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
// if we didn't get a deviceId from the login, leave mx_device_id unset,
// rather than setting it to "undefined".
//
// (in this case MatrixClient doesn't bother with the crypto stuff
// - that's fine for us).
if (credentials.deviceId) {
localStorage.setItem("mx_device_id", credentials.deviceId);
}
console.log("Session persisted for %s", credentials.userId);
}
/** /**
* Logs the current session out and transitions to the logged-out state * Logs the current session out and transitions to the logged-out state
*/ */

View File

@ -59,8 +59,8 @@ module.exports = React.createClass({
// the initial queryParams extracted from the hash-fragment of the URI // the initial queryParams extracted from the hash-fragment of the URI
startingFragmentQueryParams: React.PropTypes.object, startingFragmentQueryParams: React.PropTypes.object,
// called when the session load completes // called when we have completed a token login
onLoadCompleted: React.PropTypes.func, onTokenLoginCompleted: React.PropTypes.func,
// Represents the screen to display as a result of parsing the initial // Represents the screen to display as a result of parsing the initial
// window.location // window.location
@ -143,7 +143,7 @@ module.exports = React.createClass({
realQueryParams: {}, realQueryParams: {},
startingFragmentQueryParams: {}, startingFragmentQueryParams: {},
config: {}, config: {},
onLoadCompleted: () => {}, onTokenLoginCompleted: () => {},
}; };
}, },
@ -266,39 +266,47 @@ module.exports = React.createClass({
const teamServerConfig = this.props.config.teamServerConfig || {}; const teamServerConfig = this.props.config.teamServerConfig || {};
Lifecycle.initRtsClient(teamServerConfig.teamServerURL); Lifecycle.initRtsClient(teamServerConfig.teamServerURL);
// if the user has followed a login or register link, don't reanimate // the first thing to do is to try the token params in the query-string
// the old creds, but rather go straight to the relevant page Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => {
if(loggedIn) {
this.props.onTokenLoginCompleted();
const firstScreen = this.state.screenAfterLogin ? // don't do anything else until the page reloads - just stay in
this.state.screenAfterLogin.screen : null; // the 'loading' state.
return;
}
if (firstScreen === 'login' || // if the user has followed a login or register link, don't reanimate
firstScreen === 'register' || // the old creds, but rather go straight to the relevant page
firstScreen === 'forgot_password') { const firstScreen = this.state.screenAfterLogin ?
this.props.onLoadCompleted(); this.state.screenAfterLogin.screen : null;
this.setState({loading: false});
this._showScreenAfterLogin();
return;
}
// the extra q() ensures that synchronous exceptions hit the same codepath as if (firstScreen === 'login' ||
// asynchronous ones. firstScreen === 'register' ||
q().then(() => { firstScreen === 'forgot_password') {
return Lifecycle.loadSession({ this.setState({loading: false});
realQueryParams: this.props.realQueryParams, this._showScreenAfterLogin();
fragmentQueryParams: this.props.startingFragmentQueryParams, return;
enableGuest: this.props.enableGuest, }
guestHsUrl: this.getCurrentHsUrl(),
guestIsUrl: this.getCurrentIsUrl(), // the extra q() ensures that synchronous exceptions hit the same codepath as
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName, // asynchronous ones.
return q().then(() => {
return Lifecycle.loadSession({
fragmentQueryParams: this.props.startingFragmentQueryParams,
enableGuest: this.props.enableGuest,
guestHsUrl: this.getCurrentHsUrl(),
guestIsUrl: this.getCurrentIsUrl(),
defaultDeviceDisplayName: this.props.defaultDeviceDisplayName,
});
}).catch((e) => {
console.error("Unable to load session", e);
}).then(()=>{
// stuff this through the dispatcher so that it happens
// after the on_logged_in action.
dis.dispatch({action: 'load_completed'});
}); });
}).catch((e) => { }).done();
console.error("Unable to load session", e);
}).done(()=>{
// stuff this through the dispatcher so that it happens
// after the on_logged_in action.
dis.dispatch({action: 'load_completed'});
});
}, },
componentWillUnmount: function() { componentWillUnmount: function() {
@ -850,7 +858,6 @@ module.exports = React.createClass({
* Called when the sessionloader has finished * Called when the sessionloader has finished
*/ */
_onLoadCompleted: function() { _onLoadCompleted: function() {
this.props.onLoadCompleted();
this.setState({loading: false}); this.setState({loading: false});
}, },