Support deactivating your account with SSO
Fixes https://github.com/vector-im/riot-web/issues/12940
This commit is contained in:
parent
2b6cbae4a7
commit
bdeba252ec
2 changed files with 117 additions and 89 deletions
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -23,71 +23,109 @@ import Analytics from '../../../Analytics';
|
||||||
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
import {MatrixClientPeg} from '../../../MatrixClientPeg';
|
||||||
import * as Lifecycle from '../../../Lifecycle';
|
import * as Lifecycle from '../../../Lifecycle';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
import InteractiveAuth, {ERROR_USER_CANCELLED} from "../../structures/InteractiveAuth";
|
||||||
|
import {DEFAULT_PHASE, PasswordAuthEntry, SSOAuthEntry} from "../auth/InteractiveAuthEntryComponents";
|
||||||
|
|
||||||
|
const dialogAesthetics = {
|
||||||
|
[SSOAuthEntry.PHASE_PREAUTH]: {
|
||||||
|
body: _t("Confirm your account deactivation by using Single Sign On to prove your identity."),
|
||||||
|
continueText: _t("Single Sign On"),
|
||||||
|
continueKind: "danger",
|
||||||
|
},
|
||||||
|
[SSOAuthEntry.PHASE_POSTAUTH]: {
|
||||||
|
body: _t("Are you sure you want to deactivate your account? This is irreversible."),
|
||||||
|
continueText: _t("Confirm account deactivation"),
|
||||||
|
continueKind: "danger",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the same as aestheticsForStagePhases in InteractiveAuthDialog minus the `title`
|
||||||
|
const DEACTIVATE_AESTHETICS = {
|
||||||
|
[SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
[SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics,
|
||||||
|
[PasswordAuthEntry.LOGIN_TYPE]: {
|
||||||
|
[DEFAULT_PHASE]: {
|
||||||
|
body: _t("To continue, please enter your password:"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default class DeactivateAccountDialog extends React.Component {
|
export default class DeactivateAccountDialog extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this._onOk = this._onOk.bind(this);
|
|
||||||
this._onCancel = this._onCancel.bind(this);
|
|
||||||
this._onPasswordFieldChange = this._onPasswordFieldChange.bind(this);
|
|
||||||
this._onEraseFieldChange = this._onEraseFieldChange.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
password: "",
|
|
||||||
busy: false,
|
|
||||||
shouldErase: false,
|
shouldErase: false,
|
||||||
errStr: null,
|
errStr: null,
|
||||||
|
authData: null, // for UIA
|
||||||
|
|
||||||
|
// A few strings that are passed to InteractiveAuth for design or are displayed
|
||||||
|
// next to the InteractiveAuth component.
|
||||||
|
bodyText: null,
|
||||||
|
continueText: null,
|
||||||
|
continueKind: null,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
_onPasswordFieldChange(ev) {
|
MatrixClientPeg.get().deactivateAccount(null, false).then(r => {
|
||||||
this.setState({
|
// If we got here, oops. The server didn't require any auth.
|
||||||
password: ev.target.value,
|
// Our application lifecycle will catch the error and do the logout bits.
|
||||||
|
// We'll try to log something in an vain attempt to record what happened (storage
|
||||||
|
// is also obliterated on logout).
|
||||||
|
console.warn("User's account got deactivated without confirmation: Server had no auth");
|
||||||
|
this.setState({errStr: _t("Server did not require any authentication")});
|
||||||
|
}).catch(e => {
|
||||||
|
if (e && e.httpStatus === 401 && e.data) {
|
||||||
|
// Valid UIA response
|
||||||
|
this.setState({authData: e.data});
|
||||||
|
} else {
|
||||||
|
this.setState({errStr: _t("Server did not return valid authentication information.")});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onEraseFieldChange(ev) {
|
_onStagePhaseChange = (stage, phase) => {
|
||||||
this.setState({
|
const aesthetics = DEACTIVATE_AESTHETICS[stage];
|
||||||
shouldErase: ev.target.checked,
|
let bodyText = null;
|
||||||
});
|
let continueText = null;
|
||||||
|
let continueKind = null;
|
||||||
|
if (aesthetics) {
|
||||||
|
const phaseAesthetics = aesthetics[phase];
|
||||||
|
if (phaseAesthetics && phaseAesthetics.body) bodyText = phaseAesthetics.body;
|
||||||
|
if (phaseAesthetics && phaseAesthetics.continueText) continueText = phaseAesthetics.continueText;
|
||||||
|
if (phaseAesthetics && phaseAesthetics.continueKind) continueKind = phaseAesthetics.continueKind;
|
||||||
}
|
}
|
||||||
|
this.setState({bodyText, continueText, continueKind});
|
||||||
async _onOk() {
|
|
||||||
this.setState({busy: true});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// This assumes that the HS requires password UI auth
|
|
||||||
// for this endpoint. In reality it could be any UI auth.
|
|
||||||
const auth = {
|
|
||||||
type: 'm.login.password',
|
|
||||||
// TODO: Remove `user` once servers support proper UIA
|
|
||||||
// See https://github.com/vector-im/riot-web/issues/10312
|
|
||||||
user: MatrixClientPeg.get().credentials.userId,
|
|
||||||
identifier: {
|
|
||||||
type: "m.id.user",
|
|
||||||
user: MatrixClientPeg.get().credentials.userId,
|
|
||||||
},
|
|
||||||
password: this.state.password,
|
|
||||||
};
|
};
|
||||||
await MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase);
|
|
||||||
} catch (err) {
|
_onUIAuthFinished = (success, result, extra) => {
|
||||||
let errStr = _t('Unknown error');
|
if (success) return; // great! makeRequest() will be called too.
|
||||||
// https://matrix.org/jira/browse/SYN-744
|
|
||||||
if (err.httpStatus === 401 || err.httpStatus === 403) {
|
if (result === ERROR_USER_CANCELLED) {
|
||||||
errStr = _t('Incorrect password');
|
this._onCancel();
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
busy: false,
|
|
||||||
errStr: errStr,
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error("Error during UI Auth:", {result, extra});
|
||||||
|
this.setState({errStr: _t("There was a problem communicating with the server. Please try again.")});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onUIAuthComplete = (auth) => {
|
||||||
|
MatrixClientPeg.get().deactivateAccount(auth, this.state.shouldErase).then(r => {
|
||||||
|
// Deactivation worked - logout & close this dialog
|
||||||
Analytics.trackEvent('Account', 'Deactivate Account');
|
Analytics.trackEvent('Account', 'Deactivate Account');
|
||||||
Lifecycle.onLoggedOut();
|
Lifecycle.onLoggedOut();
|
||||||
this.props.onFinished(true);
|
this.props.onFinished(true);
|
||||||
}
|
}).catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
this.setState({errStr: _t("There was a problem communicating with the server. Please try again.")});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_onEraseFieldChange = (ev) => {
|
||||||
|
this.setState({
|
||||||
|
shouldErase: ev.target.checked,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
_onCancel() {
|
_onCancel() {
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
|
@ -95,34 +133,36 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
|
||||||
let passwordBoxClass = '';
|
|
||||||
|
|
||||||
let error = null;
|
let error = null;
|
||||||
if (this.state.errStr) {
|
if (this.state.errStr) {
|
||||||
error = <div className="error">
|
error = <div className="error">
|
||||||
{ this.state.errStr }
|
{ this.state.errStr }
|
||||||
</div>;
|
</div>;
|
||||||
passwordBoxClass = 'error';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const okLabel = this.state.busy ? <Loader /> : _t('Deactivate Account');
|
let auth = <div>{_t("Loading...")}</div>;
|
||||||
const okEnabled = this.state.password && !this.state.busy;
|
if (this.state.authData) {
|
||||||
|
auth = (
|
||||||
let cancelButton = null;
|
<div>
|
||||||
if (!this.state.busy) {
|
{this.state.bodyText}
|
||||||
cancelButton = <button onClick={this._onCancel} autoFocus={true}>
|
<InteractiveAuth
|
||||||
{ _t("Cancel") }
|
matrixClient={MatrixClientPeg.get()}
|
||||||
</button>;
|
authData={this.state.authData}
|
||||||
|
makeRequest={this._onUIAuthComplete}
|
||||||
|
onAuthFinished={this._onUIAuthFinished}
|
||||||
|
onStagePhaseChange={this._onStagePhaseChange}
|
||||||
|
continueText={this.state.continueText}
|
||||||
|
continueKind={this.state.continueKind}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Field = sdk.getComponent('elements.Field');
|
|
||||||
|
|
||||||
// this is on purpose not a <form /> to prevent Enter triggering submission, to further prevent accidents
|
// this is on purpose not a <form /> to prevent Enter triggering submission, to further prevent accidents
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_DeactivateAccountDialog"
|
<BaseDialog className="mx_DeactivateAccountDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
onEnterPressed={this.onOk}
|
|
||||||
titleClass="danger"
|
titleClass="danger"
|
||||||
title={_t("Deactivate Account")}
|
title={_t("Deactivate Account")}
|
||||||
>
|
>
|
||||||
|
@ -172,28 +212,10 @@ export default class DeactivateAccountDialog extends React.Component {
|
||||||
</label>
|
</label>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>{ _t("To continue, please enter your password:") }</p>
|
|
||||||
<Field
|
|
||||||
type="password"
|
|
||||||
label={_t('Password')}
|
|
||||||
onChange={this._onPasswordFieldChange}
|
|
||||||
value={this.state.password}
|
|
||||||
className={passwordBoxClass}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{error}
|
{error}
|
||||||
|
{auth}
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_Dialog_buttons">
|
|
||||||
<button
|
|
||||||
className="mx_Dialog_primary danger"
|
|
||||||
onClick={this._onOk}
|
|
||||||
disabled={!okEnabled}
|
|
||||||
>
|
|
||||||
{ okLabel }
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{ cancelButton }
|
|
||||||
</div>
|
</div>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1566,13 +1566,17 @@
|
||||||
"You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ",
|
"You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ": "You've previously used a newer version of Riot on %(host)s. To use this version again with end to end encryption, you will need to sign out and back in again. ",
|
||||||
"Incompatible Database": "Incompatible Database",
|
"Incompatible Database": "Incompatible Database",
|
||||||
"Continue With Encryption Disabled": "Continue With Encryption Disabled",
|
"Continue With Encryption Disabled": "Continue With Encryption Disabled",
|
||||||
"Unknown error": "Unknown error",
|
"Confirm your account deactivation by using Single Sign On to prove your identity.": "Confirm your account deactivation by using Single Sign On to prove your identity.",
|
||||||
"Incorrect password": "Incorrect password",
|
"Are you sure you want to deactivate your account? This is irreversible.": "Are you sure you want to deactivate your account? This is irreversible.",
|
||||||
|
"Confirm account deactivation": "Confirm account deactivation",
|
||||||
|
"To continue, please enter your password:": "To continue, please enter your password:",
|
||||||
|
"Server did not require any authentication": "Server did not require any authentication",
|
||||||
|
"Server did not return valid authentication information.": "Server did not return valid authentication information.",
|
||||||
|
"There was a problem communicating with the server. Please try again.": "There was a problem communicating with the server. Please try again.",
|
||||||
"This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. <b>This action is irreversible.</b>": "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. <b>This action is irreversible.</b>",
|
"This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. <b>This action is irreversible.</b>": "This will make your account permanently unusable. You will not be able to log in, and no one will be able to re-register the same user ID. This will cause your account to leave all rooms it is participating in, and it will remove your account details from your identity server. <b>This action is irreversible.</b>",
|
||||||
"Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.": "Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.",
|
"Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.": "Deactivating your account <b>does not by default cause us to forget messages you have sent.</b> If you would like us to forget your messages, please tick the box below.",
|
||||||
"Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.",
|
"Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.": "Message visibility in Matrix is similar to email. Our forgetting your messages means that messages you have sent will not be shared with any new or unregistered users, but registered users who already have access to these messages will still have access to their copy.",
|
||||||
"Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)",
|
"Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)": "Please forget all messages I have sent when my account is deactivated (<b>Warning:</b> this will cause future users to see an incomplete view of conversations)",
|
||||||
"To continue, please enter your password:": "To continue, please enter your password:",
|
|
||||||
"Verify session": "Verify session",
|
"Verify session": "Verify session",
|
||||||
"Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)",
|
"Use Legacy Verification (for older clients)": "Use Legacy Verification (for older clients)",
|
||||||
"Verify by comparing a short text string.": "Verify by comparing a short text string.",
|
"Verify by comparing a short text string.": "Verify by comparing a short text string.",
|
||||||
|
@ -1963,6 +1967,7 @@
|
||||||
"Failed to leave room": "Failed to leave room",
|
"Failed to leave room": "Failed to leave room",
|
||||||
"Can't leave Server Notices room": "Can't leave Server Notices room",
|
"Can't leave Server Notices room": "Can't leave Server Notices room",
|
||||||
"This room is used for important messages from the Homeserver, so you cannot leave it.": "This room is used for important messages from the Homeserver, so you cannot leave it.",
|
"This room is used for important messages from the Homeserver, so you cannot leave it.": "This room is used for important messages from the Homeserver, so you cannot leave it.",
|
||||||
|
"Unknown error": "Unknown error",
|
||||||
"Signed Out": "Signed Out",
|
"Signed Out": "Signed Out",
|
||||||
"For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.",
|
"For security, this session has been signed out. Please sign in again.": "For security, this session has been signed out. Please sign in again.",
|
||||||
"Terms and Conditions": "Terms and Conditions",
|
"Terms and Conditions": "Terms and Conditions",
|
||||||
|
@ -2102,6 +2107,7 @@
|
||||||
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
||||||
"Go Back": "Go Back",
|
"Go Back": "Go Back",
|
||||||
"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",
|
||||||
|
"Incorrect password": "Incorrect password",
|
||||||
"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 in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.",
|
"Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.": "Regain access to your account and recover encryption keys stored in this session. Without them, you won’t be able to read all of your secure messages in any session.",
|
||||||
"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.",
|
||||||
|
|
Loading…
Reference in a new issue