diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss
index fe1f283009..7a1c39d1f6 100644
--- a/res/css/views/elements/_AccessibleButton.scss
+++ b/res/css/views/elements/_AccessibleButton.scss
@@ -79,3 +79,22 @@ limitations under the License.
color: $button-danger-disabled-fg-color;
background-color: $button-danger-disabled-bg-color;
}
+
+.mx_AccessibleButton_kind_link {
+ color: $button-link-fg-color;
+ background-color: $button-link-bg-color;
+}
+
+.mx_AccessibleButton_kind_link.mx_AccessibleButton_disabled {
+ opacity: 0.4;
+}
+
+.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_link_sm {
+ padding: 5px 12px;
+ color: $button-link-fg-color;
+ background-color: $button-link-bg-color;
+}
+
+.mx_AccessibleButton_kind_link_sm.mx_AccessibleButton_disabled {
+ opacity: 0.4;
+}
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index f2bfe5bc8a..fc3da7bb0a 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -143,6 +143,8 @@ $button-danger-fg-color: #ffffff;
$button-danger-bg-color: $notice-primary-color;
$button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
+$button-link-fg-color: $accent-color;
+$button-link-bg-color: transparent;
$room-warning-bg-color: $header-panel-bg-color;
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index 447516a26b..d8d4b0a11b 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -244,6 +244,8 @@ $button-danger-fg-color: #ffffff;
$button-danger-bg-color: $notice-primary-color;
$button-danger-disabled-fg-color: #ffffff;
$button-danger-disabled-bg-color: #f5b6bb; // TODO: Verify color
+$button-link-fg-color: $accent-color;
+$button-link-bg-color: transparent;
// Toggle switch
$togglesw-off-color: #c1c9d6;
diff --git a/src/Lifecycle.js b/src/Lifecycle.js
index 1b76c61071..e3c4d39242 100644
--- a/src/Lifecycle.js
+++ b/src/Lifecycle.js
@@ -340,6 +340,25 @@ export function setLoggedIn(credentials) {
return _doSetLoggedIn(credentials, true);
}
+/**
+ * Hydrates an existing session by using the credentials provided. This will
+ * not clear any local storage, unlike setLoggedIn().
+ *
+ * Stops the existing Matrix client (without clearing its data) and starts a
+ * new one in its place. This additionally starts all other react-sdk services
+ * which use the new Matrix client.
+ *
+ * @param {MatrixClientCreds} credentials The credentials to use
+ *
+ * @returns {Promise} promise which resolves to the new MatrixClient once it has been started
+ */
+export function hydrateSession(credentials) {
+ stopMatrixClient();
+ localStorage.removeItem("mx_soft_logout");
+ _isLoggingOut = false;
+ return _doSetLoggedIn(credentials, false);
+}
+
/**
* fires on_logging_in, optionally clears localstorage, persists new credentials
* to localstorage, starts the new client.
@@ -541,6 +560,7 @@ async function startMatrixClient(startSyncing=true) {
await MatrixClientPeg.start();
} else {
console.warn("Caller requested only auxiliary services be started");
+ await MatrixClientPeg.assign();
}
// dispatch that we finished starting up to wire up any other bits
diff --git a/src/MatrixClientPeg.js b/src/MatrixClientPeg.js
index 07499a3a87..ca75b68e57 100644
--- a/src/MatrixClientPeg.js
+++ b/src/MatrixClientPeg.js
@@ -120,7 +120,7 @@ class MatrixClientPeg {
this._createClient(creds);
}
- async start() {
+ async assign() {
for (const dbType of ['indexeddb', 'memory']) {
try {
const promise = this.matrixClient.store.startup();
@@ -131,7 +131,7 @@ class MatrixClientPeg {
if (dbType === 'indexeddb') {
console.error('Error starting matrixclient store - falling back to memory store', err);
this.matrixClient.store = new Matrix.MemoryStore({
- localStorage: global.localStorage,
+ localStorage: global.localStorage,
});
} else {
console.error('Failed to start memory store!', err);
@@ -172,6 +172,12 @@ class MatrixClientPeg {
MatrixActionCreators.start(this.matrixClient);
MatrixClientBackedSettingsHandler.matrixClient = this.matrixClient;
+ return opts;
+ }
+
+ async start() {
+ const opts = await this.assign();
+
console.log(`MatrixClientPeg: really starting MatrixClient`);
await this.get().startClient(opts);
console.log(`MatrixClientPeg: MatrixClient started`);
diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js
index a6965e06ca..a93492cd41 100644
--- a/src/components/structures/MatrixChat.js
+++ b/src/components/structures/MatrixChat.js
@@ -450,6 +450,10 @@ export default React.createClass({
startAnyRegistrationFlow(payload);
break;
case 'start_registration':
+ if (Lifecycle.isSoftLogout()) {
+ this._onSoftLogout();
+ break;
+ }
// This starts the full registration flow
if (payload.screenAfterLogin) {
this._screenAfterLogin = payload.screenAfterLogin;
@@ -457,6 +461,10 @@ export default React.createClass({
this._startRegistration(payload.params || {});
break;
case 'start_login':
+ if (Lifecycle.isSoftLogout()) {
+ this._onSoftLogout();
+ break;
+ }
if (payload.screenAfterLogin) {
this._screenAfterLogin = payload.screenAfterLogin;
}
diff --git a/src/components/structures/auth/SoftLogout.js b/src/components/structures/auth/SoftLogout.js
index 85654d512a..57deee7175 100644
--- a/src/components/structures/auth/SoftLogout.js
+++ b/src/components/structures/auth/SoftLogout.js
@@ -23,6 +23,21 @@ import Modal from '../../../Modal';
import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils";
import SdkConfig from "../../../SdkConfig";
import MatrixClientPeg from "../../../MatrixClientPeg";
+import {sendLoginRequest} from "../../../Login";
+
+const LOGIN_VIEW = {
+ LOADING: 1,
+ PASSWORD: 2,
+ CAS: 3, // SSO, but old
+ SSO: 4,
+ UNSUPPORTED: 5,
+};
+
+const FLOWS_TO_VIEWS = {
+ "m.login.password": LOGIN_VIEW.PASSWORD,
+ "m.login.cas": LOGIN_VIEW.CAS,
+ "m.login.sso": LOGIN_VIEW.SSO,
+};
export default class SoftLogout extends React.Component {
static propTypes = {
@@ -48,9 +63,18 @@ export default class SoftLogout extends React.Component {
domainName,
userId,
displayName,
+ loginView: LOGIN_VIEW.LOADING,
+
+ busy: false,
+ password: "",
+ errorText: "",
};
}
+ componentDidMount(): void {
+ this._initLogin();
+ }
+
onClearAll = () => {
const ConfirmWipeDeviceDialog = sdk.getComponent('dialogs.ConfirmWipeDeviceDialog');
Modal.createTrackedDialog('Clear Data', 'Soft Logout', ConfirmWipeDeviceDialog, {
@@ -63,10 +87,122 @@ export default class SoftLogout extends React.Component {
});
};
- onLogin = () => {
- dis.dispatch({action: 'start_login'});
+ async _initLogin() {
+ // 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});
+ }
+
+ onPasswordChange = (ev) => {
+ this.setState({password: ev.target.value});
};
+ onForgotPassword = () => {
+ dis.dispatch({action: 'start_password_recovery'});
+ };
+
+ onPasswordLogin = async (ev) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ this.setState({busy: true});
+
+ const hsUrl = MatrixClientPeg.get().getHomeserverUrl();
+ const isUrl = MatrixClientPeg.get().getIdentityServerUrl();
+ const loginType = "m.login.password";
+ const loginParams = {
+ identifier: {
+ type: "m.id.user",
+ user: MatrixClientPeg.get().getUserId(),
+ },
+ password: this.state.password,
+ device_id: MatrixClientPeg.get().getDeviceId(),
+ };
+
+ let credentials = null;
+ try {
+ credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
+ } catch (e) {
+ let errorText = _t("Failed to re-authenticate due to a homeserver problem");
+ if (e.errcode === "M_FORBIDDEN" && (e.httpStatus === 401 || e.httpStatus === 403)) {
+ errorText = _t("Incorrect password");
+ }
+
+ this.setState({
+ busy: false,
+ errorText: errorText,
+ });
+ return;
+ }
+
+ Lifecycle.hydrateSession(credentials).catch((e) => {
+ console.error(e);
+ this.setState({busy: false, errorText: _t("Failed to re-authenticate")});
+ });
+ };
+
+ _renderSignInSection() {
+ if (this.state.loginView === LOGIN_VIEW.LOADING) {
+ const Spinner = sdk.getComponent("elements.Spinner");
+ return
PLACEHOLDER
; + } + + // Default: assume unsupported + return ( ++ {_t( + "Cannot re-authenticate with your account. Please contact your " + + "homeserver admin for more information.", + )} +
+ ); + } + render() { const AuthPage = sdk.getComponent("auth.AuthPage"); const AuthHeader = sdk.getComponent("auth.AuthHeader"); @@ -107,14 +243,7 @@ export default class SoftLogout extends React.Component {