Merge pull request #1110 from matrix-org/rav/fix_token_redirect
Avoid transitioning to loggedIn state during token loginpull/21833/head
						commit
						cb29d92912
					
				
							
								
								
									
										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 | ||||
|  * 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. | ||||
|  * | ||||
|  * 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. | ||||
|  * | ||||
|  * 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 | ||||
|  * turn will raise on_logged_in and will_start_client events. | ||||
|  * | ||||
|  * @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 | ||||
|  *     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. | ||||
|  */ | ||||
| export function loadSession(opts) { | ||||
|     const realQueryParams = opts.realQueryParams || {}; | ||||
|     const fragmentQueryParams = opts.fragmentQueryParams || {}; | ||||
|     let enableGuest = opts.enableGuest || false; | ||||
|     const guestHsUrl = opts.guestHsUrl; | ||||
|  | @ -82,14 +75,6 @@ export function loadSession(opts) { | |||
|         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 && | ||||
|         fragmentQueryParams.guest_user_id && | ||||
|         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
 | ||||
|     const client = Matrix.createClient({ | ||||
|         baseUrl: queryParams.homeserver, | ||||
|  | @ -130,17 +134,21 @@ function _loginWithToken(queryParams, defaultDeviceDisplayName) { | |||
|         }, | ||||
|     ).then(function(data) { | ||||
|         console.log("Logged in with token"); | ||||
|         return _doSetLoggedIn({ | ||||
|             userId: data.user_id, | ||||
|             deviceId: data.device_id, | ||||
|             accessToken: data.access_token, | ||||
|             homeserverUrl: queryParams.homeserver, | ||||
|             identityServerUrl: queryParams.identityServer, | ||||
|             guest: false, | ||||
|         }, true); | ||||
|     }, (err) => { | ||||
|         return _clearStorage().then(() => { | ||||
|             _persistCredentialsToLocalStorage({ | ||||
|                 userId: data.user_id, | ||||
|                 deviceId: data.device_id, | ||||
|                 accessToken: data.access_token, | ||||
|                 homeserverUrl: queryParams.homeserver, | ||||
|                 identityServerUrl: queryParams.identityServer, | ||||
|                 guest: false, | ||||
|             }); | ||||
|             return true; | ||||
|         }); | ||||
|     }).catch((err) => { | ||||
|         console.error("Failed to log in with login token: " + err + " " + | ||||
|                       err.data); | ||||
|         return false; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
|  | @ -322,23 +330,10 @@ async function _doSetLoggedIn(credentials, clearStorage) { | |||
|     // Resolves by default
 | ||||
|     let teamPromise = Promise.resolve(null); | ||||
| 
 | ||||
|     // persist the session
 | ||||
| 
 | ||||
|     if (localStorage) { | ||||
|         try { | ||||
|             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); | ||||
|             } | ||||
|             _persistCredentialsToLocalStorage(credentials); | ||||
| 
 | ||||
|             // 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.
 | ||||
|  | @ -349,8 +344,6 @@ async function _doSetLoggedIn(credentials, clearStorage) { | |||
|                     cachedPassword: credentials.password, | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             console.log("Session persisted for %s", credentials.userId); | ||||
|         } catch (e) { | ||||
|             console.warn("Error using local storage: can't persist session!", e); | ||||
|         } | ||||
|  | @ -379,6 +372,25 @@ async function _doSetLoggedIn(credentials, clearStorage) { | |||
|     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 | ||||
|  */ | ||||
|  |  | |||
|  | @ -59,8 +59,8 @@ module.exports = React.createClass({ | |||
|         // the initial queryParams extracted from the hash-fragment of the URI
 | ||||
|         startingFragmentQueryParams: React.PropTypes.object, | ||||
| 
 | ||||
|         // called when the session load completes
 | ||||
|         onLoadCompleted: React.PropTypes.func, | ||||
|         // called when we have completed a token login
 | ||||
|         onTokenLoginCompleted: React.PropTypes.func, | ||||
| 
 | ||||
|         // Represents the screen to display as a result of parsing the initial
 | ||||
|         // window.location
 | ||||
|  | @ -143,7 +143,7 @@ module.exports = React.createClass({ | |||
|             realQueryParams: {}, | ||||
|             startingFragmentQueryParams: {}, | ||||
|             config: {}, | ||||
|             onLoadCompleted: () => {}, | ||||
|             onTokenLoginCompleted: () => {}, | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|  | @ -266,39 +266,47 @@ module.exports = React.createClass({ | |||
|         const teamServerConfig = this.props.config.teamServerConfig || {}; | ||||
|         Lifecycle.initRtsClient(teamServerConfig.teamServerURL); | ||||
| 
 | ||||
|         // if the user has followed a login or register link, don't reanimate
 | ||||
|         // the old creds, but rather go straight to the relevant page
 | ||||
|         // the first thing to do is to try the token params in the query-string
 | ||||
|         Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => { | ||||
|             if(loggedIn) { | ||||
|                 this.props.onTokenLoginCompleted(); | ||||
| 
 | ||||
|         const firstScreen = this.state.screenAfterLogin ? | ||||
|             this.state.screenAfterLogin.screen : null; | ||||
|                 // don't do anything else until the page reloads - just stay in
 | ||||
|                 // the 'loading' state.
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|         if (firstScreen === 'login' || | ||||
|                 firstScreen === 'register' || | ||||
|                 firstScreen === 'forgot_password') { | ||||
|             this.props.onLoadCompleted(); | ||||
|             this.setState({loading: false}); | ||||
|             this._showScreenAfterLogin(); | ||||
|             return; | ||||
|         } | ||||
|             // if the user has followed a login or register link, don't reanimate
 | ||||
|             // the old creds, but rather go straight to the relevant page
 | ||||
|             const firstScreen = this.state.screenAfterLogin ? | ||||
|                 this.state.screenAfterLogin.screen : null; | ||||
| 
 | ||||
|         // the extra q() ensures that synchronous exceptions hit the same codepath as
 | ||||
|         // asynchronous ones.
 | ||||
|         q().then(() => { | ||||
|             return Lifecycle.loadSession({ | ||||
|                 realQueryParams: this.props.realQueryParams, | ||||
|                 fragmentQueryParams: this.props.startingFragmentQueryParams, | ||||
|                 enableGuest: this.props.enableGuest, | ||||
|                 guestHsUrl: this.getCurrentHsUrl(), | ||||
|                 guestIsUrl: this.getCurrentIsUrl(), | ||||
|                 defaultDeviceDisplayName: this.props.defaultDeviceDisplayName, | ||||
|             if (firstScreen === 'login' || | ||||
|                     firstScreen === 'register' || | ||||
|                     firstScreen === 'forgot_password') { | ||||
|                 this.setState({loading: false}); | ||||
|                 this._showScreenAfterLogin(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // the extra q() ensures that synchronous exceptions hit the same codepath as
 | ||||
|             // 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) => { | ||||
|             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'}); | ||||
|         }); | ||||
|         }).done(); | ||||
|     }, | ||||
| 
 | ||||
|     componentWillUnmount: function() { | ||||
|  | @ -853,7 +861,6 @@ module.exports = React.createClass({ | |||
|      * Called when the sessionloader has finished | ||||
|      */ | ||||
|     _onLoadCompleted: function() { | ||||
|         this.props.onLoadCompleted(); | ||||
|         this.setState({loading: false}); | ||||
|     }, | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Richard van der Hoff
						Richard van der Hoff