Merge pull request #2578 from jryans/auth-reset-password

Style reset password to match design
This commit is contained in:
J. Ryan Stinnett 2019-02-07 10:05:11 +00:00 committed by GitHub
commit 30395f064d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 191 additions and 106 deletions

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017, 2018 New Vector Ltd Copyright 2017, 2018, 2019 New Vector Ltd
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.
@ -25,6 +25,18 @@ import SdkConfig from "../../../SdkConfig";
import PasswordReset from "../../../PasswordReset"; import PasswordReset from "../../../PasswordReset";
// Phases
// Show controls to configure server details
const PHASE_SERVER_DETAILS = 0;
// Show the forgot password inputs
const PHASE_FORGOT = 1;
// Email is in the process of being sent
const PHASE_SENDING_EMAIL = 2;
// Email has been sent
const PHASE_EMAIL_SENT = 3;
// User has clicked the link in email and completed reset
const PHASE_DONE = 4;
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'ForgotPassword', displayName: 'ForgotPassword',
@ -47,28 +59,29 @@ module.exports = React.createClass({
getInitialState: function() { getInitialState: function() {
return { return {
enteredHomeserverUrl: this.props.customHsUrl || this.props.defaultHsUrl, enteredHsUrl: this.props.customHsUrl || this.props.defaultHsUrl,
enteredIdentityServerUrl: this.props.customIsUrl || this.props.defaultIsUrl, enteredIsUrl: this.props.customIsUrl || this.props.defaultIsUrl,
progress: null, phase: PHASE_FORGOT,
password: null, email: "",
password2: null, password: "",
password2: "",
errorText: null, errorText: null,
}; };
}, },
submitPasswordReset: function(hsUrl, identityUrl, email, password) { submitPasswordReset: function(hsUrl, identityUrl, email, password) {
this.setState({ this.setState({
progress: "sending_email", phase: PHASE_SENDING_EMAIL,
}); });
this.reset = new PasswordReset(hsUrl, identityUrl); this.reset = new PasswordReset(hsUrl, identityUrl);
this.reset.resetPassword(email, password).done(() => { this.reset.resetPassword(email, password).done(() => {
this.setState({ this.setState({
progress: "sent_email", phase: PHASE_EMAIL_SENT,
}); });
}, (err) => { }, (err) => {
this.showErrorDialog(_t('Failed to send email') + ": " + err.message); this.showErrorDialog(_t('Failed to send email') + ": " + err.message);
this.setState({ this.setState({
progress: null, phase: PHASE_FORGOT,
}); });
}); });
}, },
@ -80,7 +93,7 @@ module.exports = React.createClass({
return; return;
} }
this.reset.checkEmailLinkClicked().done((res) => { this.reset.checkEmailLinkClicked().done((res) => {
this.setState({ progress: "complete" }); this.setState({ phase: PHASE_DONE });
}, (err) => { }, (err) => {
this.showErrorDialog(err.message); this.showErrorDialog(err.message);
}); });
@ -126,7 +139,7 @@ module.exports = React.createClass({
onFinished: (confirmed) => { onFinished: (confirmed) => {
if (confirmed) { if (confirmed) {
this.submitPasswordReset( this.submitPasswordReset(
this.state.enteredHomeserverUrl, this.state.enteredIdentityServerUrl, this.state.enteredHsUrl, this.state.enteredIsUrl,
this.state.email, this.state.password, this.state.email, this.state.password,
); );
} }
@ -153,14 +166,29 @@ module.exports = React.createClass({
onServerConfigChange: function(config) { onServerConfigChange: function(config) {
const newState = {}; const newState = {};
if (config.hsUrl !== undefined) { if (config.hsUrl !== undefined) {
newState.enteredHomeserverUrl = config.hsUrl; newState.enteredHsUrl = config.hsUrl;
} }
if (config.isUrl !== undefined) { if (config.isUrl !== undefined) {
newState.enteredIdentityServerUrl = config.isUrl; newState.enteredIsUrl = config.isUrl;
} }
this.setState(newState); this.setState(newState);
}, },
onServerDetailsNextPhaseClick(ev) {
ev.stopPropagation();
this.setState({
phase: PHASE_FORGOT,
});
},
onEditServerDetailsClick(ev) {
ev.preventDefault();
ev.stopPropagation();
this.setState({
phase: PHASE_SERVER_DETAILS,
});
},
onLoginClick: function(ev) { onLoginClick: function(ev) {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
@ -175,94 +203,151 @@ module.exports = React.createClass({
}); });
}, },
render: function() { renderServerDetails() {
const AuthPage = sdk.getComponent("auth.AuthPage");
const AuthHeader = sdk.getComponent("auth.AuthHeader");
const AuthBody = sdk.getComponent("auth.AuthBody");
const ServerConfig = sdk.getComponent("auth.ServerConfig"); const ServerConfig = sdk.getComponent("auth.ServerConfig");
const Spinner = sdk.getComponent("elements.Spinner"); const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
let resetPasswordJsx; if (SdkConfig.get()['disable_custom_urls']) {
return null;
if (this.state.progress === "sending_email") {
resetPasswordJsx = <Spinner />;
} else if (this.state.progress === "sent_email") {
resetPasswordJsx = (
<div>
{ _t("An email has been sent to %(emailAddress)s. Once you've followed the link it contains, " +
"click below.", { emailAddress: this.state.email }) }
<br />
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
value={_t('I have verified my email address')} />
</div>
);
} else if (this.state.progress === "complete") {
resetPasswordJsx = (
<div>
<p>{ _t('Your password has been reset') }.</p>
<p>{ _t('You have been logged out of all devices and will no longer receive push notifications. ' +
'To re-enable notifications, sign in again on each device') }.</p>
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
value={_t('Return to login screen')} />
</div>
);
} else {
let serverConfigSection;
if (!SdkConfig.get()['disable_custom_urls']) {
serverConfigSection = (
<ServerConfig ref="serverConfig"
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
customHsUrl={this.props.customHsUrl}
customIsUrl={this.props.customIsUrl}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={0} />
);
} }
return <div>
<ServerConfig
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
customHsUrl={this.state.enteredHsUrl}
customIsUrl={this.state.enteredIsUrl}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={0} />
<AccessibleButton className="mx_Login_submit"
onClick={this.onServerDetailsNextPhaseClick}
>
{_t("Next")}
</AccessibleButton>
</div>;
},
renderForgot() {
let errorText = null; let errorText = null;
const err = this.state.errorText || this.props.defaultServerDiscoveryError; const err = this.state.errorText || this.props.defaultServerDiscoveryError;
if (err) { if (err) {
errorText = <div className="mx_Login_error">{ err }</div>; errorText = <div className="mx_Login_error">{ err }</div>;
} }
resetPasswordJsx = ( let yourMatrixAccountText = _t('Your account');
<div> try {
<p> const parsedHsUrl = new URL(this.state.enteredHsUrl);
{ _t('To reset your password, enter the email address linked to your account') }: yourMatrixAccountText = _t('Your account on %(serverName)s', {
</p> serverName: parsedHsUrl.hostname,
<div> });
} catch (e) {
errorText = <div className="mx_Login_error">{_t(
"The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please " +
"enter a valid URL including the protocol prefix.",
{
hsUrl: this.state.enteredHsUrl,
})}</div>;
}
// If custom URLs are allowed, wire up the server details edit link.
let editLink = null;
if (!SdkConfig.get()['disable_custom_urls']) {
editLink = <a className="mx_AuthBody_editServerDetails"
href="#" onClick={this.onEditServerDetailsClick}
>
{_t('Change')}
</a>;
}
return <div>
<h3>
{yourMatrixAccountText}
{editLink}
</h3>
{errorText}
<form onSubmit={this.onSubmitForm}> <form onSubmit={this.onSubmitForm}>
<input className="mx_Login_field" ref="user" type="text" <div className="mx_AuthBody_fieldRow">
<input className="mx_Login_field" type="text"
name="reset_email" // define a name so browser's password autofill gets less confused name="reset_email" // define a name so browser's password autofill gets less confused
value={this.state.email} value={this.state.email}
onChange={this.onInputChanged.bind(this, "email")} onChange={this.onInputChanged.bind(this, "email")}
placeholder={_t('Email address')} autoFocus /> placeholder={_t('Email')} autoFocus />
<br /> </div>
<input className="mx_Login_field" ref="pass" type="password" <div className="mx_AuthBody_fieldRow">
<input className="mx_Login_field" type="password"
name="reset_password" name="reset_password"
value={this.state.password} value={this.state.password}
onChange={this.onInputChanged.bind(this, "password")} onChange={this.onInputChanged.bind(this, "password")}
placeholder={_t('New password')} /> placeholder={_t('Password')} />
<br /> <input className="mx_Login_field" type="password"
<input className="mx_Login_field" ref="pass" type="password"
name="reset_password_confirm" name="reset_password_confirm"
value={this.state.password2} value={this.state.password2}
onChange={this.onInputChanged.bind(this, "password2")} onChange={this.onInputChanged.bind(this, "password2")}
placeholder={_t('Confirm your new password')} /> placeholder={_t('Confirm')} />
<br /> </div>
<span>{_t(
'A verification email will be sent to your inbox to confirm ' +
'setting your new password.',
)}</span>
<input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} /> <input className="mx_Login_submit" type="submit" value={_t('Send Reset Email')} />
</form> </form>
{ serverConfigSection }
{ errorText }
<a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#"> <a className="mx_AuthBody_changeFlow" onClick={this.onLoginClick} href="#">
{_t('Sign in instead')} {_t('Sign in instead')}
</a> </a>
</div> </div>;
</div> },
);
}
renderSendingEmail() {
const Spinner = sdk.getComponent("elements.Spinner");
return <Spinner />;
},
renderEmailSent() {
return <div>
{_t("An email has been sent to %(emailAddress)s. Once you've followed the " +
"link it contains, click below.", { emailAddress: this.state.email })}
<br />
<input className="mx_Login_submit" type="button" onClick={this.onVerify}
value={_t('I have verified my email address')} />
</div>;
},
renderDone() {
return <div>
<p>{_t("Your password has been reset.")}</p>
<p>{_t(
"You have been logged out of all devices and will no longer receive " +
"push notifications. To re-enable notifications, sign in again on each " +
"device.",
)}</p>
<input className="mx_Login_submit" type="button" onClick={this.props.onComplete}
value={_t('Return to login screen')} />
</div>;
},
render: function() {
const AuthPage = sdk.getComponent("auth.AuthPage");
const AuthHeader = sdk.getComponent("auth.AuthHeader");
const AuthBody = sdk.getComponent("auth.AuthBody");
let resetPasswordJsx;
switch (this.state.phase) {
case PHASE_SERVER_DETAILS:
resetPasswordJsx = this.renderServerDetails();
break;
case PHASE_FORGOT:
resetPasswordJsx = this.renderForgot();
break;
case PHASE_SENDING_EMAIL:
resetPasswordJsx = this.renderSendingEmail();
break;
case PHASE_EMAIL_SENT:
resetPasswordJsx = this.renderEmailSent();
break;
case PHASE_DONE:
resetPasswordJsx = this.renderDone();
break;
}
return ( return (
<AuthPage> <AuthPage>

View file

@ -31,10 +31,10 @@ import { AutoDiscovery } from "matrix-js-sdk";
const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/;
// Phases // Phases
// Show the appropriate login flow(s) for the server
const PHASE_LOGIN = 0;
// Show controls to configure server details // Show controls to configure server details
const PHASE_SERVER_DETAILS = 1; const PHASE_SERVER_DETAILS = 0;
// Show the appropriate login flow(s) for the server
const PHASE_LOGIN = 1;
// Enable phases for login // Enable phases for login
const PHASES_ENABLED = true; const PHASES_ENABLED = true;
@ -514,7 +514,6 @@ module.exports = React.createClass({
} }
if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) { if (PHASES_ENABLED && this.state.phase !== PHASE_SERVER_DETAILS) {
// TODO: ...
return null; return null;
} }

View file

@ -1335,16 +1335,17 @@
"A new password must be entered.": "A new password must be entered.", "A new password must be entered.": "A new password must be entered.",
"New passwords must match each other.": "New passwords must match each other.", "New passwords must match each other.": "New passwords must match each other.",
"Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.", "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.": "Resetting password will currently reset any end-to-end encryption keys on all devices, making encrypted chat history unreadable, unless you first export your room keys and re-import them afterwards. In future this will be improved.",
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", "Your account": "Your account",
"I have verified my email address": "I have verified my email address", "Your account on %(serverName)s": "Your account on %(serverName)s",
"Your password has been reset": "Your password has been reset", "The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please enter a valid URL including the protocol prefix.": "The homeserver URL %(hsUrl)s doesn't seem to be valid URL. Please enter a valid URL including the protocol prefix.",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device", "A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.",
"Return to login screen": "Return to login screen",
"To reset your password, enter the email address linked to your account": "To reset your password, enter the email address linked to your account",
"New password": "New password",
"Confirm your new password": "Confirm your new password",
"Send Reset Email": "Send Reset Email", "Send Reset Email": "Send Reset Email",
"Sign in instead": "Sign in instead", "Sign in instead": "Sign in instead",
"An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.",
"I have verified my email address": "I have verified my email address",
"Your password has been reset.": "Your password has been reset.",
"You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.": "You have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, sign in again on each device.",
"Return to login screen": "Return to login screen",
"Set a new password": "Set a new password", "Set a new password": "Set a new password",
"Invalid homeserver discovery response": "Invalid homeserver discovery response", "Invalid homeserver discovery response": "Invalid homeserver discovery response",
"Invalid identity server discovery response": "Invalid identity server discovery response", "Invalid identity server discovery response": "Invalid identity server discovery response",