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
|
// 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}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 won’t 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 won’t 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>
|
||||||
|
|
|
@ -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 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.",
|
"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?",
|
"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",
|
||||||
|
|
Loading…
Reference in a new issue