Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Further password reset flow enhancements #9662

Merged
merged 5 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 42 additions & 13 deletions res/css/views/auth/_AuthBody.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,50 @@ limitations under the License.
}

/* specialisation for password reset views */
.mx_AuthBody_forgot-password {
.mx_AuthBody.mx_AuthBody_forgot-password {
font-size: $font-14px;
color: $primary-content;
padding: 50px 32px;
min-height: 600px;

h1 {
margin-bottom: $spacing-20;
margin-top: $spacing-24;
margin: $spacing-24 0;
}

.mx_AuthBody_button-container {
display: flex;
justify-content: center;
}

.mx_Login_submit {
font-weight: $font-semi-bold;
margin: 0 0 $spacing-16;
}

.mx_AuthBody_text {
margin-bottom: $spacing-32;

p {
margin: 0 0 $spacing-8;
}
}

.mx_AuthBody_sign-in-instead-button {
font-weight: $font-semi-bold;
padding: $spacing-4;
}

.mx_AuthBody_fieldRow {
margin-bottom: $spacing-24;
}

.mx_AccessibleButton.mx_AccessibleButton_hasKind {
background: none;

&:disabled {
cursor: default;
opacity: .4;
}
}
}

Expand All @@ -154,12 +189,6 @@ limitations under the License.
color: $secondary-content;
display: flex;
gap: $spacing-8;
margin-bottom: 10px;
margin-top: $spacing-24;
}

.mx_AuthBody_did-not-receive--centered {
justify-content: center;
}

.mx_AuthBody_resend-button {
Expand All @@ -168,7 +197,7 @@ limitations under the License.
color: $accent;
display: flex;
gap: $spacing-4;
padding: 4px;
padding: $spacing-4;

&:hover {
background-color: $system;
Expand Down Expand Up @@ -209,7 +238,7 @@ limitations under the License.
text-align: center;

.mx_AuthBody_paddedFooter_title {
margin-top: 16px;
margin-top: $spacing-16;
font-size: $font-15px;
line-height: $font-24px;

Expand All @@ -220,7 +249,7 @@ limitations under the License.
}

.mx_AuthBody_paddedFooter_subtitle {
margin-top: 8px;
margin-top: $spacing-8;
font-size: $font-10px;
line-height: $font-14px;
}
Expand All @@ -236,7 +265,7 @@ limitations under the License.
}

.mx_SSOButtons + .mx_AuthBody_changeFlow {
margin-top: 24px;
margin-top: $spacing-24;
}

.mx_AuthBody_spinner {
Expand Down
13 changes: 11 additions & 2 deletions res/css/views/dialogs/_VerifyEMailDialog.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ limitations under the License.

.mx_Dialog {
color: $primary-content;
font-size: 14px;
padding: 16px;
font-size: $font-14px;
padding: $spacing-24 $spacing-24 $spacing-16;
text-align: center;
width: 485px;

Expand All @@ -34,5 +34,14 @@ limitations under the License.
color: $secondary-content;
line-height: 20px;
}

.mx_AuthBody_did-not-receive {
justify-content: center;
margin-bottom: $spacing-8;
}

.mx_Dialog_cancelButton {
right: 10px;
}
}
}
12 changes: 10 additions & 2 deletions src/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,11 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
<div className="mx_Dialog">
{ this.staticModal.elem }
</div>
<div className="mx_Dialog_background mx_Dialog_staticBackground" onClick={this.onBackgroundClick} />
<div
data-testid="dialog-background"
className="mx_Dialog_background mx_Dialog_staticBackground"
onClick={this.onBackgroundClick}
/>
</div>
);

Expand All @@ -368,7 +372,11 @@ export class ModalManager extends TypedEventEmitter<ModalManagerEvent, HandlerMa
<div className="mx_Dialog">
{ modal.elem }
</div>
<div className="mx_Dialog_background" onClick={this.onBackgroundClick} />
<div
data-testid="dialog-background"
className="mx_Dialog_background"
onClick={this.onBackgroundClick}
/>
</div>
);

