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

Commit

Permalink
Further password reset flow enhancements (#9662)
Browse files Browse the repository at this point in the history
  • Loading branch information
weeman1337 authored Dec 6, 2022
1 parent 82ad8d5 commit 89439d4
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 67 deletions.
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

0 comments on commit 89439d4

Please sign in to comment.