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