Apply prettier formatting

This commit is contained in:
Michael Weimann 2022-12-12 12:24:14 +01:00
parent 1cac306093
commit 526645c791
No known key found for this signature in database
GPG key ID: 53F535A266BB9584
1576 changed files with 65385 additions and 62478 deletions

View file

@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { _t } from '../../../languageHandler';
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
import { _t } from "../../../languageHandler";
import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStore";
import SetupEncryptionBody from "./SetupEncryptionBody";
import AccessibleButton from '../../views/elements/AccessibleButton';
import AccessibleButton from "../../views/elements/AccessibleButton";
import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
import AuthPage from "../../views/auth/AuthPage";
@ -93,7 +93,11 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
let skipButton;
if (phase === Phase.Intro || phase === Phase.ConfirmReset) {
skipButton = (
<AccessibleButton onClick={this.onSkipClick} className="mx_CompleteSecurity_skip" aria-label={_t("Skip verification for now")} />
<AccessibleButton
onClick={this.onSkipClick}
className="mx_CompleteSecurity_skip"
aria-label={_t("Skip verification for now")}
/>
);
}
@ -101,9 +105,9 @@ export default class CompleteSecurity extends React.Component<IProps, IState> {
<AuthPage>
<CompleteSecurityBody>
<h1 className="mx_CompleteSecurity_header">
{ icon }
{ title }
{ skipButton }
{icon}
{title}
{skipButton}
</h1>
<div className="mx_CompleteSecurity_body">
<SetupEncryptionBody onFinished={this.props.onFinished} />

View file

@ -14,11 +14,11 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import AuthPage from '../../views/auth/AuthPage';
import CompleteSecurityBody from '../../views/auth/CompleteSecurityBody';
import CreateCrossSigningDialog from '../../views/dialogs/security/CreateCrossSigningDialog';
import AuthPage from "../../views/auth/AuthPage";
import CompleteSecurityBody from "../../views/auth/CompleteSecurityBody";
import CreateCrossSigningDialog from "../../views/dialogs/security/CreateCrossSigningDialog";
interface IProps {
onFinished: () => void;

View file

@ -16,33 +16,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactNode } from 'react';
import { logger } from 'matrix-js-sdk/src/logger';
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 { sleep } from "matrix-js-sdk/src/utils";
import { _t, _td } from '../../../languageHandler';
import { _t, _td } from "../../../languageHandler";
import Modal from "../../../Modal";
import PasswordReset from "../../../PasswordReset";
import AuthPage from "../../views/auth/AuthPage";
import PassphraseField from '../../views/auth/PassphraseField';
import { PASSWORD_MIN_SCORE } from '../../views/auth/RegistrationForm';
import PassphraseField from "../../views/auth/PassphraseField";
import { PASSWORD_MIN_SCORE } from "../../views/auth/RegistrationForm";
import AuthHeader from "../../views/auth/AuthHeader";
import AuthBody from "../../views/auth/AuthBody";
import PassphraseConfirmField from "../../views/auth/PassphraseConfirmField";
import StyledCheckbox from '../../views/elements/StyledCheckbox';
import { ValidatedServerConfig } from '../../../utils/ValidatedServerConfig';
import StyledCheckbox from "../../views/elements/StyledCheckbox";
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
import { Icon as LockIcon } from "../../../../res/img/element-icons/lock.svg";
import QuestionDialog from '../../views/dialogs/QuestionDialog';
import { EnterEmail } from './forgot-password/EnterEmail';
import { CheckEmail } from './forgot-password/CheckEmail';
import Field from '../../views/elements/Field';
import { ErrorMessage } from '../ErrorMessage';
import QuestionDialog from "../../views/dialogs/QuestionDialog";
import { EnterEmail } from "./forgot-password/EnterEmail";
import { CheckEmail } from "./forgot-password/CheckEmail";
import Field from "../../views/elements/Field";
import { ErrorMessage } from "../ErrorMessage";
import { Icon as CheckboxIcon } from "../../../../res/img/element-icons/Checkbox.svg";
import { VerifyEmailModal } from './forgot-password/VerifyEmailModal';
import Spinner from '../../views/elements/Spinner';
import { formatSeconds } from '../../../DateUtils';
import AutoDiscoveryUtils from '../../../utils/AutoDiscoveryUtils';
import { VerifyEmailModal } from "./forgot-password/VerifyEmailModal";
import Spinner from "../../views/elements/Spinner";
import { formatSeconds } from "../../../DateUtils";
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
const emailCheckInterval = 2000;
@ -115,7 +115,8 @@ export default class ForgotPassword extends React.Component<Props, State> {
}
public componentDidUpdate(prevProps: Readonly<Props>) {
if (prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
if (
prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
) {
// Do a liveliness check on the new URLs
@ -128,19 +129,16 @@ export default class ForgotPassword extends React.Component<Props, State> {
private async checkServerLiveliness(serverConfig: ValidatedServerConfig): Promise<void> {
try {
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(
serverConfig.hsUrl,
serverConfig.isUrl,
);
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(serverConfig.hsUrl, serverConfig.isUrl);
this.setState({
serverIsAlive: true,
});
} catch (e: any) {
const {
serverIsAlive,
serverDeadError,
} = AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password");
const { serverIsAlive, serverDeadError } = AutoDiscoveryUtils.authComponentStateForError(
e,
"forgot_password",
);
this.setState({
serverIsAlive,
errorText: serverDeadError,
@ -190,12 +188,9 @@ export default class ForgotPassword extends React.Component<Props, State> {
const errorText = isNaN(retryAfterMs)
? _t("Too many attempts in a short time. Wait some time before trying again.")
: _t(
"Too many attempts in a short time. Retry after %(timeout)s.",
{
timeout: formatSeconds(retryAfterMs / 1000),
},
);
: _t("Too many attempts in a short time. Retry after %(timeout)s.", {
timeout: formatSeconds(retryAfterMs / 1000),
});
this.setState({
errorText,
@ -205,8 +200,10 @@ export default class ForgotPassword extends React.Component<Props, State> {
if (err?.name === "ConnectionError") {
this.setState({
errorText: _t("Cannot reach homeserver") + ": "
+ _t("Ensure you have a stable internet connection, or get in touch with the server admin"),
errorText:
_t("Cannot reach homeserver") +
": " +
_t("Ensure you have a stable internet connection, or get in touch with the server admin"),
});
return;
}
@ -227,10 +224,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
}
private async verifyFieldsBeforeSubmit(): Promise<boolean> {
const fieldIdsInDisplayOrder = [
this.fieldPassword,
this.fieldPasswordConfirm,
];
const fieldIdsInDisplayOrder = [this.fieldPassword, this.fieldPasswordConfirm];
const invalidFields: Field[] = [];
@ -256,7 +250,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
}
private async onPhasePasswordInputSubmit(): Promise<void> {
if (!await this.verifyFieldsBeforeSubmit()) return;
if (!(await this.verifyFieldsBeforeSubmit())) return;
if (this.state.logoutDevices) {
const logoutDevicesConfirmation = await this.renderConfirmLogoutDevicesDialog();
@ -357,124 +351,137 @@ export default class ForgotPassword extends React.Component<Props, State> {
};
renderEnterEmail(): JSX.Element {
return <EnterEmail
email={this.state.email}
errorText={this.state.errorText}
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}
/>;
return (
<EnterEmail
email={this.state.email}
errorText={this.state.errorText}
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}
/>
);
}
async renderConfirmLogoutDevicesDialog(): Promise<boolean> {
const { finished } = Modal.createDialog<[boolean]>(QuestionDialog, {
title: _t('Warning!'),
description:
title: _t("Warning!"),
description: (
<div>
<p>{ !this.state.serverSupportsControlOfDevicesLogout ?
_t(
"Resetting your password on this homeserver will cause all of your devices to be " +
"signed out. This will delete the message encryption keys stored on them, " +
"making encrypted chat history unreadable.",
) :
_t(
"Signing out your devices will delete the message encryption keys stored on them, " +
"making encrypted chat history unreadable.",
)
}</p>
<p>{ _t(
"If you want to retain access to your chat history in encrypted rooms, set up Key Backup " +
"or export your message keys from one of your other devices before proceeding.",
) }</p>
</div>,
button: _t('Continue'),
<p>
{!this.state.serverSupportsControlOfDevicesLogout
? _t(
"Resetting your password on this homeserver will cause all of your devices to be " +
"signed out. This will delete the message encryption keys stored on them, " +
"making encrypted chat history unreadable.",
)
: _t(
"Signing out your devices will delete the message encryption keys stored on them, " +
"making encrypted chat history unreadable.",
)}
</p>
<p>
{_t(
"If you want to retain access to your chat history in encrypted rooms, set up Key Backup " +
"or export your message keys from one of your other devices before proceeding.",
)}
</p>
</div>
),
button: _t("Continue"),
});
const [confirmed] = await finished;
return confirmed;
}
renderCheckEmail(): JSX.Element {
return <CheckEmail
email={this.state.email}
errorText={this.state.errorText}
onReEnterEmailClick={() => this.setState({ phase: Phase.EnterEmail })}
onResendClick={this.sendVerificationMail}
onSubmitForm={this.onSubmitForm}
/>;
return (
<CheckEmail
email={this.state.email}
errorText={this.state.errorText}
onReEnterEmailClick={() => this.setState({ phase: Phase.EnterEmail })}
onResendClick={this.sendVerificationMail}
onSubmitForm={this.onSubmitForm}
/>
);
}
renderSetPassword(): JSX.Element {
const submitButtonChild = this.state.phase === Phase.ResettingPassword
? <Spinner w={16} h={16} />
: _t("Reset password");
const submitButtonChild =
this.state.phase === Phase.ResettingPassword ? <Spinner w={16} h={16} /> : _t("Reset password");
return <>
<LockIcon className="mx_AuthBody_lockIcon" />
<h1>{ _t("Reset your password") }</h1>
<form onSubmit={this.onSubmitForm}>
<fieldset disabled={this.state.phase === Phase.ResettingPassword}>
<div className="mx_AuthBody_fieldRow">
<PassphraseField
name="reset_password"
type="password"
label={_td("New Password")}
value={this.state.password}
minScore={PASSWORD_MIN_SCORE}
fieldRef={field => this.fieldPassword = field}
onChange={this.onInputChanged.bind(this, "password")}
autoComplete="new-password"
/>
<PassphraseConfirmField
name="reset_password_confirm"
label={_td("Confirm new password")}
labelRequired={_td("A new password must be entered.")}
labelInvalid={_td("New passwords must match each other.")}
value={this.state.password2}
password={this.state.password}
fieldRef={field => this.fieldPasswordConfirm = field}
onChange={this.onInputChanged.bind(this, "password2")}
autoComplete="new-password"
/>
</div>
{ this.state.serverSupportsControlOfDevicesLogout ?
return (
<>
<LockIcon className="mx_AuthBody_lockIcon" />
<h1>{_t("Reset your password")}</h1>
<form onSubmit={this.onSubmitForm}>
<fieldset disabled={this.state.phase === Phase.ResettingPassword}>
<div className="mx_AuthBody_fieldRow">
<StyledCheckbox onChange={() => this.setState({ logoutDevices: !this.state.logoutDevices })} checked={this.state.logoutDevices}>
{ _t("Sign out of all devices") }
</StyledCheckbox>
</div> : null
}
{ this.state.errorText && <ErrorMessage message={this.state.errorText} /> }
<button
type="submit"
className="mx_Login_submit"
>
{ submitButtonChild }
</button>
</fieldset>
</form>
</>;
<PassphraseField
name="reset_password"
type="password"
label={_td("New Password")}
value={this.state.password}
minScore={PASSWORD_MIN_SCORE}
fieldRef={(field) => (this.fieldPassword = field)}
onChange={this.onInputChanged.bind(this, "password")}
autoComplete="new-password"
/>
<PassphraseConfirmField
name="reset_password_confirm"
label={_td("Confirm new password")}
labelRequired={_td("A new password must be entered.")}
labelInvalid={_td("New passwords must match each other.")}
value={this.state.password2}
password={this.state.password}
fieldRef={(field) => (this.fieldPasswordConfirm = field)}
onChange={this.onInputChanged.bind(this, "password2")}
autoComplete="new-password"
/>
</div>
{this.state.serverSupportsControlOfDevicesLogout ? (
<div className="mx_AuthBody_fieldRow">
<StyledCheckbox
onChange={() => this.setState({ logoutDevices: !this.state.logoutDevices })}
checked={this.state.logoutDevices}
>
{_t("Sign out of all devices")}
</StyledCheckbox>
</div>
) : null}
{this.state.errorText && <ErrorMessage message={this.state.errorText} />}
<button type="submit" className="mx_Login_submit">
{submitButtonChild}
</button>
</fieldset>
</form>
</>
);
}
renderDone() {
return <>
<CheckboxIcon className="mx_Icon mx_Icon_32 mx_Icon_accent" />
<h1>{ _t("Your password has been reset.") }</h1>
{ this.state.logoutDevices ?
<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>
: null
}
<input
className="mx_Login_submit"
type="button"
onClick={this.props.onComplete}
value={_t('Return to login screen')} />
</>;
return (
<>
<CheckboxIcon className="mx_Icon mx_Icon_32 mx_Icon_accent" />
<h1>{_t("Your password has been reset.")}</h1>
{this.state.logoutDevices ? (
<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>
) : null}
<input
className="mx_Login_submit"
type="button"
onClick={this.props.onComplete}
value={_t("Return to login screen")}
/>
</>
);
}
render() {
@ -507,9 +514,7 @@ export default class ForgotPassword extends React.Component<Props, State> {
return (
<AuthPage>
<AuthHeader />
<AuthBody className="mx_AuthBody_forgot-password">
{ resetPasswordJsx }
</AuthBody>
<AuthBody className="mx_AuthBody_forgot-password">{resetPasswordJsx}</AuthBody>
</AuthPage>
);
}

View file

@ -14,19 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactNode } from 'react';
import React, { ReactNode } from "react";
import { ConnectionError, MatrixError } from "matrix-js-sdk/src/http-api";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { ISSOFlow, LoginFlow } from "matrix-js-sdk/src/@types/auth";
import { _t, _td } from '../../../languageHandler';
import Login from '../../../Login';
import SdkConfig from '../../../SdkConfig';
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
import { _t, _td } from "../../../languageHandler";
import Login from "../../../Login";
import SdkConfig from "../../../SdkConfig";
import { messageForResourceLimitError } from "../../../utils/ErrorUtils";
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
import AuthPage from "../../views/auth/AuthPage";
import PlatformPeg from '../../../PlatformPeg';
import PlatformPeg from "../../../PlatformPeg";
import SettingsStore from "../../../settings/SettingsStore";
import { UIFeature } from "../../../settings/UIFeature";
import { IMatrixClientCreds } from "../../../MatrixClientPeg";
@ -37,8 +37,8 @@ import SSOButtons from "../../views/elements/SSOButtons";
import ServerPicker from "../../views/elements/ServerPicker";
import AuthBody from "../../views/auth/AuthBody";
import AuthHeader from "../../views/auth/AuthHeader";
import AccessibleButton from '../../views/elements/AccessibleButton';
import { ValidatedServerConfig } from '../../../utils/ValidatedServerConfig';
import AccessibleButton from "../../views/elements/AccessibleButton";
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
// These are used in several places, and come from the js-sdk's autodiscovery
// stuff. We define them here so that they'll be picked up by i18n.
@ -122,7 +122,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
flows: null,
username: props.defaultUsername? props.defaultUsername: '',
username: props.defaultUsername ? props.defaultUsername : "",
phoneCountry: null,
phoneNumber: "",
@ -134,13 +134,13 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
// map from login step type to a function which will render a control
// letting you do that login type
this.stepRendererMap = {
'm.login.password': this.renderPasswordStep,
"m.login.password": this.renderPasswordStep,
// CAS and SSO are the same thing, modulo the url we link to
// eslint-disable-next-line @typescript-eslint/naming-convention
'm.login.cas': () => this.renderSsoStep("cas"),
"m.login.cas": () => this.renderSsoStep("cas"),
// eslint-disable-next-line @typescript-eslint/naming-convention
'm.login.sso': () => this.renderSsoStep("sso"),
"m.login.sso": () => this.renderSsoStep("sso"),
};
}
@ -153,7 +153,8 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
}
public componentDidUpdate(prevProps) {
if (prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
if (
prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
) {
// Ensure that we end up actually logging in to the right place
@ -197,91 +198,77 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
loginIncorrect: false,
});
this.loginLogic.loginViaPassword(
username, phoneCountry, phoneNumber, password,
).then((data) => {
this.setState({ serverIsAlive: true }); // it must be, we logged in.
this.props.onLoggedIn(data, password);
}, (error) => {
if (this.unmounted) {
return;
}
let errorText;
this.loginLogic.loginViaPassword(username, phoneCountry, phoneNumber, password).then(
(data) => {
this.setState({ serverIsAlive: true }); // it must be, we logged in.
this.props.onLoggedIn(data, password);
},
(error) => {
if (this.unmounted) {
return;
}
let errorText;
// Some error strings only apply for logging in
const usingEmail = username.indexOf("@") > 0;
if (error.httpStatus === 400 && usingEmail) {
errorText = _t('This homeserver does not support login using email address.');
} else if (error.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
const errorTop = messageForResourceLimitError(
error.data.limit_type,
error.data.admin_contact,
{
'monthly_active_user': _td(
"This homeserver has hit its Monthly Active User limit.",
),
'hs_blocked': _td(
"This homeserver has been blocked by its administrator.",
),
'': _td(
"This homeserver has exceeded one of its resource limits.",
),
},
);
const errorDetail = messageForResourceLimitError(
error.data.limit_type,
error.data.admin_contact,
{
'': _td("Please <a>contact your service administrator</a> to continue using this service."),
},
);
errorText = (
<div>
<div>{ errorTop }</div>
<div className="mx_Login_smallError">{ errorDetail }</div>
</div>
);
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
if (error.errcode === 'M_USER_DEACTIVATED') {
errorText = _t('This account has been deactivated.');
} else if (SdkConfig.get("disable_custom_urls")) {
// Some error strings only apply for logging in
const usingEmail = username.indexOf("@") > 0;
if (error.httpStatus === 400 && usingEmail) {
errorText = _t("This homeserver does not support login using email address.");
} else if (error.errcode === "M_RESOURCE_LIMIT_EXCEEDED") {
const errorTop = messageForResourceLimitError(error.data.limit_type, error.data.admin_contact, {
"monthly_active_user": _td("This homeserver has hit its Monthly Active User limit."),
"hs_blocked": _td("This homeserver has been blocked by its administrator."),
"": _td("This homeserver has exceeded one of its resource limits."),
});
const errorDetail = messageForResourceLimitError(error.data.limit_type, error.data.admin_contact, {
"": _td("Please <a>contact your service administrator</a> to continue using this service."),
});
errorText = (
<div>
<div>{ _t('Incorrect username and/or password.') }</div>
<div className="mx_Login_smallError">
{ _t(
'Please note you are logging into the %(hs)s server, not matrix.org.',
{ hs: this.props.serverConfig.hsName },
) }
</div>
<div>{errorTop}</div>
<div className="mx_Login_smallError">{errorDetail}</div>
</div>
);
} else if (error.httpStatus === 401 || error.httpStatus === 403) {
if (error.errcode === "M_USER_DEACTIVATED") {
errorText = _t("This account has been deactivated.");
} else if (SdkConfig.get("disable_custom_urls")) {
errorText = (
<div>
<div>{_t("Incorrect username and/or password.")}</div>
<div className="mx_Login_smallError">
{_t("Please note you are logging into the %(hs)s server, not matrix.org.", {
hs: this.props.serverConfig.hsName,
})}
</div>
</div>
);
} else {
errorText = _t("Incorrect username and/or password.");
}
} else {
errorText = _t('Incorrect username and/or password.');
// other errors, not specific to doing a password login
errorText = this.errorTextFromError(error);
}
} else {
// other errors, not specific to doing a password login
errorText = this.errorTextFromError(error);
}
this.setState({
busy: false,
busyLoggingIn: false,
errorText: errorText,
// 401 would be the sensible status code for 'incorrect password'
// but the login API gives a 403 https://matrix.org/jira/browse/SYN-744
// mentions this (although the bug is for UI auth which is not this)
// We treat both as an incorrect password
loginIncorrect: error.httpStatus === 401 || error.httpStatus === 403,
});
});
this.setState({
busy: false,
busyLoggingIn: false,
errorText: errorText,
// 401 would be the sensible status code for 'incorrect password'
// but the login API gives a 403 https://matrix.org/jira/browse/SYN-744
// mentions this (although the bug is for UI auth which is not this)
// We treat both as an incorrect password
loginIncorrect: error.httpStatus === 401 || error.httpStatus === 403,
});
},
);
};
onUsernameChanged = username => {
onUsernameChanged = (username) => {
this.setState({ username: username });
};
onUsernameBlur = async username => {
onUsernameBlur = async (username) => {
const doWellknownLookup = username[0] === "@";
this.setState({
username: username,
@ -290,7 +277,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
canTryLogin: true,
});
if (doWellknownLookup) {
const serverName = username.split(':').slice(1).join(':');
const serverName = username.split(":").slice(1).join(":");
try {
const result = await AutoDiscoveryUtils.validateServerName(serverName);
this.props.onServerConfigChange(result);
@ -328,34 +315,37 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
}
};
onPhoneCountryChanged = phoneCountry => {
onPhoneCountryChanged = (phoneCountry) => {
this.setState({ phoneCountry: phoneCountry });
};
onPhoneNumberChanged = phoneNumber => {
onPhoneNumberChanged = (phoneNumber) => {
this.setState({
phoneNumber: phoneNumber,
});
};
onRegisterClick = ev => {
onRegisterClick = (ev) => {
ev.preventDefault();
ev.stopPropagation();
this.props.onRegisterClick();
};
onTryRegisterClick = ev => {
const hasPasswordFlow = this.state.flows?.find(flow => flow.type === "m.login.password");
const ssoFlow = this.state.flows?.find(flow => flow.type === "m.login.sso" || flow.type === "m.login.cas");
onTryRegisterClick = (ev) => {
const hasPasswordFlow = this.state.flows?.find((flow) => flow.type === "m.login.password");
const ssoFlow = this.state.flows?.find((flow) => flow.type === "m.login.sso" || flow.type === "m.login.cas");
// If has no password flow but an SSO flow guess that the user wants to register with SSO.
// TODO: instead hide the Register button if registration is disabled by checking with the server,
// has no specific errCode currently and uses M_FORBIDDEN.
if (ssoFlow && !hasPasswordFlow) {
ev.preventDefault();
ev.stopPropagation();
const ssoKind = ssoFlow.type === 'm.login.sso' ? 'sso' : 'cas';
PlatformPeg.get().startSingleSignOn(this.loginLogic.createTemporaryClient(), ssoKind,
this.props.fragmentAfterLogin);
const ssoKind = ssoFlow.type === "m.login.sso" ? "sso" : "cas";
PlatformPeg.get().startSingleSignOn(
this.loginLogic.createTemporaryClient(),
ssoKind,
this.props.fragmentAfterLogin,
);
} else {
// Don't intercept - just go through to the register page
this.onRegisterClick(ev);
@ -364,9 +354,10 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
private async initLoginLogic({ hsUrl, isUrl }: ValidatedServerConfig) {
let isDefaultServer = false;
if (this.props.serverConfig.isDefault
&& hsUrl === this.props.serverConfig.hsUrl
&& isUrl === this.props.serverConfig.isUrl
if (
this.props.serverConfig.isDefault &&
hsUrl === this.props.serverConfig.hsUrl &&
isUrl === this.props.serverConfig.isUrl
) {
isDefaultServer = true;
}
@ -385,8 +376,7 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
// Do a quick liveliness check on the URLs
try {
const { warning } =
await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
const { warning } = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl);
if (warning) {
this.setState({
...AutoDiscoveryUtils.authComponentStateForError(warning),
@ -405,32 +395,40 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
});
}
loginLogic.getFlows().then((flows) => {
// look for a flow where we understand all of the steps.
const supportedFlows = flows.filter(this.isSupportedFlow);
loginLogic
.getFlows()
.then(
(flows) => {
// look for a flow where we understand all of the steps.
const supportedFlows = flows.filter(this.isSupportedFlow);
if (supportedFlows.length > 0) {
if (supportedFlows.length > 0) {
this.setState({
flows: supportedFlows,
});
return;
}
// we got to the end of the list without finding a suitable flow.
this.setState({
errorText: _t(
"This homeserver doesn't offer any login flows which are supported by this client.",
),
});
},
(err) => {
this.setState({
errorText: this.errorTextFromError(err),
loginIncorrect: false,
canTryLogin: false,
});
},
)
.finally(() => {
this.setState({
flows: supportedFlows,
busy: false,
});
return;
}
// we got to the end of the list without finding a suitable flow.
this.setState({
errorText: _t("This homeserver doesn't offer any login flows which are supported by this client."),
});
}, (err) => {
this.setState({
errorText: this.errorTextFromError(err),
loginIncorrect: false,
canTryLogin: false,
});
}).finally(() => {
this.setState({
busy: false,
});
});
}
private isSupportedFlow = (flow: LoginFlow): boolean => {
@ -449,41 +447,55 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
errCode = "HTTP " + err.httpStatus;
}
let errorText: ReactNode = _t("There was a problem communicating with the homeserver, " +
"please try again later.") + (errCode ? " (" + errCode + ")" : "");
let errorText: ReactNode =
_t("There was a problem communicating with the homeserver, " + "please try again later.") +
(errCode ? " (" + errCode + ")" : "");
if (err instanceof ConnectionError) {
if (window.location.protocol === 'https:' &&
(this.props.serverConfig.hsUrl.startsWith("http:") ||
!this.props.serverConfig.hsUrl.startsWith("http"))
if (
window.location.protocol === "https:" &&
(this.props.serverConfig.hsUrl.startsWith("http:") || !this.props.serverConfig.hsUrl.startsWith("http"))
) {
errorText = <span>
{ _t("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
"Either use HTTPS or <a>enable unsafe scripts</a>.", {},
{
'a': (sub) => {
return <a
target="_blank"
rel="noreferrer noopener"
href="https://www.google.com/search?&q=enable%20unsafe%20scripts"
>
{ sub }
</a>;
},
}) }
</span>;
errorText = (
<span>
{_t(
"Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
"Either use HTTPS or <a>enable unsafe scripts</a>.",
{},
{
a: (sub) => {
return (
<a
target="_blank"
rel="noreferrer noopener"
href="https://www.google.com/search?&q=enable%20unsafe%20scripts"
>
{sub}
</a>
);
},
},
)}
</span>
);
} else {
errorText = <span>
{ _t("Can't connect to homeserver - please check your connectivity, ensure your " +
"<a>homeserver's SSL certificate</a> is trusted, and that a browser extension " +
"is not blocking requests.", {},
{
'a': (sub) =>
<a target="_blank" rel="noreferrer noopener" href={this.props.serverConfig.hsUrl}>
{ sub }
</a>,
}) }
</span>;
errorText = (
<span>
{_t(
"Can't connect to homeserver - please check your connectivity, ensure your " +
"<a>homeserver's SSL certificate</a> is trusted, and that a browser extension " +
"is not blocking requests.",
{},
{
a: (sub) => (
<a target="_blank" rel="noreferrer noopener" href={this.props.serverConfig.hsUrl}>
{sub}
</a>
),
},
)}
</span>
);
}
}
@ -494,18 +506,17 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
if (!this.state.flows) return null;
// this is the ideal order we want to show the flows in
const order = [
"m.login.password",
"m.login.sso",
];
const order = ["m.login.password", "m.login.sso"];
const flows = order.map(type => this.state.flows.find(flow => flow.type === type)).filter(Boolean);
return <React.Fragment>
{ flows.map(flow => {
const stepRenderer = this.stepRendererMap[flow.type];
return <React.Fragment key={flow.type}>{ stepRenderer() }</React.Fragment>;
}) }
</React.Fragment>;
const flows = order.map((type) => this.state.flows.find((flow) => flow.type === type)).filter(Boolean);
return (
<React.Fragment>
{flows.map((flow) => {
const stepRenderer = this.stepRendererMap[flow.type];
return <React.Fragment key={flow.type}>{stepRenderer()}</React.Fragment>;
})}
</React.Fragment>
);
}
private renderPasswordStep = () => {
@ -528,8 +539,8 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
);
};
private renderSsoStep = loginType => {
const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType) as ISSOFlow;
private renderSsoStep = (loginType) => {
const flow = this.state.flows.find((flow) => flow.type === "m.login." + loginType) as ISSOFlow;
return (
<SSOButtons
@ -537,60 +548,65 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
flow={flow}
loginType={loginType}
fragmentAfterLogin={this.props.fragmentAfterLogin}
primary={!this.state.flows.find(flow => flow.type === "m.login.password")}
primary={!this.state.flows.find((flow) => flow.type === "m.login.password")}
/>
);
};
render() {
const loader = this.isBusy() && !this.state.busyLoggingIn ?
<div className="mx_Login_loader"><Spinner /></div> : null;
const loader =
this.isBusy() && !this.state.busyLoggingIn ? (
<div className="mx_Login_loader">
<Spinner />
</div>
) : null;
const errorText = this.state.errorText;
let errorTextSection;
if (errorText) {
errorTextSection = (
<div className="mx_Login_error">
{ errorText }
</div>
);
errorTextSection = <div className="mx_Login_error">{errorText}</div>;
}
let serverDeadSection;
if (!this.state.serverIsAlive) {
const classes = classNames({
"mx_Login_error": true,
"mx_Login_serverError": true,
"mx_Login_serverErrorNonFatal": !this.state.serverErrorIsFatal,
mx_Login_error: true,
mx_Login_serverError: true,
mx_Login_serverErrorNonFatal: !this.state.serverErrorIsFatal,
});
serverDeadSection = (
<div className={classes}>
{ this.state.serverDeadError }
</div>
);
serverDeadSection = <div className={classes}>{this.state.serverDeadError}</div>;
}
let footer;
if (this.props.isSyncing || this.state.busyLoggingIn) {
footer = <div className="mx_AuthBody_paddedFooter">
<div className="mx_AuthBody_paddedFooter_title">
<InlineSpinner w={20} h={20} />
{ this.props.isSyncing ? _t("Syncing...") : _t("Signing In...") }
footer = (
<div className="mx_AuthBody_paddedFooter">
<div className="mx_AuthBody_paddedFooter_title">
<InlineSpinner w={20} h={20} />
{this.props.isSyncing ? _t("Syncing...") : _t("Signing In...")}
</div>
{this.props.isSyncing && (
<div className="mx_AuthBody_paddedFooter_subtitle">
{_t("If you've joined lots of rooms, this might take a while")}
</div>
)}
</div>
{ this.props.isSyncing && <div className="mx_AuthBody_paddedFooter_subtitle">
{ _t("If you've joined lots of rooms, this might take a while") }
</div> }
</div>;
);
} else if (SettingsStore.getValue(UIFeature.Registration)) {
footer = (
<span className="mx_AuthBody_changeFlow">
{ _t("New? <a>Create account</a>", {}, {
a: sub =>
<AccessibleButton kind='link_inline' onClick={this.onTryRegisterClick}>
{ sub }
</AccessibleButton>,
}) }
{_t(
"New? <a>Create account</a>",
{},
{
a: (sub) => (
<AccessibleButton kind="link_inline" onClick={this.onTryRegisterClick}>
{sub}
</AccessibleButton>
),
},
)}
</span>
);
}
@ -600,17 +616,17 @@ export default class LoginComponent extends React.PureComponent<IProps, IState>
<AuthHeader disableLanguageSelector={this.props.isSyncing || this.state.busyLoggingIn} />
<AuthBody>
<h1>
{ _t('Sign in') }
{ loader }
{_t("Sign in")}
{loader}
</h1>
{ errorTextSection }
{ serverDeadSection }
{errorTextSection}
{serverDeadSection}
<ServerPicker
serverConfig={this.props.serverConfig}
onServerConfigChange={this.props.onServerConfigChange}
/>
{ this.renderLoginComponentForFlows() }
{ footer }
{this.renderLoginComponentForFlows()}
{footer}
</AuthBody>
</AuthPage>
);

View file

@ -14,33 +14,33 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { AuthType, createClient, IAuthData } from 'matrix-js-sdk/src/matrix';
import React, { Fragment, ReactNode } from 'react';
import { AuthType, createClient, IAuthData } from "matrix-js-sdk/src/matrix";
import React, { Fragment, ReactNode } from "react";
import { MatrixClient } from "matrix-js-sdk/src/client";
import classNames from "classnames";
import { logger } from "matrix-js-sdk/src/logger";
import { ISSOFlow } from "matrix-js-sdk/src/@types/auth";
import { _t, _td } from '../../../languageHandler';
import { messageForResourceLimitError } from '../../../utils/ErrorUtils';
import { _t, _td } from "../../../languageHandler";
import { messageForResourceLimitError } from "../../../utils/ErrorUtils";
import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils";
import * as Lifecycle from '../../../Lifecycle';
import * as Lifecycle from "../../../Lifecycle";
import { IMatrixClientCreds, MatrixClientPeg } from "../../../MatrixClientPeg";
import AuthPage from "../../views/auth/AuthPage";
import Login from "../../../Login";
import dis from "../../../dispatcher/dispatcher";
import SSOButtons from "../../views/elements/SSOButtons";
import ServerPicker from '../../views/elements/ServerPicker';
import RegistrationForm from '../../views/auth/RegistrationForm';
import AccessibleButton from '../../views/elements/AccessibleButton';
import ServerPicker from "../../views/elements/ServerPicker";
import RegistrationForm from "../../views/auth/RegistrationForm";
import AccessibleButton from "../../views/elements/AccessibleButton";
import AuthBody from "../../views/auth/AuthBody";
import AuthHeader from "../../views/auth/AuthHeader";
import InteractiveAuth, { InteractiveAuthCallback } from "../InteractiveAuth";
import Spinner from "../../views/elements/Spinner";
import { AuthHeaderDisplay } from './header/AuthHeaderDisplay';
import { AuthHeaderProvider } from './header/AuthHeaderProvider';
import SettingsStore from '../../../settings/SettingsStore';
import { ValidatedServerConfig } from '../../../utils/ValidatedServerConfig';
import { AuthHeaderDisplay } from "./header/AuthHeaderDisplay";
import { AuthHeaderProvider } from "./header/AuthHeaderProvider";
import SettingsStore from "../../../settings/SettingsStore";
import { ValidatedServerConfig } from "../../../utils/ValidatedServerConfig";
const debuglog = (...args: any[]) => {
if (SettingsStore.getValue("debug_registration")) {
@ -167,7 +167,8 @@ export default class Registration extends React.Component<IProps, IState> {
};
public componentDidUpdate(prevProps) {
if (prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
if (
prevProps.serverConfig.hsUrl !== this.props.serverConfig.hsUrl ||
prevProps.serverConfig.isUrl !== this.props.serverConfig.isUrl
) {
this.replaceClient(this.props.serverConfig);
@ -218,7 +219,7 @@ export default class Registration extends React.Component<IProps, IState> {
try {
const loginFlows = await this.loginLogic.getFlows();
if (serverConfig !== this.latestServerConfig) return; // discard, serverConfig changed from under us
ssoFlow = loginFlows.find(f => f.type === "m.login.sso" || f.type === "m.login.cas") as ISSOFlow;
ssoFlow = loginFlows.find((f) => f.type === "m.login.sso" || f.type === "m.login.cas") as ISSOFlow;
} catch (e) {
if (serverConfig !== this.latestServerConfig) return; // discard, serverConfig changed from under us
logger.error("Failed to get login flows to check for SSO support", e);
@ -253,7 +254,7 @@ export default class Registration extends React.Component<IProps, IState> {
// the user off to the login page to figure their account out.
if (ssoFlow) {
// Redirect to login page - server probably expects SSO only
dis.dispatch({ action: 'start_login' });
dis.dispatch({ action: "start_login" });
} else {
this.setState({
serverErrorIsFatal: true, // fatal because user cannot continue on this server
@ -301,34 +302,32 @@ export default class Registration extends React.Component<IProps, IState> {
if (!success) {
let errorText: ReactNode = response.message || response.toString();
// can we give a better error message?
if (response.errcode === 'M_RESOURCE_LIMIT_EXCEEDED') {
const errorTop = messageForResourceLimitError(
response.data.limit_type,
response.data.admin_contact,
{
'monthly_active_user': _td("This homeserver has hit its Monthly Active User limit."),
'hs_blocked': _td("This homeserver has been blocked by its administrator."),
'': _td("This homeserver has exceeded one of its resource limits."),
},
);
if (response.errcode === "M_RESOURCE_LIMIT_EXCEEDED") {
const errorTop = messageForResourceLimitError(response.data.limit_type, response.data.admin_contact, {
"monthly_active_user": _td("This homeserver has hit its Monthly Active User limit."),
"hs_blocked": _td("This homeserver has been blocked by its administrator."),
"": _td("This homeserver has exceeded one of its resource limits."),
});
const errorDetail = messageForResourceLimitError(
response.data.limit_type,
response.data.admin_contact,
{
'': _td("Please <a>contact your service administrator</a> to continue using this service."),
"": _td("Please <a>contact your service administrator</a> to continue using this service."),
},
);
errorText = <div>
<p>{ errorTop }</p>
<p>{ errorDetail }</p>
</div>;
errorText = (
<div>
<p>{errorTop}</p>
<p>{errorDetail}</p>
</div>
);
} else if (response.required_stages && response.required_stages.includes(AuthType.Msisdn)) {
let msisdnAvailable = false;
for (const flow of response.available_flows) {
msisdnAvailable = msisdnAvailable || flow.stages.includes(AuthType.Msisdn);
}
if (!msisdnAvailable) {
errorText = _t('This server does not support authentication with a phone number.');
errorText = _t("This server does not support authentication with a phone number.");
}
} else if (response.errcode === "M_USER_IN_USE") {
errorText = _t("Someone already has that username, please try another.");
@ -362,9 +361,7 @@ export default class Registration extends React.Component<IProps, IState> {
// the user had a separate guest session they didn't actually mean to replace.
const [sessionOwner, sessionIsGuest] = await Lifecycle.getStoredSessionOwner();
if (sessionOwner && !sessionIsGuest && sessionOwner !== response.user_id) {
logger.log(
`Found a session for ${sessionOwner} but ${response.user_id} has just registered.`,
);
logger.log(`Found a session for ${sessionOwner} but ${response.user_id} has just registered.`);
newState.differentLoggedInUserId = sessionOwner;
}
@ -387,13 +384,16 @@ export default class Registration extends React.Component<IProps, IState> {
if (!hasEmail && hasAccessToken && !newState.differentLoggedInUserId) {
// we'll only try logging in if we either have no email to verify at all or we're the client that verified
// the email, not the client that started the registration flow
await this.props.onLoggedIn({
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
}, this.state.formVals.password);
await this.props.onLoggedIn(
{
userId: response.user_id,
deviceId: response.device_id,
homeserverUrl: this.state.matrixClient.getHomeserverUrl(),
identityServerUrl: this.state.matrixClient.getIdentityServerUrl(),
accessToken: response.access_token,
},
this.state.formVals.password,
);
this.setupPushers();
} else {
@ -409,31 +409,37 @@ export default class Registration extends React.Component<IProps, IState> {
return Promise.resolve();
}
const matrixClient = MatrixClientPeg.get();
return matrixClient.getPushers().then((resp) => {
const pushers = resp.pushers;
for (let i = 0; i < pushers.length; ++i) {
if (pushers[i].kind === 'email') {
const emailPusher = pushers[i];
emailPusher.data = { brand: this.props.brand };
matrixClient.setPusher(emailPusher).then(() => {
logger.log("Set email branding to " + this.props.brand);
}, (error) => {
logger.error("Couldn't set email branding: " + error);
});
return matrixClient.getPushers().then(
(resp) => {
const pushers = resp.pushers;
for (let i = 0; i < pushers.length; ++i) {
if (pushers[i].kind === "email") {
const emailPusher = pushers[i];
emailPusher.data = { brand: this.props.brand };
matrixClient.setPusher(emailPusher).then(
() => {
logger.log("Set email branding to " + this.props.brand);
},
(error) => {
logger.error("Couldn't set email branding: " + error);
},
);
}
}
}
}, (error) => {
logger.error("Couldn't get pushers: " + error);
});
},
(error) => {
logger.error("Couldn't get pushers: " + error);
},
);
}
private onLoginClick = ev => {
private onLoginClick = (ev) => {
ev.preventDefault();
ev.stopPropagation();
this.props.onLoginClick();
};
private onGoToFormClicked = ev => {
private onGoToFormClicked = (ev) => {
ev.preventDefault();
ev.stopPropagation();
this.replaceClient(this.props.serverConfig);
@ -469,7 +475,7 @@ export default class Registration extends React.Component<IProps, IState> {
// Links to the login page shown after registration is completed are routed through this
// which checks the user hasn't already logged in somewhere else (perhaps we should do
// this more generally?)
private onLoginClickWithCheck = async ev => {
private onLoginClickWithCheck = async (ev) => {
ev.preventDefault();
const sessionLoaded = await Lifecycle.loadSession({ ignoreGuest: true });
@ -483,23 +489,27 @@ export default class Registration extends React.Component<IProps, IState> {
private renderRegisterComponent() {
if (this.state.matrixClient && this.state.doingUIAuth) {
return <InteractiveAuth
matrixClient={this.state.matrixClient}
makeRequest={this.makeRegisterRequest}
onAuthFinished={this.onUIAuthFinished}
inputs={this.getUIAuthInputs()}
requestEmailToken={this.requestEmailToken}
sessionId={this.props.sessionId}
clientSecret={this.props.clientSecret}
emailSid={this.props.idSid}
poll={true}
/>;
return (
<InteractiveAuth
matrixClient={this.state.matrixClient}
makeRequest={this.makeRegisterRequest}
onAuthFinished={this.onUIAuthFinished}
inputs={this.getUIAuthInputs()}
requestEmailToken={this.requestEmailToken}
sessionId={this.props.sessionId}
clientSecret={this.props.clientSecret}
emailSid={this.props.idSid}
poll={true}
/>
);
} else if (!this.state.matrixClient && !this.state.busy) {
return null;
} else if (this.state.busy || !this.state.flows) {
return <div className="mx_AuthBody_spinner">
<Spinner />
</div>;
return (
<div className="mx_AuthBody_spinner">
<Spinner />
</div>
);
} else if (this.state.flows.length) {
let ssoSection;
if (this.state.ssoFlow) {
@ -508,47 +518,50 @@ export default class Registration extends React.Component<IProps, IState> {
// when there is only a single (or 0) providers we show a wide button with `Continue with X` text
if (providers.length > 1) {
// i18n: ssoButtons is a placeholder to help translators understand context
continueWithSection = <h2 className="mx_AuthBody_centered">
{ _t("Continue with %(ssoButtons)s", { ssoButtons: "" }).trim() }
</h2>;
continueWithSection = (
<h2 className="mx_AuthBody_centered">
{_t("Continue with %(ssoButtons)s", { ssoButtons: "" }).trim()}
</h2>
);
}
// i18n: ssoButtons & usernamePassword are placeholders to help translators understand context
ssoSection = <React.Fragment>
{ continueWithSection }
<SSOButtons
matrixClient={this.loginLogic.createTemporaryClient()}
flow={this.state.ssoFlow}
loginType={this.state.ssoFlow.type === "m.login.sso" ? "sso" : "cas"}
fragmentAfterLogin={this.props.fragmentAfterLogin}
/>
<h2 className="mx_AuthBody_centered">
{ _t(
"%(ssoButtons)s Or %(usernamePassword)s",
{
ssoSection = (
<React.Fragment>
{continueWithSection}
<SSOButtons
matrixClient={this.loginLogic.createTemporaryClient()}
flow={this.state.ssoFlow}
loginType={this.state.ssoFlow.type === "m.login.sso" ? "sso" : "cas"}
fragmentAfterLogin={this.props.fragmentAfterLogin}
/>
<h2 className="mx_AuthBody_centered">
{_t("%(ssoButtons)s Or %(usernamePassword)s", {
ssoButtons: "",
usernamePassword: "",
},
).trim() }
</h2>
</React.Fragment>;
}).trim()}
</h2>
</React.Fragment>
);
}
return <React.Fragment>
{ ssoSection }
<RegistrationForm
defaultUsername={this.state.formVals.username}
defaultEmail={this.state.formVals.email}
defaultPhoneCountry={this.state.formVals.phoneCountry}
defaultPhoneNumber={this.state.formVals.phoneNumber}
defaultPassword={this.state.formVals.password}
onRegisterClick={this.onFormSubmit}
flows={this.state.flows}
serverConfig={this.props.serverConfig}
canSubmit={!this.state.serverErrorIsFatal}
matrixClient={this.state.matrixClient}
/>
</React.Fragment>;
return (
<React.Fragment>
{ssoSection}
<RegistrationForm
defaultUsername={this.state.formVals.username}
defaultEmail={this.state.formVals.email}
defaultPhoneCountry={this.state.formVals.phoneCountry}
defaultPhoneNumber={this.state.formVals.phoneNumber}
defaultPassword={this.state.formVals.password}
onRegisterClick={this.onFormSubmit}
flows={this.state.flows}
serverConfig={this.props.serverConfig}
canSubmit={!this.state.serverErrorIsFatal}
matrixClient={this.state.matrixClient}
/>
</React.Fragment>
);
}
}
@ -556,118 +569,144 @@ export default class Registration extends React.Component<IProps, IState> {
let errorText;
const err = this.state.errorText;
if (err) {
errorText = <div className="mx_Login_error">{ err }</div>;
errorText = <div className="mx_Login_error">{err}</div>;
}
let serverDeadSection;
if (!this.state.serverIsAlive) {
const classes = classNames({
"mx_Login_error": true,
"mx_Login_serverError": true,
"mx_Login_serverErrorNonFatal": !this.state.serverErrorIsFatal,
mx_Login_error: true,
mx_Login_serverError: true,
mx_Login_serverErrorNonFatal: !this.state.serverErrorIsFatal,
});
serverDeadSection = (
<div className={classes}>
{ this.state.serverDeadError }
</div>
);
serverDeadSection = <div className={classes}>{this.state.serverDeadError}</div>;
}
const signIn = <span className="mx_AuthBody_changeFlow">
{ _t("Already have an account? <a>Sign in here</a>", {}, {
a: sub => <AccessibleButton kind='link_inline' onClick={this.onLoginClick}>{ sub }</AccessibleButton>,
}) }
</span>;
const signIn = (
<span className="mx_AuthBody_changeFlow">
{_t(
"Already have an account? <a>Sign in here</a>",
{},
{
a: (sub) => (
<AccessibleButton kind="link_inline" onClick={this.onLoginClick}>
{sub}
</AccessibleButton>
),
},
)}
</span>
);
// Only show the 'go back' button if you're not looking at the form
let goBack;
if (this.state.doingUIAuth) {
goBack = <AccessibleButton
kind='link'
className="mx_AuthBody_changeFlow"
onClick={this.onGoToFormClicked}
>
{ _t('Go back') }
</AccessibleButton>;
goBack = (
<AccessibleButton kind="link" className="mx_AuthBody_changeFlow" onClick={this.onGoToFormClicked}>
{_t("Go back")}
</AccessibleButton>
);
}
let body;
if (this.state.completedNoSignin) {
let regDoneText;
if (this.state.differentLoggedInUserId) {
regDoneText = <div>
<p>{ _t(
"Your new account (%(newAccountId)s) is registered, but you're already " +
"logged into a different account (%(loggedInUserId)s).", {
newAccountId: this.state.registeredUsername,
loggedInUserId: this.state.differentLoggedInUserId,
},
) }</p>
<p><AccessibleButton
kind="link_inline"
onClick={async event => {
const sessionLoaded = await this.onLoginClickWithCheck(event);
if (sessionLoaded) {
dis.dispatch({ action: "view_welcome_page" });
}
}}
>
{ _t("Continue with previous account") }
</AccessibleButton></p>
</div>;
regDoneText = (
<div>
<p>
{_t(
"Your new account (%(newAccountId)s) is registered, but you're already " +
"logged into a different account (%(loggedInUserId)s).",
{
newAccountId: this.state.registeredUsername,
loggedInUserId: this.state.differentLoggedInUserId,
},
)}
</p>
<p>
<AccessibleButton
kind="link_inline"
onClick={async (event) => {
const sessionLoaded = await this.onLoginClickWithCheck(event);
if (sessionLoaded) {
dis.dispatch({ action: "view_welcome_page" });
}
}}
>
{_t("Continue with previous account")}
</AccessibleButton>
</p>
</div>
);
} else {
// regardless of whether we're the client that started the registration or not, we should
// try our credentials anyway
regDoneText = <h2>{ _t(
"<a>Log in</a> to your new account.", {},
{
a: (sub) => <AccessibleButton
kind="link_inline"
onClick={async event => {
const sessionLoaded = await this.onLoginClickWithCheck(event);
if (sessionLoaded) {
dis.dispatch({ action: "view_home_page" });
}
}}
>{ sub }</AccessibleButton>,
},
) }</h2>;
regDoneText = (
<h2>
{_t(
"<a>Log in</a> to your new account.",
{},
{
a: (sub) => (
<AccessibleButton
kind="link_inline"
onClick={async (event) => {
const sessionLoaded = await this.onLoginClickWithCheck(event);
if (sessionLoaded) {
dis.dispatch({ action: "view_home_page" });
}
}}
>
{sub}
</AccessibleButton>
),
},
)}
</h2>
);
}
body = <div>
<h1>{ _t("Registration Successful") }</h1>
{ regDoneText }
</div>;
body = (
<div>
<h1>{_t("Registration Successful")}</h1>
{regDoneText}
</div>
);
} else {
body = <Fragment>
<div className="mx_Register_mainContent">
<AuthHeaderDisplay
title={_t('Create account')}
serverPicker={<ServerPicker
title={_t("Host account on")}
dialogTitle={_t("Decide where your account is hosted")}
serverConfig={this.props.serverConfig}
onServerConfigChange={this.state.doingUIAuth ? undefined : this.props.onServerConfigChange}
/>}
>
{ errorText }
{ serverDeadSection }
</AuthHeaderDisplay>
{ this.renderRegisterComponent() }
</div>
<div className="mx_Register_footerActions">
{ goBack }
{ signIn }
</div>
</Fragment>;
body = (
<Fragment>
<div className="mx_Register_mainContent">
<AuthHeaderDisplay
title={_t("Create account")}
serverPicker={
<ServerPicker
title={_t("Host account on")}
dialogTitle={_t("Decide where your account is hosted")}
serverConfig={this.props.serverConfig}
onServerConfigChange={
this.state.doingUIAuth ? undefined : this.props.onServerConfigChange
}
/>
}
>
{errorText}
{serverDeadSection}
</AuthHeaderDisplay>
{this.renderRegisterComponent()}
</div>
<div className="mx_Register_footerActions">
{goBack}
{signIn}
</div>
</Fragment>
);
}
return (
<AuthPage>
<AuthHeader />
<AuthHeaderProvider>
<AuthBody flex>
{ body }
</AuthBody>
<AuthBody flex>{body}</AuthBody>
</AuthHeaderProvider>
</AuthPage>
);

View file

@ -14,27 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { ISecretStorageKeyInfo } from 'matrix-js-sdk/src/crypto/api';
import React from "react";
import { ISecretStorageKeyInfo } from "matrix-js-sdk/src/crypto/api";
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
import { VerificationRequest } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import { logger } from "matrix-js-sdk/src/logger";
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import VerificationRequestDialog from '../../views/dialogs/VerificationRequestDialog';
import { SetupEncryptionStore, Phase } from '../../../stores/SetupEncryptionStore';
import { _t } from "../../../languageHandler";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import Modal from "../../../Modal";
import VerificationRequestDialog from "../../views/dialogs/VerificationRequestDialog";
import { SetupEncryptionStore, Phase } from "../../../stores/SetupEncryptionStore";
import EncryptionPanel from "../../views/right_panel/EncryptionPanel";
import AccessibleButton from '../../views/elements/AccessibleButton';
import Spinner from '../../views/elements/Spinner';
import AccessibleButton from "../../views/elements/AccessibleButton";
import Spinner from "../../views/elements/Spinner";
function keyHasPassphrase(keyInfo: ISecretStorageKeyInfo): boolean {
return Boolean(
keyInfo.passphrase &&
keyInfo.passphrase.salt &&
keyInfo.passphrase.iterations,
);
return Boolean(keyInfo.passphrase && keyInfo.passphrase.salt && keyInfo.passphrase.iterations);
}
interface IProps {
@ -146,33 +142,34 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
};
public render() {
const {
phase,
lostKeys,
} = this.state;
const { phase, lostKeys } = this.state;
if (this.state.verificationRequest) {
return <EncryptionPanel
layout="dialog"
verificationRequest={this.state.verificationRequest}
onClose={this.onEncryptionPanelClose}
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
isRoomEncrypted={false}
/>;
return (
<EncryptionPanel
layout="dialog"
verificationRequest={this.state.verificationRequest}
onClose={this.onEncryptionPanelClose}
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
isRoomEncrypted={false}
/>
);
} else if (phase === Phase.Intro) {
if (lostKeys) {
return (
<div>
<p>{ _t(
"It looks like you don't have a Security Key or any other devices you can " +
"verify against. This device will not be able to access old encrypted messages. " +
"In order to verify your identity on this device, you'll need to reset " +
"your verification keys.",
) }</p>
<p>
{_t(
"It looks like you don't have a Security Key or any other devices you can " +
"verify against. This device will not be able to access old encrypted messages. " +
"In order to verify your identity on this device, you'll need to reset " +
"your verification keys.",
)}
</p>
<div className="mx_CompleteSecurity_actionRow">
<AccessibleButton kind="primary" onClick={this.onResetConfirmClick}>
{ _t("Proceed with reset") }
{_t("Proceed with reset")}
</AccessibleButton>
</div>
</div>
@ -188,38 +185,44 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
let useRecoveryKeyButton;
if (recoveryKeyPrompt) {
useRecoveryKeyButton = <AccessibleButton kind="primary" onClick={this.onUsePassphraseClick}>
{ recoveryKeyPrompt }
</AccessibleButton>;
useRecoveryKeyButton = (
<AccessibleButton kind="primary" onClick={this.onUsePassphraseClick}>
{recoveryKeyPrompt}
</AccessibleButton>
);
}
let verifyButton;
if (store.hasDevicesToVerifyAgainst) {
verifyButton = <AccessibleButton kind="primary" onClick={this.onVerifyClick}>
{ _t("Verify with another device") }
</AccessibleButton>;
verifyButton = (
<AccessibleButton kind="primary" onClick={this.onVerifyClick}>
{_t("Verify with another device")}
</AccessibleButton>
);
}
return (
<div>
<p>{ _t(
"Verify your identity to access encrypted messages and prove your identity to others.",
) }</p>
<p>
{_t("Verify your identity to access encrypted messages and prove your identity to others.")}
</p>
<div className="mx_CompleteSecurity_actionRow">
{ verifyButton }
{ useRecoveryKeyButton }
{verifyButton}
{useRecoveryKeyButton}
</div>
<div className="mx_SetupEncryptionBody_reset">
{ _t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
a: (sub) => <AccessibleButton
kind="link_inline"
className="mx_SetupEncryptionBody_reset_link"
onClick={this.onResetClick}
>
{ sub }
</AccessibleButton>,
}) }
{_t("Forgotten or lost all recovery methods? <a>Reset all</a>", null, {
a: (sub) => (
<AccessibleButton
kind="link_inline"
className="mx_SetupEncryptionBody_reset_link"
onClick={this.onResetClick}
>
{sub}
</AccessibleButton>
),
})}
</div>
</div>
);
@ -227,25 +230,24 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
} else if (phase === Phase.Done) {
let message;
if (this.state.backupInfo) {
message = <p>{ _t(
"Your new device is now verified. It has access to your " +
"encrypted messages, and other users will see it as trusted.",
) }</p>;
message = (
<p>
{_t(
"Your new device is now verified. It has access to your " +
"encrypted messages, and other users will see it as trusted.",
)}
</p>
);
} else {
message = <p>{ _t(
"Your new device is now verified. Other users will see it as trusted.",
) }</p>;
message = <p>{_t("Your new device is now verified. Other users will see it as trusted.")}</p>;
}
return (
<div>
<div className="mx_CompleteSecurity_heroIcon mx_E2EIcon_verified" />
{ message }
{message}
<div className="mx_CompleteSecurity_actionRow">
<AccessibleButton
kind="primary"
onClick={this.onDoneClick}
>
{ _t("Done") }
<AccessibleButton kind="primary" onClick={this.onDoneClick}>
{_t("Done")}
</AccessibleButton>
</div>
</div>
@ -253,22 +255,18 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
} else if (phase === Phase.ConfirmSkip) {
return (
<div>
<p>{ _t(
"Without verifying, you won't have access to all your messages " +
"and may appear as untrusted to others.",
) }</p>
<p>
{_t(
"Without verifying, you won't have access to all your messages " +
"and may appear as untrusted to others.",
)}
</p>
<div className="mx_CompleteSecurity_actionRow">
<AccessibleButton
kind="danger_outline"
onClick={this.onSkipConfirmClick}
>
{ _t("I'll verify later") }
<AccessibleButton kind="danger_outline" onClick={this.onSkipConfirmClick}>
{_t("I'll verify later")}
</AccessibleButton>
<AccessibleButton
kind="primary"
onClick={this.onSkipBackClick}
>
{ _t("Go Back") }
<AccessibleButton kind="primary" onClick={this.onSkipBackClick}>
{_t("Go Back")}
</AccessibleButton>
</div>
</div>
@ -276,23 +274,27 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
} else if (phase === Phase.ConfirmReset) {
return (
<div>
<p>{ _t(
"Resetting your verification keys cannot be undone. After resetting, " +
"you won't have access to old encrypted messages, and any friends who " +
"have previously verified you will see security warnings until you " +
"re-verify with them.",
) }</p>
<p>{ _t(
"Please only proceed if you're sure you've lost all of your other " +
"devices and your security key.",
) }</p>
<p>
{_t(
"Resetting your verification keys cannot be undone. After resetting, " +
"you won't have access to old encrypted messages, and any friends who " +
"have previously verified you will see security warnings until you " +
"re-verify with them.",
)}
</p>
<p>
{_t(
"Please only proceed if you're sure you've lost all of your other " +
"devices and your security key.",
)}
</p>
<div className="mx_CompleteSecurity_actionRow">
<AccessibleButton kind="danger_outline" onClick={this.onResetConfirmClick}>
{ _t("Proceed with reset") }
{_t("Proceed with reset")}
</AccessibleButton>
<AccessibleButton kind="primary" onClick={this.onResetBackClick}>
{ _t("Go Back") }
{_t("Go Back")}
</AccessibleButton>
</div>
</div>

View file

@ -14,23 +14,23 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React from "react";
import { logger } from "matrix-js-sdk/src/logger";
import { Optional } from "matrix-events-sdk";
import { ISSOFlow, LoginFlow } from "matrix-js-sdk/src/@types/auth";
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
import * as Lifecycle from '../../../Lifecycle';
import Modal from '../../../Modal';
import { _t } from "../../../languageHandler";
import dis from "../../../dispatcher/dispatcher";
import * as Lifecycle from "../../../Lifecycle";
import Modal from "../../../Modal";
import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { sendLoginRequest } from "../../../Login";
import AuthPage from "../../views/auth/AuthPage";
import { SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY } from "../../../BasePlatform";
import SSOButtons from "../../views/elements/SSOButtons";
import ConfirmWipeDeviceDialog from '../../views/dialogs/ConfirmWipeDeviceDialog';
import Field from '../../views/elements/Field';
import AccessibleButton from '../../views/elements/AccessibleButton';
import ConfirmWipeDeviceDialog from "../../views/dialogs/ConfirmWipeDeviceDialog";
import Field from "../../views/elements/Field";
import AccessibleButton from "../../views/elements/AccessibleButton";
import Spinner from "../../views/elements/Spinner";
import AuthHeader from "../../views/auth/AuthHeader";
import AuthBody from "../../views/auth/AuthBody";
@ -95,7 +95,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
const cli = MatrixClientPeg.get();
if (cli.isCryptoEnabled()) {
cli.countSessionsNeedingBackup().then(remaining => {
cli.countSessionsNeedingBackup().then((remaining) => {
this.setState({ keyBackupNeeded: remaining > 0 });
});
}
@ -114,7 +114,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
private async initLogin() {
const queryParams = this.props.realQueryParams;
const hasAllParams = queryParams && queryParams['loginToken'];
const hasAllParams = queryParams && queryParams["loginToken"];
if (hasAllParams) {
this.setState({ loginView: LoginView.Loading });
this.trySsoLogin();
@ -125,10 +125,10 @@ export default class SoftLogout extends React.Component<IProps, IState> {
// care about login flows here, unless it is the single flow we support.
const client = MatrixClientPeg.get();
const flows = (await client.loginFlows()).flows;
const loginViews = flows.map(f => STATIC_FLOWS_TO_VIEWS[f.type]);
const loginViews = flows.map((f) => STATIC_FLOWS_TO_VIEWS[f.type]);
const isSocialSignOn = loginViews.includes(LoginView.Password) && loginViews.includes(LoginView.SSO);
const firstView = loginViews.filter(f => !!f)[0] || LoginView.Unsupported;
const firstView = loginViews.filter((f) => !!f)[0] || LoginView.Unsupported;
const chosenView = isSocialSignOn ? LoginView.PasswordWithSocialSignOn : firstView;
this.setState({ flows, loginView: chosenView });
}
@ -138,7 +138,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
};
private onForgotPassword = () => {
dis.dispatch({ action: 'start_password_recovery' });
dis.dispatch({ action: "start_password_recovery" });
};
private onPasswordLogin = async (ev) => {
@ -188,7 +188,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
const isUrl = localStorage.getItem(SSO_ID_SERVER_URL_KEY) || MatrixClientPeg.get().getIdentityServerUrl();
const loginType = "m.login.token";
const loginParams = {
token: this.props.realQueryParams['loginToken'],
token: this.props.realQueryParams["loginToken"],
device_id: MatrixClientPeg.get().getDeviceId(),
};
@ -201,24 +201,26 @@ export default class SoftLogout extends React.Component<IProps, IState> {
return;
}
Lifecycle.hydrateSession(credentials).then(() => {
if (this.props.onTokenLoginCompleted) this.props.onTokenLoginCompleted();
}).catch((e) => {
logger.error(e);
this.setState({ busy: false, loginView: LoginView.Unsupported });
});
Lifecycle.hydrateSession(credentials)
.then(() => {
if (this.props.onTokenLoginCompleted) this.props.onTokenLoginCompleted();
})
.catch((e) => {
logger.error(e);
this.setState({ busy: false, loginView: LoginView.Unsupported });
});
}
private renderPasswordForm(introText: Optional<string>): JSX.Element {
let error: JSX.Element = null;
if (this.state.errorText) {
error = <span className='mx_Login_error'>{ this.state.errorText }</span>;
error = <span className="mx_Login_error">{this.state.errorText}</span>;
}
return (
<form onSubmit={this.onPasswordLogin}>
{ introText ? <p>{ introText }</p> : null }
{ error }
{introText ? <p>{introText}</p> : null}
{error}
<Field
type="password"
label={_t("Password")}
@ -232,10 +234,10 @@ export default class SoftLogout extends React.Component<IProps, IState> {
type="submit"
disabled={this.state.busy}
>
{ _t("Sign In") }
{_t("Sign In")}
</AccessibleButton>
<AccessibleButton onClick={this.onForgotPassword} kind="link">
{ _t("Forgotten your password?") }
{_t("Forgotten your password?")}
</AccessibleButton>
</form>
);
@ -243,17 +245,17 @@ export default class SoftLogout extends React.Component<IProps, IState> {
private renderSsoForm(introText: Optional<string>): JSX.Element {
const loginType = this.state.loginView === LoginView.CAS ? "cas" : "sso";
const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType) as ISSOFlow;
const flow = this.state.flows.find((flow) => flow.type === "m.login." + loginType) as ISSOFlow;
return (
<div>
{ introText ? <p>{ introText }</p> : null }
{introText ? <p>{introText}</p> : null}
<SSOButtons
matrixClient={MatrixClientPeg.get()}
flow={flow}
loginType={loginType}
fragmentAfterLogin={this.props.fragmentAfterLogin}
primary={!this.state.flows.find(flow => flow.type === "m.login.password")}
primary={!this.state.flows.find((flow) => flow.type === "m.login.password")}
/>
</div>
);
@ -268,7 +270,8 @@ export default class SoftLogout extends React.Component<IProps, IState> {
if (this.state.keyBackupNeeded) {
introText = _t(
"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.");
"Without them, you won't be able to read all of your secure messages in any session.",
);
}
if (this.state.loginView === LoginView.Password) {
@ -296,29 +299,28 @@ export default class SoftLogout extends React.Component<IProps, IState> {
// okay enough.
//
// Note: "mx_AuthBody_centered" text taken from registration page.
return <>
<p>{ introText }</p>
{ this.renderSsoForm(null) }
<h2 className="mx_AuthBody_centered">
{ _t(
"%(ssoButtons)s Or %(usernamePassword)s",
{
return (
<>
<p>{introText}</p>
{this.renderSsoForm(null)}
<h2 className="mx_AuthBody_centered">
{_t("%(ssoButtons)s Or %(usernamePassword)s", {
ssoButtons: "",
usernamePassword: "",
},
).trim() }
</h2>
{ this.renderPasswordForm(null) }
</>;
}).trim()}
</h2>
{this.renderPasswordForm(null)}
</>
);
}
// Default: assume unsupported/error
return (
<p>
{ _t(
{_t(
"You cannot sign in to your account. Please contact your " +
"homeserver admin for more information.",
) }
"homeserver admin for more information.",
)}
</p>
);
}
@ -328,26 +330,22 @@ export default class SoftLogout extends React.Component<IProps, IState> {
<AuthPage>
<AuthHeader />
<AuthBody>
<h1>
{ _t("You're signed out") }
</h1>
<h1>{_t("You're signed out")}</h1>
<h2>{ _t("Sign in") }</h2>
<div>
{ this.renderSignInSection() }
</div>
<h2>{_t("Sign in")}</h2>
<div>{this.renderSignInSection()}</div>
<h2>{ _t("Clear personal data") }</h2>
<h2>{_t("Clear personal data")}</h2>
<p>
{ _t(
{_t(
"Warning: Your personal data (including encryption keys) is still stored " +
"in this session. Clear it if you're finished using this session, or want to sign " +
"in to another account.",
) }
"in this session. Clear it if you're finished using this session, or want to sign " +
"in to another account.",
)}
</p>
<div>
<AccessibleButton onClick={this.onClearAll} kind="danger">
{ _t("Clear all data") }
{_t("Clear all data")}
</AccessibleButton>
</div>
</AuthBody>

View file

@ -19,7 +19,7 @@ import React, { ReactNode } from "react";
import AccessibleButton from "../../../views/elements/AccessibleButton";
import { Icon as EMailPromptIcon } from "../../../../../res/img/element-icons/email-prompt.svg";
import { Icon as RetryIcon } from "../../../../../res/img/element-icons/retry.svg";
import { _t } from '../../../../languageHandler';
import { _t } from "../../../../languageHandler";
import Tooltip, { Alignment } from "../../../views/elements/Tooltip";
import { useTimeoutToggle } from "../../../../hooks/useTimeoutToggle";
import { ErrorMessage } from "../../ErrorMessage";
@ -49,50 +49,35 @@ export const CheckEmail: React.FC<CheckEmailProps> = ({
toggleTooltipVisible();
};
return <>
<EMailPromptIcon className="mx_AuthBody_emailPromptIcon--shifted" />
<h1>{ _t("Check your email to continue") }</h1>
<div className="mx_AuthBody_text">
<p>
{ _t(
"Follow the instructions sent to <b>%(email)s</b>",
{ email: email },
{ b: t => <b>{ t }</b> },
) }
</p>
return (
<>
<EMailPromptIcon className="mx_AuthBody_emailPromptIcon--shifted" />
<h1>{_t("Check your email to continue")}</h1>
<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("Wrong email address?") }</span>
<AccessibleButton
className="mx_AuthBody_resend-button"
kind="link"
onClick={onReEnterEmailClick}
>
{ _t("Re-enter email address") }
<span className="mx_VerifyEMailDialog_text-light">{_t("Did not receive it?")}</span>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
{_t("Resend")}
<Tooltip
label={_t("Verification link email resent!")}
alignment={Alignment.Top}
visible={tooltipVisible}
/>
</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
className="mx_AuthBody_resend-button"
kind="link"
onClick={onResendClickFn}
>
<RetryIcon className="mx_Icon mx_Icon_16" />
{ _t("Resend") }
<Tooltip
label={_t("Verification link email resent!")}
alignment={Alignment.Top}
visible={tooltipVisible}
/>
</AccessibleButton>
</div>
</>;
</>
);
};

View file

@ -17,7 +17,7 @@ limitations under the License.
import React, { ReactNode, useRef } from "react";
import { Icon as EmailIcon } from "../../../../../res/img/element-icons/Email-icon.svg";
import { _t, _td } from '../../../../languageHandler';
import { _t, _td } from "../../../../languageHandler";
import EmailField from "../../../views/auth/EmailField";
import { ErrorMessage } from "../../ErrorMessage";
import Spinner from "../../../views/elements/Spinner";
@ -46,9 +46,7 @@ export const EnterEmail: React.FC<EnterEmailProps> = ({
onLoginClick,
onSubmitForm,
}) => {
const submitButtonChild = loading
? <Spinner w={16} h={16} />
: _t("Send email");
const submitButtonChild = loading ? <Spinner w={16} h={16} /> : _t("Send email");
const emailFieldRef = useRef<Field>(null);
@ -62,49 +60,47 @@ export const EnterEmail: React.FC<EnterEmailProps> = ({
emailFieldRef.current?.validate({ allowEmpty: false, focused: true });
};
return <>
<EmailIcon className="mx_AuthBody_icon" />
<h1>{ _t("Enter your email to reset password") }</h1>
<p className="mx_AuthBody_text">
{
_t(
return (
<>
<EmailIcon className="mx_AuthBody_icon" />
<h1>{_t("Enter your email to reset password")}</h1>
<p className="mx_AuthBody_text">
{_t(
"<b>%(homeserver)s</b> will send you a verification link to let you reset your password.",
{ homeserver },
{ b: t => <b>{ t }</b> },
)
}
</p>
<form onSubmit={onSubmit}>
<fieldset disabled={loading}>
<div className="mx_AuthBody_fieldRow">
<EmailField
name="reset_email" // define a name so browser's password autofill gets less confused
label="Email address"
labelRequired={_td("The email address linked to your account must be entered.")}
labelInvalid={_td("The email address doesn't appear to be valid.")}
value={email}
autoFocus={true}
onChange={(event: React.FormEvent<HTMLInputElement>) => onInputChanged("email", event)}
fieldRef={emailFieldRef}
/>
</div>
{ errorText && <ErrorMessage message={errorText} /> }
<button
type="submit"
className="mx_Login_submit"
>
{ 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>
</>;
{ b: (t) => <b>{t}</b> },
)}
</p>
<form onSubmit={onSubmit}>
<fieldset disabled={loading}>
<div className="mx_AuthBody_fieldRow">
<EmailField
name="reset_email" // define a name so browser's password autofill gets less confused
label="Email address"
labelRequired={_td("The email address linked to your account must be entered.")}
labelInvalid={_td("The email address doesn't appear to be valid.")}
value={email}
autoFocus={true}
onChange={(event: React.FormEvent<HTMLInputElement>) => onInputChanged("email", event)}
fieldRef={emailFieldRef}
/>
</div>
{errorText && <ErrorMessage message={errorText} />}
<button type="submit" className="mx_Login_submit">
{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>
</>
);
};

View file

@ -46,55 +46,49 @@ export const VerifyEmailModal: React.FC<Props> = ({
toggleTooltipVisible();
};
return <>
<EmailPromptIcon className="mx_AuthBody_emailPromptIcon" />
<h1>{ _t("Verify your email to continue") }</h1>
<p>
{ _t(
`We need to know its you before resetting your password.
return (
<>
<EmailPromptIcon className="mx_AuthBody_emailPromptIcon" />
<h1>{_t("Verify your email to continue")}</h1>
<p>
{_t(
`We need to know its you before resetting your password.
Click the link in the email we just sent to <b>%(email)s</b>`,
{
email,
},
{
b: sub => <b>{ sub }</b>,
},
) }
</p>
{
email,
},
{
b: (sub) => <b>{sub}</b>,
},
)}
</p>
<div className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{_t("Did not receive it?")}</span>
<AccessibleButton className="mx_AuthBody_resend-button" kind="link" onClick={onResendClickFn}>
<RetryIcon className="mx_Icon mx_Icon_16" />
{_t("Resend")}
<Tooltip
label={_t("Verification link email resent!")}
alignment={Alignment.Top}
visible={tooltipVisible}
/>
</AccessibleButton>
{errorText && <ErrorMessage message={errorText} />}
</div>
<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 className="mx_AuthBody_did-not-receive">
<span className="mx_VerifyEMailDialog_text-light">{ _t("Did not receive it?") }</span>
<AccessibleButton
className="mx_AuthBody_resend-button"
kind="link"
onClick={onResendClickFn}
>
<RetryIcon className="mx_Icon mx_Icon_16" />
{ _t("Resend") }
<Tooltip
label={_t("Verification link email resent!")}
alignment={Alignment.Top}
visible={tooltipVisible}
/>
</AccessibleButton>
{ errorText && <ErrorMessage message={errorText} /> }
</div>
<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>
<AccessibleButton
onClick={onCloseClick}
className="mx_Dialog_cancelButton"
aria-label={_t("Close dialog")}
/>
</>;
onClick={onCloseClick}
className="mx_Dialog_cancelButton"
aria-label={_t("Close dialog")}
/>
</>
);
};

View file

@ -32,10 +32,10 @@ export function AuthHeaderDisplay({ title, icon, serverPicker, children }: Props
const current = context.state.length ? context.state[0] : null;
return (
<Fragment>
{ current?.icon ?? icon }
<h1>{ current?.title ?? title }</h1>
{ children }
{ current?.hideServerPicker !== true && serverPicker }
{current?.icon ?? icon}
<h1>{current?.title ?? title}</h1>
{children}
{current?.hideServerPicker !== true && serverPicker}
</Fragment>
);
}

View file

@ -22,7 +22,7 @@ import { AuthHeaderModifier } from "./AuthHeaderModifier";
export enum AuthHeaderActionType {
Add,
Remove
Remove,
}
interface AuthHeaderAction {
@ -39,14 +39,10 @@ export function AuthHeaderProvider({ children }: PropsWithChildren<{}>) {
case AuthHeaderActionType.Add:
return [action.value, ...state];
case AuthHeaderActionType.Remove:
return (state.length && isEqual(state[0], action.value)) ? state.slice(1) : state;
return state.length && isEqual(state[0], action.value) ? state.slice(1) : state;
}
},
[] as ComponentProps<typeof AuthHeaderModifier>[],
);
return (
<AuthHeaderContext.Provider value={{ state, dispatch }}>
{ children }
</AuthHeaderContext.Provider>
);
return <AuthHeaderContext.Provider value={{ state, dispatch }}>{children}</AuthHeaderContext.Provider>;
}