Merge pull request #3197 from matrix-org/travis/soft-logout-sso

Support SSO for rehydrating a soft-logged-out session.
This commit is contained in:
Travis Ralston 2019-07-10 08:15:02 -06:00 committed by GitHub
commit 4e53e522fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 144 additions and 37 deletions

View file

@ -283,29 +283,32 @@ 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
Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => { // if the session isn't soft logged out (ie: is a clean session being logged in)
if (loggedIn) { if (!Lifecycle.isSoftLogout()) {
this.props.onTokenLoginCompleted(); Lifecycle.attemptTokenLogin(this.props.realQueryParams).then((loggedIn) => {
if (loggedIn) {
this.props.onTokenLoginCompleted();
// don't do anything else until the page reloads - just stay in // don't do anything else until the page reloads - just stay in
// the 'loading' state. // the 'loading' state.
return; return;
} }
// if the user has followed a login or register link, don't reanimate // 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 old creds, but rather go straight to the relevant page
const firstScreen = this._screenAfterLogin ? const firstScreen = this._screenAfterLogin ?
this._screenAfterLogin.screen : null; this._screenAfterLogin.screen : null;
if (firstScreen === 'login' || if (firstScreen === 'login' ||
firstScreen === 'register' || firstScreen === 'register' ||
firstScreen === 'forgot_password') { firstScreen === 'forgot_password') {
this._showScreenAfterLogin(); this._showScreenAfterLogin();
return; return;
} }
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() && !Lifecycle.isSoftLogout()) {
// 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,14 @@ export default class SoftLogout extends React.Component {
}; };
async _initLogin() { async _initLogin() {
const queryParams = this.props.realQueryParams;
const hasAllParams = queryParams && queryParams['homeserver'] && queryParams['loginToken'];
if (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 +123,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,12 +185,55 @@ 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");
return <Spinner />; return <Spinner />;
} }
let introText = null; // null is translated to something area specific in this function
if (this.state.keyBackupNeeded) {
introText = _t(
"Regain access to your account and recover encryption keys stored on this device. " +
"Without them, you wont be able to read all of your secure messages on any device.");
}
if (this.state.loginView === LOGIN_VIEW.PASSWORD) { if (this.state.loginView === LOGIN_VIEW.PASSWORD) {
const Field = sdk.getComponent("elements.Field"); const Field = sdk.getComponent("elements.Field");
const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -167,12 +243,9 @@ export default class SoftLogout extends React.Component {
error = <span className='mx_Login_error'>{this.state.errorText}</span>; error = <span className='mx_Login_error'>{this.state.errorText}</span>;
} }
let introText = _t("Enter your password to sign in and regain access to your account."); if (!introText) {
if (this.state.keyBackupNeeded) { introText = _t("Enter your password to sign in and regain access to your account.");
introText = _t( } // else we already have a message and should use it (key backup warning)
"Regain access your account and recover encryption keys stored on this device. " +
"Without them, you wont be able to read all of your secure messages on any device.");
}
return ( return (
<form onSubmit={this.onPasswordLogin}> <form onSubmit={this.onPasswordLogin}>
@ -202,15 +275,27 @@ 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>;
if (!introText) {
introText = _t("Sign in and regain access to your account.");
} // else we already have a message and should use it (key backup warning)
return (
<div>
<p>{introText}</p>
<AccessibleButton kind='primary' onClick={this.onSsoLogin}>
{_t('Sign in with single sign-on')}
</AccessibleButton>
</div>
);
} }
// Default: assume unsupported // Default: assume unsupported/error
return ( return (
<p> <p>
{_t( {_t(
"Cannot re-authenticate with your account. Please contact your " + "You cannot sign in to your account. Please contact your " +
"homeserver admin for more information.", "homeserver admin for more information.",
)} )}
</p> </p>

View file

@ -93,7 +93,6 @@
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:", "Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
"Unnamed Room": "Unnamed Room", "Unnamed Room": "Unnamed Room",
"Error": "Error", "Error": "Error",
"You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
"Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.", "Unable to load! Check your network connectivity and try again.": "Unable to load! Check your network connectivity and try again.",
"Dismiss": "Dismiss", "Dismiss": "Dismiss",
"Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings", "Riot does not have permission to send you notifications - please check your browser settings": "Riot does not have permission to send you notifications - please check your browser settings",
@ -928,6 +927,7 @@
"Saturday": "Saturday", "Saturday": "Saturday",
"Today": "Today", "Today": "Today",
"Yesterday": "Yesterday", "Yesterday": "Yesterday",
"View Source": "View Source",
"Error decrypting audio": "Error decrypting audio", "Error decrypting audio": "Error decrypting audio",
"Reply": "Reply", "Reply": "Reply",
"Edit": "Edit", "Edit": "Edit",
@ -1127,6 +1127,7 @@
"Start chatting": "Start chatting", "Start chatting": "Start chatting",
"Click on the button below to start chatting!": "Click on the button below to start chatting!", "Click on the button below to start chatting!": "Click on the button below to start chatting!",
"Start Chatting": "Start Chatting", "Start Chatting": "Start Chatting",
"You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
"Removing…": "Removing…", "Removing…": "Removing…",
"Confirm Removal": "Confirm Removal", "Confirm Removal": "Confirm Removal",
"Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.", "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.": "Are you sure you wish to remove (delete) this event? Note that if you delete a room name or topic change, it could undo the change.",
@ -1319,7 +1320,6 @@
"Cancel Sending": "Cancel Sending", "Cancel Sending": "Cancel Sending",
"Forward Message": "Forward Message", "Forward Message": "Forward Message",
"Pin Message": "Pin Message", "Pin Message": "Pin Message",
"View Source": "View Source",
"View Decrypted Source": "View Decrypted Source", "View Decrypted Source": "View Decrypted Source",
"Unhide Preview": "Unhide Preview", "Unhide Preview": "Unhide Preview",
"Share Permalink": "Share Permalink", "Share Permalink": "Share Permalink",
@ -1589,10 +1589,11 @@
"Create your account": "Create your account", "Create your account": "Create your account",
"Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem", "Failed to re-authenticate due to a homeserver problem": "Failed to re-authenticate due to a homeserver problem",
"Failed to re-authenticate": "Failed to re-authenticate", "Failed to re-authenticate": "Failed to re-authenticate",
"Regain access to your account and recover encryption keys stored on this device. Without them, you wont be able to read all of your secure messages on any device.": "Regain access to your account and recover encryption keys stored on this device. Without them, you wont be able to read all of your secure messages on any device.",
"Enter your password to sign in and regain access to your account.": "Enter your password to sign in and regain access to your account.", "Enter your password to sign in and regain access to your account.": "Enter your password to sign in and regain access to your account.",
"Regain access your account and recover encryption keys stored on this device. Without them, you wont be able to read all of your secure messages on any device.": "Regain access your account and recover encryption keys stored on this device. Without them, you wont be able to read all of your secure messages on any device.",
"Forgotten your password?": "Forgotten your password?", "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.", "Sign in and regain access to your account.": "Sign in and regain access to your account.",
"You cannot sign in to your account. Please contact your homeserver admin for more information.": "You cannot sign in to your account. Please contact your homeserver admin for more information.",
"You're signed out": "You're signed out", "You're signed out": "You're signed out",
"Your homeserver (<strong1>%(domainName)s</strong1>) admin has signed you out of your account <strong2>%(displayName)s (%(userId)s)</strong2>.": "Your homeserver (<strong1>%(domainName)s</strong1>) admin has signed you out of your account <strong2>%(displayName)s (%(userId)s)</strong2>.", "Your homeserver (<strong1>%(domainName)s</strong1>) admin has signed you out of your account <strong2>%(displayName)s (%(userId)s)</strong2>.": "Your homeserver (<strong1>%(domainName)s</strong1>) admin has signed you out of your account <strong2>%(displayName)s (%(userId)s)</strong2>.",
"Clear personal data": "Clear personal data", "Clear personal data": "Clear personal data",