Apply prettier formatting
This commit is contained in:
parent
1cac306093
commit
526645c791
1576 changed files with 65385 additions and 62478 deletions
|
@ -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} />
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>;
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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 it’s 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 it’s 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")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue