1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2025-07-28 15:22:05 +03:00

Merge pull request #1110 from matrix-org/rav/fix_token_redirect

Avoid transitioning to loggedIn state during token login
This commit is contained in:
Richard van der Hoff
2017-06-19 08:52:22 +01:00
committed by GitHub
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() {
@ -853,7 +861,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});
}, },