Expand Down
20 changes: 0 additions & 20 deletions src/PasswordReset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import { createClient, IRequestTokenResponse, MatrixClient } from 'matrix-js-sdk

import { _t } from './languageHandler';

const CHECK_EMAIL_VERIFIED_POLL_INTERVAL = 2000;

/**
* Allows a user to reset their password on a homeserver.
*
Expand Down Expand Up @@ -108,24 +106,6 @@ export default class PasswordReset {
await this.checkEmailLinkClicked();
}

public async retrySetNewPassword(password: string): Promise<void> {
this.password = password;
return new Promise((resolve) => {
this.tryCheckEmailLinkClicked(resolve);
});
}

private tryCheckEmailLinkClicked(resolve: Function): void {
this.checkEmailLinkClicked()
.then(() => resolve())
.catch(() => {
window.setTimeout(
() => this.tryCheckEmailLinkClicked(resolve),
CHECK_EMAIL_VERIFIED_POLL_INTERVAL,
);
});
}

/**
* Checks if the email link has been clicked by attempting to change the password
* for the mxid linked to the email.
Expand Down
38 changes: 32 additions & 6 deletions src/components/structures/auth/ForgotPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ limitations under the License.
import React, { ReactNode } from 'react';
import { logger } from 'matrix-js-sdk/src/logger';
import { createClient } from "matrix-js-sdk/src/matrix";
import { sleep } from 'matrix-js-sdk/src/utils';

import { _t, _td } from '../../../languageHandler';
import Modal from "../../../Modal";
Expand All @@ -43,6 +44,8 @@ import Spinner from '../../views/elements/Spinner';
import { formatSeconds } from '../../../DateUtils';
import AutoDiscoveryUtils from '../../../utils/AutoDiscoveryUtils';

const emailCheckInterval = 2000;

enum Phase {
// Show email input
EnterEmail = 1,
Expand All @@ -60,7 +63,7 @@ enum Phase {

interface Props {
serverConfig: ValidatedServerConfig;
onLoginClick?: () => void;
onLoginClick: () => void;
onComplete: () => void;
}

Expand Down Expand Up @@ -277,22 +280,43 @@ export default class ForgotPassword extends React.Component<Props, State> {
{
email: this.state.email,
errorText: this.state.errorText,
onCloseClick: () => {
modal.close();
this.setState({ phase: Phase.PasswordInput });
},
onReEnterEmailClick: () => {
modal.close();
this.setState({ phase: Phase.EnterEmail });
},
onResendClick: this.sendVerificationMail,
},
"mx_VerifyEMailDialog",
false,
false,
{
// this modal cannot be dismissed except reset is done or forced
onBeforeClose: async (reason?: string) => {
return this.state.phase === Phase.Done || reason === "force";
if (reason === "backgroundClick") {
// Modal dismissed by clicking the background.
// Go one phase back.
this.setState({ phase: Phase.PasswordInput });
}

return true;
},
},
);

await this.reset.retrySetNewPassword(this.state.password);
this.phase = Phase.Done;
modal.close();
// Don't retry if the phase changed. For example when going back to email input.
while (this.state.phase === Phase.ResettingPassword) {
try {
await this.reset.setNewPassword(this.state.password);
this.setState({ phase: Phase.Done });
modal.close();
} catch (e) {
// Email not confirmed, yet. Retry after a while.
await sleep(emailCheckInterval);
}
}
}

private onSubmitForm = async (ev: React.FormEvent): Promise<void> => {
Expand Down Expand Up @@ -339,6 +363,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
homeserver={this.props.serverConfig.hsName}
loading={this.state.phase === Phase.SendingEmail}
onInputChanged={this.onInputChanged}
onLoginClick={this.props.onLoginClick!} // set by default props
onSubmitForm={this.onSubmitForm}
/>;
}
Expand Down Expand Up @@ -374,6 +399,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
return <CheckEmail
email={this.state.email}
errorText={this.state.errorText}
onReEnterEmailClick={() => this.setState({ phase: Phase.EnterEmail })}
onResendClick={this.sendVerificationMail}
onSubmitForm={this.onSubmitForm}
/>;
Expand Down
42 changes: 28 additions & 14 deletions src/components/structures/auth/forgot-password/CheckEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ErrorMessage } from "../../ErrorMessage";
interface CheckEmailProps {
email: string;
errorText: string | ReactNode | null;
onReEnterEmailClick: () => void;
onResendClick: () => Promise<boolean>;
onSubmitForm: (ev: React.FormEvent) => void;
}
Expand All @@ -37,6 +38,7 @@ interface CheckEmailProps {
export const CheckEmail: React.FC<CheckEmailProps> = ({
email,
errorText,
onReEnterEmailClick,
onSubmitForm,
onResendClick,
}) => {
Expand All @@ -50,13 +52,32 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
return <>
<EMailPromptIcon className="mx_AuthBody_emailPromptIcon--shifted" />
<h1>{ _t("Check your email to continue") }</h1>
<p>
{ _t(
"Follow the instructions sent to <b>%(email)s</b>",
{ email: email },
{ b: t => <b>{ t }</b> },
) }
</p>
<div className="mx_AuthBody_text">
<p>
{ _t(
"Follow the instructions sent to <b>%(email)s</b>",
{ email: email },
{ b: t => <b>{ t }</b> },
) }
</p>
<div className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{ _t("Wrong email address?") }</span>
<AccessibleButton
className="mx_AuthBody_resend-button"
kind="link"
onClick={onReEnterEmailClick}
>
{ _t("Re-enter email address") }
</AccessibleButton>
</div>
</div>
{ errorText && <ErrorMessage message={errorText} /> }
<input
onClick={onSubmitForm}
type="button"
className="mx_Login_submit"
value={_t("Next")}
/>
<div className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{ _t("Did not receive it?") }</span>
<AccessibleButton
Expand All @@ -73,12 +94,5 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
/>
</AccessibleButton>
</div>
{ errorText && <ErrorMessage message={errorText} /> }
<input
onClick={onSubmitForm}
type="button"
className="mx_Login_submit"
value={_t("Next")}
/>
</>;
};
12 changes: 12 additions & 0 deletions src/components/structures/auth/forgot-password/EnterEmail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import EmailField from "../../../views/auth/EmailField";
import { ErrorMessage } from "../../ErrorMessage";
import Spinner from "../../../views/elements/Spinner";
import Field from "../../../views/elements/Field";
import AccessibleButton from "../../../views/elements/AccessibleButton";

interface EnterEmailProps {
email: string;
errorText: string | ReactNode | null;
homeserver: string;
loading: boolean;
onInputChanged: (stateKey: string, ev: React.FormEvent<HTMLInputElement>) => void;
onLoginClick: () => void;
onSubmitForm: (ev: React.FormEvent) => void;
}

Expand All @@ -41,6 +43,7 @@ export const EnterEmail: React.FC<EnterEmailProps> = ({
homeserver,
loading,
onInputChanged,
onLoginClick,
onSubmitForm,
}) => {
const submitButtonChild = loading
Expand Down Expand Up @@ -92,6 +95,15 @@ export const EnterEmail: React.FC<EnterEmailProps> = ({
>
{ submitButtonChild }
</button>
<div className="mx_AuthBody_button-container">
<AccessibleButton
className="mx_AuthBody_sign-in-instead-button"
element="button"
kind="link"
onClick={onLoginClick}>
{ _t("Sign in instead") }
</AccessibleButton>
</div>
</fieldset>
</form>
</>;
Expand Down
Loading