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:
commit
4e53e522fb
3 changed files with 144 additions and 37 deletions
|
@ -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();
|
||||
// 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;
|
||||
}
|
||||
// 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({
|
||||
|
@ -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'});
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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() && !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') {
|
||||
dis.dispatch({
|
||||
action: 'view_create_room',
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
@ -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,
|
||||
|
@ -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() {
|
||||
|
@ -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: "",
|
||||
|
@ -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 => {
|
||||
|
@ -95,6 +108,14 @@ export default class SoftLogout extends React.Component {
|
|||
};
|
||||
|
||||
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
|
||||
// care about login flows here, unless it is the single flow we support.
|
||||
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;
|
||||
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) => {
|
||||
|
@ -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() {
|
||||
if (this.state.loginView === LOGIN_VIEW.LOADING) {
|
||||
const Spinner = sdk.getComponent("elements.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 won’t be able to read all of your secure messages on any device.");
|
||||
}
|
||||
|
||||
if (this.state.loginView === LOGIN_VIEW.PASSWORD) {
|
||||
const Field = sdk.getComponent("elements.Field");
|
||||
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>;
|
||||
}
|
||||
|
||||
let introText = _t("Enter your password to sign in and regain access to your account.");
|
||||
if (this.state.keyBackupNeeded) {
|
||||
introText = _t(
|
||||
"Regain access your account and recover encryption keys stored on this device. " +
|
||||
"Without them, you won’t be able to read all of your secure messages on any device.");
|
||||
}
|
||||
if (!introText) {
|
||||
introText = _t("Enter your password to sign in and regain access to your account.");
|
||||
} // else we already have a message and should use it (key backup warning)
|
||||
|
||||
return (
|
||||
<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) {
|
||||
// TODO: TravisR - https://github.com/vector-im/riot-web/issues/10238
|
||||
return <p>PLACEHOLDER</p>;
|
||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||
|
||||
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 (
|
||||
<p>
|
||||
{_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.",
|
||||
)}
|
||||
</p>
|
||||
|
|
|
@ -93,7 +93,6 @@
|
|||
"Failed to add the following rooms to %(groupId)s:": "Failed to add the following rooms to %(groupId)s:",
|
||||
"Unnamed Room": "Unnamed Room",
|
||||
"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.",
|
||||
"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",
|
||||
|
@ -928,6 +927,7 @@
|
|||
"Saturday": "Saturday",
|
||||
"Today": "Today",
|
||||
"Yesterday": "Yesterday",
|
||||
"View Source": "View Source",
|
||||
"Error decrypting audio": "Error decrypting audio",
|
||||
"Reply": "Reply",
|
||||
"Edit": "Edit",
|
||||
|
@ -1127,6 +1127,7 @@
|
|||
"Start chatting": "Start chatting",
|
||||
"Click on the button below to start chatting!": "Click on the button below to start chatting!",
|
||||
"Start Chatting": "Start Chatting",
|
||||
"You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
|
||||
"Removing…": "Removing…",
|
||||
"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.",
|
||||
|
@ -1319,7 +1320,6 @@
|
|||
"Cancel Sending": "Cancel Sending",
|
||||
"Forward Message": "Forward Message",
|
||||
"Pin Message": "Pin Message",
|
||||
"View Source": "View Source",
|
||||
"View Decrypted Source": "View Decrypted Source",
|
||||
"Unhide Preview": "Unhide Preview",
|
||||
"Share Permalink": "Share Permalink",
|
||||
|
@ -1589,10 +1589,11 @@
|
|||
"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": "Failed to re-authenticate",
|
||||
"Regain access to your account and recover encryption keys stored on this device. Without them, you won’t 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 won’t 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.",
|
||||
"Regain access your account and recover encryption keys stored on this device. Without them, you won’t 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 won’t be able to read all of your secure messages on any device.",
|
||||
"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",
|
||||
"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",
|
||||
|
|
Loading…
Reference in a new issue