Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

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

Merged
merged 4 commits into from
Jul 10, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 44 additions & 23 deletions src/components/structures/MatrixChat.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,29 +283,32 @@ export default React.createClass({
}

// 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();

// don't do anything else until the page reloads - just stay in
// the 'loading' state.
return;
}
// 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) => {
if (loggedIn) {
this.props.onTokenLoginCompleted();

// don't do anything else until the page reloads - just stay in
// the 'loading' state.
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._screenAfterLogin ?
this._screenAfterLogin.screen : null;
// 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._screenAfterLogin ?
this._screenAfterLogin.screen : null;

if (firstScreen === 'login' ||
if (firstScreen === 'login' ||
firstScreen === 'register' ||
firstScreen === 'forgot_password') {
this._showScreenAfterLogin();
return;
}
this._showScreenAfterLogin();
return;
}

return this._loadSession();
});
return this._loadSession();
});
}

if (SettingsStore.getValue("showCookieBar")) {
this.setState({
Expand Down Expand Up @@ -1250,10 +1253,7 @@ export default React.createClass({
this._screenAfterLogin = null;
} else if (localStorage && localStorage.getItem('mx_last_room_id')) {
// Before defaulting to directory, show the last viewed room
dis.dispatch({
action: 'view_room',
room_id: localStorage.getItem('mx_last_room_id'),
});
this._viewLastRoom();
} else {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'view_welcome_page'});
Expand All @@ -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
*/
Expand Down Expand Up @@ -1565,6 +1572,17 @@ export default React.createClass({
action: 'start_password_recovery',
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') {
dis.dispatch({
action: 'view_create_room',
Expand Down Expand Up @@ -1957,7 +1975,10 @@ export default React.createClass({
if (this.state.view === VIEWS.SOFT_LOGOUT) {
const SoftLogout = sdk.getComponent('structures.auth.SoftLogout');
return (
<SoftLogout />
<SoftLogout
realQueryParams={this.props.realQueryParams}
onTokenLoginCompleted={this.props.onTokenLoginCompleted}
/>
);
}

Expand Down
80 changes: 77 additions & 3 deletions src/components/structures/auth/SoftLogout.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import {_t} from '../../../languageHandler';
import sdk from '../../../index';
import dis from '../../../dispatcher';
Expand All @@ -24,6 +25,7 @@ import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import SdkConfig from "../../../SdkConfig";
import MatrixClientPeg from "../../../MatrixClientPeg";
import {sendLoginRequest} from "../../../Login";
import url from 'url';

const LOGIN_VIEW = {
LOADING: 1,
Expand All @@ -41,7 +43,11 @@ const FLOWS_TO_VIEWS = {

export default class SoftLogout extends React.Component {
static propTypes = {
// Nothing.
// Query parameters from MatrixChat
realQueryParams: PropTypes.object, // {homeserver, identityServer, loginToken}

// Called when the SSO login completes
onTokenLoginCompleted: PropTypes.func,
};

constructor() {
Expand All @@ -67,6 +73,7 @@ export default class SoftLogout extends React.Component {
displayName,
loginView: LOGIN_VIEW.LOADING,
keyBackupNeeded: true, // assume we do while we figure it out (see componentWillMount)
ssoUrl: null,

busy: false,
password: "",
Expand All @@ -75,6 +82,12 @@ export default class SoftLogout extends React.Component {
}

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();

MatrixClientPeg.get().flagAllGroupSessionsForBackup().then(remaining => {
Expand All @@ -95,13 +108,34 @@ export default class SoftLogout extends React.Component {
};

async _initLogin() {
const requiredQueryParams = ['homeserver', 'loginToken'];
const hasAllParams = requiredQueryParams
.filter(p => Object.keys(this.props.realQueryParams).includes(p)).length === requiredQueryParams.length;
turt2live marked this conversation as resolved.
Show resolved Hide resolved
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
// 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});

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) => {
Expand Down Expand Up @@ -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() {
if (this.state.loginView === LOGIN_VIEW.LOADING) {
const Spinner = sdk.getComponent("elements.Spinner");
Expand Down Expand Up @@ -202,8 +272,12 @@ export default class SoftLogout extends React.Component {
}

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>;
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return (
<AccessibleButton kind='primary' onClick={this.onSsoLogin}>
{_t('Sign in with single sign-on')}
</AccessibleButton>
);
}

// Default: assume unsupported
Expand Down