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.pull/21833/head
parent
6a2c2d64fa
commit
eb1fc9ae2d
102
src/Lifecycle.js
102
src/Lifecycle.js
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue