Support SSO for rehydrating a soft-logged-out session.

Fixes https://github.com/vector-im/riot-web/issues/10238
This commit is contained in:
Travis Ralston 2019-07-08 12:43:16 -06:00
parent 6cb148a88a
commit 1eb60ef1c4
2 changed files with 120 additions and 25 deletions

View file

@ -283,6 +283,8 @@ export default React.createClass({
} }
// the first thing to do is to try the token params in the query-string // the first thing to do is to try the token params in the query-string
// if the session isn't soft logged out (ie: is a clean session being logged in)
if (!Lifecycle.isSoftLogout()) {
Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => { Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => {
if (loggedIn) { if (loggedIn) {
this.props.onTokenLoginCompleted(); this.props.onTokenLoginCompleted();
@ -306,6 +308,7 @@ export default React.createClass({
return this._loadSession(); return this._loadSession();
}); });
}
if (SettingsStore.getValue("showCookieBar")) { if (SettingsStore.getValue("showCookieBar")) {
this.setState({ this.setState({
@ -1250,10 +1253,7 @@ export default React.createClass({
this._screenAfterLogin = null; this._screenAfterLogin = null;
} else if (localStorage && localStorage.getItem('mx_last_room_id')) { } else if (localStorage && localStorage.getItem('mx_last_room_id')) {
// Before defaulting to directory, show the last viewed room // Before defaulting to directory, show the last viewed room
dis.dispatch({ this._viewLastRoom();
action: 'view_room',
room_id: localStorage.getItem('mx_last_room_id'),
});
} else { } else {
if (MatrixClientPeg.get().isGuest()) { if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'view_welcome_page'}); dis.dispatch({action: 'view_welcome_page'});
@ -1267,6 +1267,13 @@ export default React.createClass({
} }
}, },
_viewLastRoom: function() {
dis.dispatch({
action: 'view_room',
room_id: localStorage.getItem('mx_last_room_id'),
});
},
/** /**
* Called when the session is logged out * Called when the session is logged out
*/ */
@ -1565,6 +1572,17 @@ export default React.createClass({
action: 'start_password_recovery', action: 'start_password_recovery',
params: params, params: params,
}); });
} else if (screen === 'soft_logout') {
if (MatrixClientPeg.get() && MatrixClientPeg.get().getUserId()) {
// Logged in - visit a room
this._viewLastRoom();
} else {
// Ultimately triggers soft_logout if needed
dis.dispatch({
action: 'start_login',
params: params,
});
}
} else if (screen == 'new') { } else if (screen == 'new') {
dis.dispatch({ dis.dispatch({
action: 'view_create_room', action: 'view_create_room',
@ -1957,7 +1975,10 @@ export default React.createClass({
if (this.state.view === VIEWS.SOFT_LOGOUT) { if (this.state.view === VIEWS.SOFT_LOGOUT) {
const SoftLogout = sdk.getComponent('structures.auth.SoftLogout'); const SoftLogout = sdk.getComponent('structures.auth.SoftLogout');
return ( return (
<SoftLogout /> <SoftLogout
realQueryParams={this.props.realQueryParams}
onTokenLoginCompleted={this.props.onTokenLoginCompleted}
/>
); );
} }

View file

@ -15,6 +15,7 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import {_t} from '../../../languageHandler'; import {_t} from '../../../languageHandler';
import sdk from '../../../index'; import sdk from '../../../index';
import dis from '../../../dispatcher'; import dis from '../../../dispatcher';
@ -24,6 +25,7 @@ import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import SdkConfig from "../../../SdkConfig"; import SdkConfig from "../../../SdkConfig";
import MatrixClientPeg from "../../../MatrixClientPeg"; import MatrixClientPeg from "../../../MatrixClientPeg";
import {sendLoginRequest} from "../../../Login"; import {sendLoginRequest} from "../../../Login";
import url from 'url';
const LOGIN_VIEW = { const LOGIN_VIEW = {
LOADING: 1, LOADING: 1,
@ -41,7 +43,11 @@ const FLOWS_TO_VIEWS = {
export default class SoftLogout extends React.Component { export default class SoftLogout extends React.Component {
static propTypes = { static propTypes = {
// Nothing. // Query parameters from MatrixChat
realQueryParams: PropTypes.object, // {homeserver, identityServer, loginToken}
// Called when the SSO login completes
onTokenLoginCompleted: PropTypes.func,
}; };
constructor() { constructor() {
@ -67,6 +73,7 @@ export default class SoftLogout extends React.Component {
displayName, displayName,
loginView: LOGIN_VIEW.LOADING, loginView: LOGIN_VIEW.LOADING,
keyBackupNeeded: true, // assume we do while we figure it out (see componentWillMount) keyBackupNeeded: true, // assume we do while we figure it out (see componentWillMount)
ssoUrl: null,
busy: false, busy: false,
password: "", password: "",
@ -75,6 +82,12 @@ export default class SoftLogout extends React.Component {
} }
componentDidMount(): void { componentDidMount(): void {
// We've ended up here when we don't need to - navigate to login
if (!Lifecycle.isSoftLogout()) {
dis.dispatch({action: "on_logged_in"});
return;
}
this._initLogin(); this._initLogin();
MatrixClientPeg.get().flagAllGroupSessionsForBackup().then(remaining => { MatrixClientPeg.get().flagAllGroupSessionsForBackup().then(remaining => {
@ -95,6 +108,15 @@ export default class SoftLogout extends React.Component {
}; };
async _initLogin() { async _initLogin() {
const requiredQueryParams = ['homeserver', 'loginToken'];
const hasAllParams = requiredQueryParams
.filter(p => Object.keys(this.props.realQueryParams).includes(p)).length === requiredQueryParams.length;
if (this.props.realQueryParams && hasAllParams) {
this.setState({loginView: LOGIN_VIEW.LOADING});
this.trySsoLogin();
return;
}
// Note: we don't use the existing Login class because it is heavily flow-based. We don't // 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. // care about login flows here, unless it is the single flow we support.
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
@ -102,6 +124,18 @@ export default class SoftLogout extends React.Component {
const chosenView = loginViews.filter(f => !!f)[0] || LOGIN_VIEW.UNSUPPORTED; const chosenView = loginViews.filter(f => !!f)[0] || LOGIN_VIEW.UNSUPPORTED;
this.setState({loginView: chosenView}); this.setState({loginView: chosenView});
if (chosenView === LOGIN_VIEW.CAS || chosenView === LOGIN_VIEW.SSO) {
const client = MatrixClientPeg.get();
const appUrl = url.parse(window.location.href, true);
appUrl.hash = ""; // Clear #/soft_logout off the URL
appUrl.query["homeserver"] = client.getHomeserverUrl();
appUrl.query["identityServer"] = client.getIdentityServerUrl();
const ssoUrl = client.getSsoLoginUrl(url.format(appUrl), chosenView === LOGIN_VIEW.CAS ? "cas" : "sso");
this.setState({ssoUrl});
}
} }
onPasswordChange = (ev) => { onPasswordChange = (ev) => {
@ -152,6 +186,42 @@ export default class SoftLogout extends React.Component {
}); });
}; };
async trySsoLogin() {
this.setState({busy: true});
const hsUrl = this.props.realQueryParams['homeserver'];
const isUrl = this.props.realQueryParams['identityServer'] || MatrixClientPeg.get().getIdentityServerUrl();
const loginType = "m.login.token";
const loginParams = {
token: this.props.realQueryParams['loginToken'],
device_id: MatrixClientPeg.get().getDeviceId(),
};
let credentials = null;
try {
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
} catch (e) {
console.error(e);
this.setState({busy: false, loginView: LOGIN_VIEW.UNSUPPORTED});
return;
}
Lifecycle.hydrateSession(credentials).then(() => {
if (this.props.onTokenLoginCompleted) this.props.onTokenLoginCompleted();
}).catch((e) => {
console.error(e);
this.setState({busy: false, loginView: LOGIN_VIEW.UNSUPPORTED});
});
}
onSsoLogin = async (ev) => {
ev.preventDefault();
ev.stopPropagation();
this.setState({busy: true});
window.location.href = this.state.ssoUrl;
};
_renderSignInSection() { _renderSignInSection() {
if (this.state.loginView === LOGIN_VIEW.LOADING) { if (this.state.loginView === LOGIN_VIEW.LOADING) {
const Spinner = sdk.getComponent("elements.Spinner"); const Spinner = sdk.getComponent("elements.Spinner");
@ -202,8 +272,12 @@ export default class SoftLogout extends React.Component {
} }
if (this.state.loginView === LOGIN_VIEW.SSO || this.state.loginView === LOGIN_VIEW.CAS) { if (this.state.loginView === LOGIN_VIEW.SSO || this.state.loginView === LOGIN_VIEW.CAS) {
// TODO: TravisR - https://github.com/vector-im/riot-web/issues/10238 const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return <p>PLACEHOLDER</p>; return (
<AccessibleButton kind='primary' onClick={this.onSsoLogin}>
{_t('Sign in with single sign-on')}
</AccessibleButton>
);
} }
// Default: assume unsupported // Default: assume unsupported