From fc3542ac4c681cf257235d7e2d1e232228692edd Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 19 Nov 2020 11:07:44 +0000 Subject: [PATCH 01/19] Extend Platform to support idpId for SSO flows --- src/BasePlatform.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 0a1f06f0b3b..4f7c7126e9e 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -248,15 +248,16 @@ export default abstract class BasePlatform { * @param {MatrixClient} mxClient the matrix client using which we should start the flow * @param {"sso"|"cas"} loginType the type of SSO it is, CAS/SSO. * @param {string} fragmentAfterLogin the hash to pass to the app during sso callback. + * @param {string} idpId The ID of the Identity Provider being targeted, optional. */ - startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string) { + startSingleSignOn(mxClient: MatrixClient, loginType: "sso" | "cas", fragmentAfterLogin: string, idpId?: string) { // persist hs url and is url for when the user is returned to the app with the login token localStorage.setItem(SSO_HOMESERVER_URL_KEY, mxClient.getHomeserverUrl()); if (mxClient.getIdentityServerUrl()) { localStorage.setItem(SSO_ID_SERVER_URL_KEY, mxClient.getIdentityServerUrl()); } const callbackUrl = this.getSSOCallbackUrl(fragmentAfterLogin); - window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType); // redirect to SSO + window.location.href = mxClient.getSsoLoginUrl(callbackUrl.toString(), loginType, idpId); // redirect to SSO } onKeyDown(ev: KeyboardEvent): boolean { From a1351ea1cdf549565dee3c8854c4fc8b523e5a9c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 20 Nov 2020 15:45:34 +0000 Subject: [PATCH 02/19] Increase Dialog button mixin border-radius to 8px --- res/themes/dark/css/_dark.scss | 2 +- res/themes/legacy-dark/css/_legacy-dark.scss | 2 +- res/themes/legacy-light/css/_legacy-light.scss | 2 +- res/themes/light/css/_light.scss | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index 76cc5e2df9e..82042d5ea37 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -214,7 +214,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.28); /* align images in buttons (eg spinners) */ vertical-align: middle; border: 0px; - border-radius: 4px; + border-radius: 8px; font-family: $font-family; font-size: $font-14px; color: $button-fg-color; diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 716d8c73857..a377b86ff91 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -205,7 +205,7 @@ $composer-shadow-color: tranparent; /* align images in buttons (eg spinners) */ vertical-align: middle; border: 0px; - border-radius: 4px; + border-radius: 8px; font-family: $font-family; font-size: $font-14px; color: $button-fg-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 8c42c5c97fc..fa03729c57c 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -328,7 +328,7 @@ $composer-shadow-color: tranparent; /* align images in buttons (eg spinners) */ vertical-align: middle; border: 0px; - border-radius: 4px; + border-radius: 8px; font-family: $font-family; font-size: $font-14px; color: $button-fg-color; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 5437a6de1cf..2e2d234f377 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -332,7 +332,7 @@ $composer-shadow-color: rgba(0, 0, 0, 0.04); /* align images in buttons (eg spinners) */ vertical-align: middle; border: 0px; - border-radius: 4px; + border-radius: 8px; font-family: $font-family; font-size: $font-14px; color: $button-fg-color; From 6f6e850075bb359d5b4b0aa989834871675abe45 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 23 Nov 2020 10:23:28 +0000 Subject: [PATCH 03/19] lowercase username placeholder in Password Login and Registration Form --- src/components/views/auth/PasswordLogin.tsx | 1 + src/components/views/auth/RegistrationForm.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx index fced2e08d01..f29418d50e0 100644 --- a/src/components/views/auth/PasswordLogin.tsx +++ b/src/components/views/auth/PasswordLogin.tsx @@ -357,6 +357,7 @@ export default class PasswordLogin extends React.PureComponent { key="username_input" type="text" label={_t("Username")} + placeholder={_t("Username").toLocaleLowerCase()} value={this.props.username} onChange={this.onUsernameChanged} onFocus={this.onUsernameFocus} diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index f6fb3bb3ea1..58a0f63b5f5 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -522,6 +522,7 @@ export default class RegistrationForm extends React.PureComponent Date: Mon, 23 Nov 2020 10:25:46 +0000 Subject: [PATCH 04/19] Improve no email warning during registration --- res/css/_components.scss | 1 + .../_RegistrationEmailPromptDialog.scss | 28 ++++++ .../views/auth/RegistrationForm.tsx | 54 +++++------ .../dialogs/RegistrationEmailPromptDialog.tsx | 96 +++++++++++++++++++ src/components/views/elements/Field.tsx | 4 +- src/i18n/strings/en_EN.json | 7 +- 6 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 res/css/views/dialogs/_RegistrationEmailPromptDialog.scss create mode 100644 src/components/views/dialogs/RegistrationEmailPromptDialog.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index eae67a84a2e..9dd65d2a4f9 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -78,6 +78,7 @@ @import "./views/dialogs/_MessageEditHistoryDialog.scss"; @import "./views/dialogs/_ModalWidgetDialog.scss"; @import "./views/dialogs/_NewSessionReviewDialog.scss"; +@import "./views/dialogs/_RegistrationEmailPromptDialog.scss"; @import "./views/dialogs/_RoomSettingsDialog.scss"; @import "./views/dialogs/_RoomSettingsDialogBridges.scss"; @import "./views/dialogs/_RoomUpgradeDialog.scss"; diff --git a/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss b/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss new file mode 100644 index 00000000000..31fc6d7a047 --- /dev/null +++ b/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss @@ -0,0 +1,28 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_RegistrationEmailPromptDialog { + width: 417px; + + .mx_Dialog_content { + margin-bottom: 24px; + color: $tertiary-fg-color; + } + + .mx_Dialog_primary { + width: 100%; + } +} diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 58a0f63b5f5..764dfdd5269 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -28,6 +28,9 @@ import withValidation from '../elements/Validation'; import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import PassphraseField from "./PassphraseField"; import CountlyAnalytics from "../../../CountlyAnalytics"; +import Field from '../elements/Field'; +import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog'; +import QuestionDialog from '../dialogs/QuestionDialog'; enum RegistrationField { Email = "field_email", @@ -104,6 +107,7 @@ export default class RegistrationForm extends React.PureComponent { ev.preventDefault(); + ev.persist(); if (!this.props.canSubmit) return; @@ -116,36 +120,36 @@ export default class RegistrationForm extends React.PureComponent { + if (confirmed) this.doSubmit(ev); + }, + }); } else if (this.showEmail()) { - desc = _t( - "If you don't specify an email address, you won't be able to reset your password. " + - "Are you sure?", - ); + CountlyAnalytics.instance.track("onboarding_registration_submit_warn"); + Modal.createTrackedDialog("Email prompt dialog", '', RegistrationEmailPromptDialog, { + onFinished: async (confirmed: boolean, email?: string) => { + if (confirmed) { + this.setState({ + email, + }, () => { + this.doSubmit(ev); + }); + } + }, + }); } else { // user can't set an e-mail so don't prompt them to this.doSubmit(ev); return; } - - CountlyAnalytics.instance.track("onboarding_registration_submit_warn"); - - const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); - Modal.createTrackedDialog('If you don\'t specify an email address...', '', QuestionDialog, { - title: _t("Warning!"), - description: desc, - button: _t("Continue"), - onFinished: (confirmed) => { - if (confirmed) { - this.doSubmit(ev); - } - }, - }); } else { this.doSubmit(ev); } @@ -443,7 +447,6 @@ export default class RegistrationForm extends React.PureComponent this[RegistrationField.PasswordConfirm] = field} @@ -493,7 +495,6 @@ export default class RegistrationForm extends React.PureComponent this[RegistrationField.Username] = field} diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx new file mode 100644 index 00000000000..8e91a07bf5c --- /dev/null +++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx @@ -0,0 +1,96 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import * as React from "react"; + +import { _t } from '../../../languageHandler'; +import { IDialogProps } from "./IDialogProps"; +import {useRef, useState} from "react"; +import Field from "../elements/Field"; +import CountlyAnalytics from "../../../CountlyAnalytics"; +import withValidation from "../elements/Validation"; +import * as Email from "../../../email"; +import BaseDialog from "./BaseDialog"; +import DialogButtons from "../elements/DialogButtons"; + +interface IProps extends IDialogProps { + onFinished(continued: boolean, email?: string): void; +} + +const validation = withValidation({ + rules: [ + { + key: "email", + test: ({ value }) => !value || Email.looksValid(value), + invalid: () => _t("Doesn't look like a valid email address"), + }, + ], +}); + +const RegistrationEmailPromptDialog: React.FC = ({onFinished}) => { + const [email, setEmail] = useState(""); + const fieldRef = useRef(); + + const onSubmit = async () => { + if (email) { + const valid = await fieldRef.current.validate({ allowEmpty: false }); + + if (!valid) { + fieldRef.current.focus(); + fieldRef.current.validate({ allowEmpty: false, focused: true }); + return; + } + } + + onFinished(true, email); + }; + + return onFinished(false)} + fixedWidth={false} + > +
+

{_t("Just a heads up, if you don't add an email and forget your password, you could " + + "permanently lose access to your account.", {}, { + b: sub => {sub}, + })}

+
+ { + setEmail(ev.target.value); + }} + onValidate={async fieldState => await validation(fieldState)} + onFocus={() => CountlyAnalytics.instance.track("onboarding_registration_email2_focus")} + onBlur={() => CountlyAnalytics.instance.track("onboarding_registration_email2_blur")} + /> + +
+ +
; +}; + +export default RegistrationEmailPromptDialog; diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index fb34f51b604..58bd5226b6e 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -167,7 +167,7 @@ export default class Field extends React.PureComponent { } }; - private async validate({ focused, allowEmpty = true }: {focused: boolean, allowEmpty?: boolean}) { + public async validate({ focused, allowEmpty = true }: {focused?: boolean, allowEmpty?: boolean}) { if (!this.props.onValidate) { return; } @@ -196,6 +196,8 @@ export default class Field extends React.PureComponent { feedbackVisible: false, }); } + + return valid; } public render() { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e04c929f80b..b66df6c7619 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2045,6 +2045,10 @@ "Use this session to verify your new one, granting it access to encrypted messages:": "Use this session to verify your new one, granting it access to encrypted messages:", "If you didn’t sign in to this session, your account may be compromised.": "If you didn’t sign in to this session, your account may be compromised.", "This wasn't me": "This wasn't me", + "Doesn't look like a valid email address": "Doesn't look like a valid email address", + "Continuing without email": "Continuing without email", + "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.": "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.", + "Email (optional)": "Email (optional)", "Please fill why you're reporting.": "Please fill why you're reporting.", "Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator", "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.", @@ -2226,7 +2230,6 @@ "Keep going...": "Keep going...", "Enter username": "Enter username", "Enter email address": "Enter email address", - "Doesn't look like a valid email address": "Doesn't look like a valid email address", "Enter phone number": "Enter phone number", "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", "Email": "Email", @@ -2236,14 +2239,12 @@ "Sign in with": "Sign in with", "Sign in": "Sign in", "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "No identity server is configured so you cannot add an email address in order to reset your password in the future.", - "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", "Use an email address to recover your account": "Use an email address to recover your account", "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", "Passwords don't match": "Passwords don't match", "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", - "Email (optional)": "Email (optional)", "Phone (optional)": "Phone (optional)", "Register": "Register", "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.", From 613710b75c25394208aa270b1bcf46de552c323e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 23 Nov 2020 11:28:49 +0000 Subject: [PATCH 05/19] Iterate Auth copy --- res/css/structures/auth/_Login.scss | 4 +++- res/css/views/auth/_AuthBody.scss | 9 ++++++++ src/components/structures/auth/Login.tsx | 8 ++++--- .../structures/auth/Registration.tsx | 10 +++++---- src/components/views/auth/PasswordLogin.tsx | 22 +++++++------------ .../views/auth/RegistrationForm.tsx | 18 ++++++++------- src/i18n/strings/en_EN.json | 12 +++++----- 7 files changed, 48 insertions(+), 35 deletions(-) diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index 02436833a2e..0774ac273d6 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -18,7 +18,7 @@ limitations under the License. .mx_Login_submit { @mixin mx_DialogButton; width: 100%; - margin-top: 35px; + margin-top: 24px; margin-bottom: 24px; box-sizing: border-box; text-align: center; @@ -91,6 +91,8 @@ limitations under the License. } div.mx_AccessibleButton_kind_link.mx_Login_forgot { + display: block; + margin: 0 auto; // style it as a link font-size: inherit; padding: 0; diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index 0ba0d10e068..b51511a671c 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -146,6 +146,15 @@ limitations under the License. display: block; text-align: center; width: 100%; + margin-top: 24px; + + > a { + font-weight: $font-semi-bold; + } +} + +form + .mx_AuthBody_changeFlow { + margin-top: 0; } .mx_AuthBody_spinner { diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 4cd8981a65d..17220981c92 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -670,9 +670,11 @@ export default class LoginComponent extends React.Component { ; } else if (SettingsStore.getValue(UIFeature.Registration)) { footer = ( - - { _t('Create account') } - + + {_t("New? Create account", {}, { + a: sub => { sub }, + })} + ); } diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index f97f20cf598..004029c920d 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -650,9 +650,11 @@ export default class Registration extends React.Component { ); } - const signIn = - { _t('Sign in instead') } - ; + const signIn = + {_t("Already have an account? Sign in here", {}, { + a: sub => { sub }, + })} + ; // Only show the 'go back' button if you're not looking at the form let goBack; @@ -736,7 +738,7 @@ export default class Registration extends React.Component { } body =
-

{ _t('Create your account') }

+

{ _t('Create account') }

{ errorText } { serverDeadSection } { this.renderServerComponent() } diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx index f29418d50e0..198c76849c7 100644 --- a/src/components/views/auth/PasswordLogin.tsx +++ b/src/components/views/auth/PasswordLogin.tsx @@ -411,20 +411,14 @@ export default class PasswordLogin extends React.PureComponent { let forgotPasswordJsx; if (this.props.onForgotPasswordClick) { - forgotPasswordJsx = - {_t('Not sure of your password? Set a new one', {}, { - a: sub => ( - - {sub} - - ), - })} - ; + forgotPasswordJsx = + {_t("Forgot password?")} + ; } const pwFieldClass = classNames({ diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 764dfdd5269..610618bb3e2 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -540,17 +540,19 @@ export default class RegistrationForm extends React.PureComponent - {_t( - "Set an email for account recovery. " + - "Use email or phone to optionally be discoverable by existing contacts.", - )} + { + _t("Add an email to be able to reset your password.") + } { + _t("Use email or phone to optionally be discoverable by existing contacts.") + }
; } else { emailHelperText =
- {_t( - "Set an email for account recovery. " + - "Use email to optionally be discoverable by existing contacts.", - )} + { + _t("Add an email to be able to reset your password.") + } { + _t("Use email to optionally be discoverable by existing contacts.") + }
; } } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b66df6c7619..fff7bdac44d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2235,7 +2235,7 @@ "Email": "Email", "Username": "Username", "Phone": "Phone", - "Not sure of your password? Set a new one": "Not sure of your password? Set a new one", + "Forgot password?": "Forgot password?", "Sign in with": "Sign in with", "Sign in": "Sign in", "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "No identity server is configured so you cannot add an email address in order to reset your password in the future.", @@ -2247,8 +2247,9 @@ "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", "Phone (optional)": "Phone (optional)", "Register": "Register", - "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.", - "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.", + "Add an email to be able to reset your password.": "Add an email to be able to reset your password.", + "Use email or phone to optionally be discoverable by existing contacts.": "Use email or phone to optionally be discoverable by existing contacts.", + "Use email to optionally be discoverable by existing contacts.": "Use email to optionally be discoverable by existing contacts.", "Enter your custom homeserver URL What does this mean?": "Enter your custom homeserver URL What does this mean?", "Homeserver URL": "Homeserver URL", "Enter your custom identity server URL What does this mean?": "Enter your custom identity server URL What does this mean?", @@ -2456,10 +2457,11 @@ "Syncing...": "Syncing...", "Signing In...": "Signing In...", "If you've joined lots of rooms, this might take a while": "If you've joined lots of rooms, this might take a while", - "Create account": "Create account", + "New? Create account": "New? Create account", "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", + "Already have an account? Sign in here": "Already have an account? Sign in here", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", "Continue with previous account": "Continue with previous account", "Log in to your new account.": "Log in to your new account.", @@ -2467,7 +2469,7 @@ "Registration Successful": "Registration Successful", "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", "Create your Matrix account on ": "Create your Matrix account on ", - "Create your account": "Create your account", + "Create account": "Create account", "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", "Use Recovery Key": "Use Recovery Key", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.", From 1d53a5cf235fcf1f1666e956f52bab0ea651aa0c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 23 Nov 2020 17:10:15 +0000 Subject: [PATCH 06/19] Initial support for MSC2858 --- res/css/_components.scss | 1 + res/css/structures/auth/_Login.scss | 6 - res/css/views/elements/_SSOButtons.scss | 41 ++++++ src/Login.ts | 37 +++--- src/components/structures/auth/Login.tsx | 131 ++++++++----------- src/components/structures/auth/SoftLogout.js | 16 ++- src/components/views/auth/PasswordLogin.tsx | 5 - src/components/views/elements/SSOButton.js | 42 ------ src/components/views/elements/SSOButtons.tsx | 111 ++++++++++++++++ src/i18n/strings/en_EN.json | 1 + 10 files changed, 237 insertions(+), 154 deletions(-) create mode 100644 res/css/views/elements/_SSOButtons.scss delete mode 100644 src/components/views/elements/SSOButton.js create mode 100644 src/components/views/elements/SSOButtons.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 9dd65d2a4f9..53a72c4ce85 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -124,6 +124,7 @@ @import "./views/elements/_RichText.scss"; @import "./views/elements/_RoleButton.scss"; @import "./views/elements/_RoomAliasField.scss"; +@import "./views/elements/_SSOButtons.scss"; @import "./views/elements/_Slider.scss"; @import "./views/elements/_Spinner.scss"; @import "./views/elements/_StyledCheckbox.scss"; diff --git a/res/css/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss index 0774ac273d6..a8cb7d7eee7 100644 --- a/res/css/structures/auth/_Login.scss +++ b/res/css/structures/auth/_Login.scss @@ -33,12 +33,6 @@ limitations under the License. cursor: default; } -.mx_AuthBody a.mx_Login_sso_link:link, -.mx_AuthBody a.mx_Login_sso_link:hover, -.mx_AuthBody a.mx_Login_sso_link:visited { - color: $button-primary-fg-color; -} - .mx_Login_loader { display: inline; position: relative; diff --git a/res/css/views/elements/_SSOButtons.scss b/res/css/views/elements/_SSOButtons.scss new file mode 100644 index 00000000000..8dc5d30257f --- /dev/null +++ b/res/css/views/elements/_SSOButtons.scss @@ -0,0 +1,41 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_SSOButtons { + display: flex; + justify-content: center; + + .mx_SSOButton { + position: relative; + + > img { + object-fit: contain; + position: absolute; + left: 12px; + top: 12px; + } + } + + .mx_SSOButton_mini { + box-sizing: border-box; + width: 50px; // 48px + 1px border on all sides + height: 50px; // 48px + 1px border on all sides + + & + .mx_SSOButton_mini { + margin-left: 24px; + } + } +} diff --git a/src/Login.ts b/src/Login.ts index ae4aa226edb..d5776da856b 100644 --- a/src/Login.ts +++ b/src/Login.ts @@ -30,9 +30,24 @@ interface ILoginOptions { // TODO: Move this to JS SDK interface ILoginFlow { - type: string; + type: "m.login.password" | "m.login.cas"; } +export interface IIdentityProvider { + id: string; + name: string; + icon?: string; +} + +export interface ISSOFlow { + type: "m.login.sso"; + // eslint-disable-next-line camelcase + identity_providers: IIdentityProvider[]; + "org.matrix.msc2858.identity_providers": IIdentityProvider[]; // Unstable prefix for MSC2858 +} + +export type LoginFlow = ISSOFlow | ILoginFlow; + // TODO: Move this to JS SDK /* eslint-disable camelcase */ interface ILoginParams { @@ -48,9 +63,8 @@ export default class Login { private hsUrl: string; private isUrl: string; private fallbackHsUrl: string; - private currentFlowIndex: number; // TODO: Flows need a type in JS SDK - private flows: Array; + private flows: Array; private defaultDeviceDisplayName: string; private tempClient: MatrixClient; @@ -63,7 +77,6 @@ export default class Login { this.hsUrl = hsUrl; this.isUrl = isUrl; this.fallbackHsUrl = fallbackHsUrl; - this.currentFlowIndex = 0; this.flows = []; this.defaultDeviceDisplayName = opts.defaultDeviceDisplayName; this.tempClient = null; // memoize @@ -100,27 +113,13 @@ export default class Login { }); } - public async getFlows(): Promise> { + public async getFlows(): Promise> { const client = this.createTemporaryClient(); const { flows } = await client.loginFlows(); this.flows = flows; - this.currentFlowIndex = 0; - // technically the UI should display options for all flows for the - // user to then choose one, so return all the flows here. return this.flows; } - public chooseFlow(flowIndex): void { - this.currentFlowIndex = flowIndex; - } - - public getCurrentFlowStep(): string { - // technically the flow can have multiple steps, but no one does this - // for login so we can ignore it. - const flowStep = this.flows[this.currentFlowIndex]; - return flowStep ? flowStep.type : null; - } - public loginViaPassword( username: string, phoneCountry: string, diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 17220981c92..dd1fcc4d9ad 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -14,17 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {ComponentProps, ReactNode} from 'react'; +import React, {ReactNode} from 'react'; +import {MatrixError} from "matrix-js-sdk/src/http-api"; import {_t, _td} from '../../../languageHandler'; import * as sdk from '../../../index'; -import Login from '../../../Login'; +import Login, {ISSOFlow, LoginFlow} from '../../../Login'; import SdkConfig from '../../../SdkConfig'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import classNames from "classnames"; import AuthPage from "../../views/auth/AuthPage"; -import SSOButton from "../../views/elements/SSOButton"; import PlatformPeg from '../../../PlatformPeg'; import SettingsStore from "../../../settings/SettingsStore"; import {UIFeature} from "../../../settings/UIFeature"; @@ -35,6 +35,7 @@ import PasswordLogin from "../../views/auth/PasswordLogin"; import SignInToText from "../../views/auth/SignInToText"; import InlineSpinner from "../../views/elements/InlineSpinner"; import Spinner from "../../views/elements/Spinner"; +import SSOButtons from "../../views/elements/SSOButtons"; // Enable phases for login const PHASES_ENABLED = true; @@ -90,17 +91,14 @@ interface IState { // can we attempt to log in or are there validation errors? canTryLogin: boolean; + phase: Phase; + flows?: LoginFlow[]; + // used for preserving form values when changing homeserver username: string; phoneCountry?: string; phoneNumber: string; - // Phase of the overall login dialog. - phase: Phase; - // The current login flow, such as password, SSO, etc. - // we need to load the flows from the server - currentFlow?: string; - // We perform liveliness checks later, but for now suppress the errors. // We also track the server dead errors independently of the regular errors so // that we can render it differently, and override any other error the user may @@ -113,9 +111,10 @@ interface IState { /* * A wire component which glues together login UI components and Login logic */ -export default class LoginComponent extends React.Component { +export default class LoginComponent extends React.PureComponent { private unmounted = false; private loginLogic: Login; + private readonly stepRendererMap: Record ReactNode>; constructor(props) { @@ -127,11 +126,14 @@ export default class LoginComponent extends React.Component { errorText: null, loginIncorrect: false, canTryLogin: true, + + phase: Phase.Login, + flows: null, + username: "", phoneCountry: null, phoneNumber: "", - phase: Phase.Login, - currentFlow: null, + serverIsAlive: true, serverErrorIsFatal: false, serverDeadError: "", @@ -351,13 +353,14 @@ export default class LoginComponent extends React.Component { }; onTryRegisterClick = ev => { - const step = this.getCurrentFlowStep(); - if (step === 'm.login.sso' || step === 'm.login.cas') { + const hasPasswordFlow = this.state.flows.find(flow => flow.type === "m.login.password"); + if (!hasPasswordFlow) { // If we're showing SSO it means that registration is also probably disabled, // so intercept the click and instead pretend the user clicked 'Sign in with SSO'. ev.preventDefault(); ev.stopPropagation(); - const ssoKind = step === 'm.login.sso' ? 'sso' : 'cas'; + const step = this.state.flows.find(flow => flow.type === "m.login.sso" || flow.type === "m.login.cas"); + const ssoKind = step.type === 'm.login.sso' ? 'sso' : 'cas'; PlatformPeg.get().startSingleSignOn(this.loginLogic.createTemporaryClient(), ssoKind, this.props.fragmentAfterLogin); } else { @@ -397,7 +400,6 @@ export default class LoginComponent extends React.Component { this.setState({ busy: true, - currentFlow: null, // reset flow loginIncorrect: false, }); @@ -432,27 +434,18 @@ export default class LoginComponent extends React.Component { loginLogic.getFlows().then((flows) => { // look for a flow where we understand all of the steps. - for (let i = 0; i < flows.length; i++ ) { - if (!this.isSupportedFlow(flows[i])) { - continue; - } + const supportedFlows = flows.filter(this.isSupportedFlow); - // we just pick the first flow where we support all the - // steps. (we don't have a UI for multiple logins so let's skip - // that for now). - loginLogic.chooseFlow(i); + if (supportedFlows.length > 0) { this.setState({ currentFlow: this.getCurrentFlowStep(), }); return; } - // we got to the end of the list without finding a suitable - // flow. + + // 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.", - ), + errorText: _t("This homeserver doesn't offer any login flows which are supported by this client."), }); }, (err) => { this.setState({ @@ -467,7 +460,7 @@ export default class LoginComponent extends React.Component { }); } - private isSupportedFlow(flow) { + private isSupportedFlow = (flow: LoginFlow): boolean => { // technically the flow can have multiple steps, but no one does this // for login and loginLogic doesn't support it so we can ignore it. if (!this.stepRendererMap[flow.type]) { @@ -475,13 +468,9 @@ export default class LoginComponent extends React.Component { return false; } return true; - } - - private getCurrentFlowStep() { - return this.loginLogic ? this.loginLogic.getCurrentFlowStep() : null; - } + }; - private errorTextFromError(err) { + private errorTextFromError(err: MatrixError): ReactNode { let errCode = err.errcode; if (!errCode && err.httpStatus) { errCode = "HTTP " + err.httpStatus; @@ -550,37 +539,38 @@ export default class LoginComponent extends React.Component { />; } - private renderLoginComponentForStep() { - if (PHASES_ENABLED && this.state.phase !== Phase.Login) { - return null; - } - - const step = this.state.currentFlow; - - if (!step) { - return null; - } - - const stepRenderer = this.stepRendererMap[step]; - - if (stepRenderer) { - return stepRenderer(); - } + renderLoginComponentForFlows() { + if (!this.state.flows) return null; - return null; - } + // this is the ideal order we want to show the flows in + const order = [ + "m.login.password", + "m.login.sso", + ]; - private renderPasswordStep = () => { let onEditServerDetailsClick = null; // If custom URLs are allowed, wire up the server details edit link. - if (PHASES_ENABLED && !SdkConfig.get()['disable_custom_urls']) { + if (!SdkConfig.get()['disable_custom_urls']) { onEditServerDetailsClick = this.onEditServerDetailsClick; } + const flows = order.map(type => this.state.flows.find(flow => flow.type === type)).filter(Boolean); + return + + { flows.map(flow => { + const stepRenderer = this.stepRendererMap[flow.type]; + return { stepRenderer() } + }) } + + } + + private renderPasswordStep = () => { return ( { }; private renderSsoStep = loginType => { - let onEditServerDetailsClick = null; - // If custom URLs are allowed, wire up the server details edit link. - if (PHASES_ENABLED && !SdkConfig.get()['disable_custom_urls']) { - onEditServerDetailsClick = this.onEditServerDetailsClick; - } - // XXX: This link does *not* have a target="_blank" because single sign-on relies on - // redirecting the user back to a URI once they're logged in. On the web, this means - // we use the same window and redirect back to Element. On Electron, this actually - // opens the SSO page in the Electron app itself due to - // https://github.com/electron/electron/issues/8841 and so happens to work. - // If this bug gets fixed, it will break SSO since it will open the SSO page in the - // user's browser, let them log into their SSO provider, then redirect their browser - // to vector://vector which, of course, will not work. + const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType) as ISSOFlow; + return (
- - - flow.type === "m.login.password")} />
); @@ -689,7 +666,7 @@ export default class LoginComponent extends React.Component { { errorTextSection } { serverDeadSection } { this.renderServerComponent() } - { this.renderLoginComponentForStep() } + { this.renderLoginComponentForFlows() } { footer } diff --git a/src/components/structures/auth/SoftLogout.js b/src/components/structures/auth/SoftLogout.js index a539c8c9ee5..fdc1aec96de 100644 --- a/src/components/structures/auth/SoftLogout.js +++ b/src/components/structures/auth/SoftLogout.js @@ -24,8 +24,8 @@ import Modal from '../../../Modal'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import {sendLoginRequest} from "../../../Login"; import AuthPage from "../../views/auth/AuthPage"; -import SSOButton from "../../views/elements/SSOButton"; import {SSO_HOMESERVER_URL_KEY, SSO_ID_SERVER_URL_KEY} from "../../../BasePlatform"; +import SSOButtons from "../../views/elements/SSOButtons"; const LOGIN_VIEW = { LOADING: 1, @@ -101,10 +101,11 @@ export default class SoftLogout extends React.Component { // Note: we don't use the existing Login class because it is heavily flow-based. We don't // care about login flows here, unless it is the single flow we support. const client = MatrixClientPeg.get(); - const loginViews = (await client.loginFlows()).flows.map(f => FLOWS_TO_VIEWS[f.type]); + const flows = (await client.loginFlows()).flows; + const loginViews = flows.map(f => FLOWS_TO_VIEWS[f.type]); const chosenView = loginViews.filter(f => !!f)[0] || LOGIN_VIEW.UNSUPPORTED; - this.setState({loginView: chosenView}); + this.setState({ flows, loginView: chosenView }); } onPasswordChange = (ev) => { @@ -240,13 +241,18 @@ export default class SoftLogout extends React.Component { introText = _t("Sign in and regain access to your account."); } // else we already have a message and should use it (key backup warning) + const loginType = this.state.loginView === LOGIN_VIEW.CAS ? "cas" : "sso"; + const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType); + return (

{introText}

- flow.type === "m.login.password")} />
); diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx index 198c76849c7..80384ba26ea 100644 --- a/src/components/views/auth/PasswordLogin.tsx +++ b/src/components/views/auth/PasswordLogin.tsx @@ -26,7 +26,6 @@ import withValidation from "../elements/Validation"; import * as Email from "../../../email"; import Field from "../elements/Field"; import CountryDropdown from "./CountryDropdown"; -import SignInToText from "./SignInToText"; // For validating phone numbers without country codes const PHONE_NUMBER_REGEX = /^[0-9()\-\s]*$/; @@ -47,7 +46,6 @@ interface IProps { onUsernameBlur?(username: string): void; onPhoneCountryChanged?(phoneCountry: string): void; onPhoneNumberChanged?(phoneNumber: string): void; - onEditServerDetailsClick?(): void; onForgotPasswordClick?(): void; } @@ -70,7 +68,6 @@ enum LoginField { */ export default class PasswordLogin extends React.PureComponent { static defaultProps = { - onEditServerDetailsClick: null, onUsernameChanged: function() {}, onUsernameBlur: function() {}, onPhoneCountryChanged: function() {}, @@ -460,8 +457,6 @@ export default class PasswordLogin extends React.PureComponent { return (
-
{loginType} {loginField} diff --git a/src/components/views/elements/SSOButton.js b/src/components/views/elements/SSOButton.js deleted file mode 100644 index 1126ae3cd77..00000000000 --- a/src/components/views/elements/SSOButton.js +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; - -import PlatformPeg from "../../../PlatformPeg"; -import AccessibleButton from "./AccessibleButton"; -import {_t} from "../../../languageHandler"; - -const SSOButton = ({matrixClient, loginType, fragmentAfterLogin, ...props}) => { - const onClick = () => { - PlatformPeg.get().startSingleSignOn(matrixClient, loginType, fragmentAfterLogin); - }; - - return ( - - {_t("Sign in with single sign-on")} - - ); -}; - -SSOButton.propTypes = { - matrixClient: PropTypes.object.isRequired, // does not use context as may use a temporary client - loginType: PropTypes.oneOf(["sso", "cas"]), // defaults to "sso" in base-apis - fragmentAfterLogin: PropTypes.string, -}; - -export default SSOButton; diff --git a/src/components/views/elements/SSOButtons.tsx b/src/components/views/elements/SSOButtons.tsx new file mode 100644 index 00000000000..8247d17db88 --- /dev/null +++ b/src/components/views/elements/SSOButtons.tsx @@ -0,0 +1,111 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from "react"; +import {MatrixClient} from "matrix-js-sdk/src/client"; + +import PlatformPeg from "../../../PlatformPeg"; +import AccessibleButton from "./AccessibleButton"; +import {_t} from "../../../languageHandler"; +import {IIdentityProvider, ISSOFlow} from "../../../Login"; +import classNames from "classnames"; + +interface ISSOButtonProps extends Omit { + idp: IIdentityProvider; + mini?: boolean; +} + +const SSOButton: React.FC = ({ + matrixClient, + loginType, + fragmentAfterLogin, + idp, + primary, + mini, + ...props +}) => { + const kind = primary ? "primary" : "primary_outline"; + const label = idp ? _t("Continue with %(provider)s", { provider: idp.name }) : _t("Sign in with single sign-on"); + + const onClick = () => { + PlatformPeg.get().startSingleSignOn(matrixClient, loginType, fragmentAfterLogin, idp.id); + }; + + let icon; + if (idp && idp.icon && idp.icon.startsWith("https://")) { + // TODO sanitize images + icon = {label}; + } + + const classes = classNames("mx_SSOButton", { + mx_SSOButton_mini: mini, + }); + + if (mini) { + // TODO fallback icon + return ( + + { icon } + + ); + } + + return ( + + { icon } + { label } + + ); +}; + +interface IProps { + matrixClient: MatrixClient; + flow: ISSOFlow; + loginType?: "sso" | "cas"; + fragmentAfterLogin?: string; + primary?: boolean; +} + +const SSOButtons: React.FC = ({matrixClient, flow, loginType, fragmentAfterLogin, primary}) => { + const providers = flow.identity_providers || flow["org.matrix.msc2858.identity_providers"] || []; + if (providers.length < 2) { + return
+ +
; + } + + return
+ { providers.map(idp => ( + + )) } +
; +}; + +export default SSOButtons; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index fff7bdac44d..cfa9dd23639 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1827,6 +1827,7 @@ "This address is available to use": "This address is available to use", "This address is already in use": "This address is already in use", "Room directory": "Room directory", + "Continue with %(provider)s": "Continue with %(provider)s", "Sign in with single sign-on": "Sign in with single sign-on", "And %(count)s more...|other": "And %(count)s more...", "Home": "Home", From f7d7182dc96f3f71926475fe4151d393d9dc2f28 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 24 Nov 2020 12:09:11 +0000 Subject: [PATCH 07/19] Iterate Multi-SSO support --- res/css/views/auth/_AuthBody.scss | 9 +- src/Login.ts | 8 +- src/components/structures/MatrixChat.tsx | 1 + .../structures/auth/ForgotPassword.js | 2 + src/components/structures/auth/Login.tsx | 33 ++--- .../structures/auth/Registration.tsx | 124 +++++++++++------- src/i18n/strings/en_EN.json | 2 + 7 files changed, 100 insertions(+), 79 deletions(-) diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index b51511a671c..67c8df0fa81 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -37,6 +37,10 @@ limitations under the License. color: $authpage-primary-color; } + h4 { + text-align: center; + } + a:link, a:hover, a:visited { @@ -146,15 +150,14 @@ limitations under the License. display: block; text-align: center; width: 100%; - margin-top: 24px; > a { font-weight: $font-semi-bold; } } -form + .mx_AuthBody_changeFlow { - margin-top: 0; +.mx_SSOButtons + .mx_AuthBody_changeFlow { + margin-top: 24px; } .mx_AuthBody_spinner { diff --git a/src/Login.ts b/src/Login.ts index d5776da856b..281906d8610 100644 --- a/src/Login.ts +++ b/src/Login.ts @@ -29,8 +29,8 @@ interface ILoginOptions { } // TODO: Move this to JS SDK -interface ILoginFlow { - type: "m.login.password" | "m.login.cas"; +interface IPasswordFlow { + type: "m.login.password"; } export interface IIdentityProvider { @@ -40,13 +40,13 @@ export interface IIdentityProvider { } export interface ISSOFlow { - type: "m.login.sso"; + type: "m.login.sso" | "m.login.cas"; // eslint-disable-next-line camelcase identity_providers: IIdentityProvider[]; "org.matrix.msc2858.identity_providers": IIdentityProvider[]; // Unstable prefix for MSC2858 } -export type LoginFlow = ISSOFlow | ILoginFlow; +export type LoginFlow = ISSOFlow | IPasswordFlow; // TODO: Move this to JS SDK /* eslint-disable camelcase */ diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 9fede15aa6c..32b961296b1 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -2009,6 +2009,7 @@ export default class MatrixChat extends React.PureComponent { onLoginClick={this.onLoginClick} onServerConfigChange={this.onServerConfigChange} defaultDeviceDisplayName={this.props.defaultDeviceDisplayName} + fragmentAfterLogin={fragmentAfterLogin} {...this.getServerProperties()} /> ); diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index f9f5263f7e8..e599808f0d6 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -319,6 +319,7 @@ export default class ForgotPassword extends React.Component { onChange={this.onInputChanged.bind(this, "password")} onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_focus")} onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_blur")} + autoComplete="new-password" /> CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword2_focus")} onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword2_blur")} + autoComplete="new-password" />
{_t( diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index dd1fcc4d9ad..cb09ade8959 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -438,7 +438,7 @@ export default class LoginComponent extends React.PureComponent if (supportedFlows.length > 0) { this.setState({ - currentFlow: this.getCurrentFlowStep(), + flows: supportedFlows, }); return; } @@ -520,22 +520,13 @@ export default class LoginComponent extends React.PureComponent return null; } - if (PHASES_ENABLED && this.state.phase !== Phase.ServerDetails) { - return null; - } - - const serverDetailsProps: ComponentProps = {}; - if (PHASES_ENABLED) { - serverDetailsProps.onAfterSubmit = this.onServerDetailsNextPhaseClick; - serverDetailsProps.submitText = _t("Next"); - serverDetailsProps.submitClass = "mx_Login_submit"; - } - return ; } @@ -591,15 +582,13 @@ export default class LoginComponent extends React.PureComponent const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType) as ISSOFlow; return ( -
- flow.type === "m.login.password")} - /> -
+ flow.type === "m.login.password")} + /> ); }; diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 004029c920d..45bfbcef46e 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import Matrix from 'matrix-js-sdk'; -import React, {ComponentProps, ReactNode} from 'react'; +import React, {ReactNode} from 'react'; import {MatrixClient} from "matrix-js-sdk/src/client"; import * as sdk from '../../../index'; @@ -28,8 +28,9 @@ import classNames from "classnames"; import * as Lifecycle from '../../../Lifecycle'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import AuthPage from "../../views/auth/AuthPage"; -import Login from "../../../Login"; +import Login, {ISSOFlow} from "../../../Login"; import dis from "../../../dispatcher/dispatcher"; +import SSOButtons from "../../views/elements/SSOButtons"; // Phases enum Phase { @@ -47,6 +48,7 @@ interface IProps { clientSecret?: string; sessionId?: string; idSid?: string; + fragmentAfterLogin?: string; // Called when the user has logged in. Params: // - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken @@ -116,12 +118,14 @@ interface IState { // if a different user ID to the one we just registered is logged in, // this is the user ID that's logged in. differentLoggedInUserId?: string; + // the SSO flow definition, this is fetched from /login as that's the only + // place it is exposed. + ssoFlow?: ISSOFlow; } -// Enable phases for registration -const PHASES_ENABLED = true; - export default class Registration extends React.Component { + loginLogic: Login; + constructor(props) { super(props); @@ -141,6 +145,11 @@ export default class Registration extends React.Component { serverErrorIsFatal: false, serverDeadError: "", }; + + const {hsUrl, isUrl} = this.props.serverConfig; + this.loginLogic = new Login(hsUrl, isUrl, null, { + defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used + }); } componentDidMount() { @@ -252,9 +261,21 @@ export default class Registration extends React.Component { console.log("Unable to determine is server needs id_server param", e); } + this.loginLogic.setHomeserverUrl(hsUrl); + this.loginLogic.setIdentityServerUrl(isUrl); + + let ssoFlow: ISSOFlow; + try { + const loginFlows = await this.loginLogic.getFlows(); + ssoFlow = loginFlows.find(f => f.type === "m.login.sso" || f.type === "m.login.cas") as ISSOFlow; + } catch (e) { + console.error("Failed to get login flows to check for SSO support", e); + } + this.setState({ matrixClient: cli, serverRequiresIdServer, + ssoFlow, busy: false, }); const showGenericError = (e) => { @@ -282,26 +303,16 @@ export default class Registration extends React.Component { // At this point registration is pretty much disabled, but before we do that let's // quickly check to see if the server supports SSO instead. If it does, we'll send // the user off to the login page to figure their account out. - try { - const loginLogic = new Login(hsUrl, isUrl, null, { - defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used + if (ssoFlow) { + // Redirect to login page - server probably expects SSO only + dis.dispatch({action: 'start_login'}); + } else { + this.setState({ + serverErrorIsFatal: true, // fatal because user cannot continue on this server + errorText: _t("Registration has been disabled on this homeserver."), + // add empty flows array to get rid of spinner + flows: [], }); - const flows = await loginLogic.getFlows(); - const hasSsoFlow = flows.find(f => f.type === 'm.login.sso' || f.type === 'm.login.cas'); - if (hasSsoFlow) { - // Redirect to login page - server probably expects SSO only - dis.dispatch({action: 'start_login'}); - } else { - this.setState({ - serverErrorIsFatal: true, // fatal because user cannot continue on this server - errorText: _t("Registration has been disabled on this homeserver."), - // add empty flows array to get rid of spinner - flows: [], - }); - } - } catch (e) { - console.error("Failed to get login flows to check for SSO support", e); - showGenericError(e); } } else { console.log("Unable to query for supported registration methods.", e); @@ -534,7 +545,7 @@ export default class Registration extends React.Component { // which is always shown if we allow custom URLs at all. // (if there's a fatal server error, we need to show the full server // config as the user may need to change servers to resolve the error). - if (PHASES_ENABLED && this.state.phase !== Phase.ServerDetails && !this.state.serverErrorIsFatal) { + if (this.state.phase !== Phase.ServerDetails && !this.state.serverErrorIsFatal) { return
{
; } - const serverDetailsProps: ComponentProps = {}; - if (PHASES_ENABLED) { - serverDetailsProps.onAfterSubmit = this.onServerDetailsNextPhaseClick; - serverDetailsProps.submitText = _t("Next"); - serverDetailsProps.submitClass = "mx_Login_submit"; - } - let serverDetails = null; switch (this.state.serverType) { case ServerType.FREE: @@ -559,7 +563,9 @@ export default class Registration extends React.Component { serverConfig={this.props.serverConfig} onServerConfigChange={this.props.onServerConfigChange} delayTimeMs={250} - {...serverDetailsProps} + onAfterSubmit={this.onServerDetailsNextPhaseClick} + submitText={_t("Next")} + submitClass="mx_Login_submit" />; break; case ServerType.ADVANCED: @@ -568,7 +574,9 @@ export default class Registration extends React.Component { onServerConfigChange={this.props.onServerConfigChange} delayTimeMs={250} showIdentityServerIfRequiredByHomeserver={true} - {...serverDetailsProps} + onAfterSubmit={this.onServerDetailsNextPhaseClick} + submitText={_t("Next")} + submitClass="mx_Login_submit" />; break; } @@ -583,7 +591,7 @@ export default class Registration extends React.Component { } private renderRegisterComponent() { - if (PHASES_ENABLED && this.state.phase !== Phase.Registration) { + if (this.state.phase !== Phase.Registration) { return null; } @@ -610,18 +618,35 @@ export default class Registration extends React.Component { ; } else if (this.state.flows.length) { - return ; + let ssoSection; + if (this.state.ssoFlow) { + ssoSection = +

{_t("Continue with")}

+ +

{_t("Or")}

+
; + } + + return + { ssoSection } + + ; } } @@ -658,7 +683,7 @@ export default class Registration extends React.Component { // Only show the 'go back' button if you're not looking at the form let goBack; - if ((PHASES_ENABLED && this.state.phase !== Phase.Registration) || this.state.doingUIAuth) { + if (this.state.phase !== Phase.Registration || this.state.doingUIAuth) { goBack = { _t('Go back') } ; @@ -725,8 +750,7 @@ export default class Registration extends React.Component { // If custom URLs are allowed, user is not doing UIA flows and they haven't selected the Free server type, // wire up the server details edit link. let editLink = null; - if (PHASES_ENABLED && - !SdkConfig.get()['disable_custom_urls'] && + if (!SdkConfig.get()['disable_custom_urls'] && this.state.serverType !== ServerType.FREE && !this.state.doingUIAuth ) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index baf801b57b4..f45f4c60cdd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2517,6 +2517,8 @@ "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", + "Continue with": "Continue with", + "Or": "Or", "Already have an account? Sign in here": "Already have an account? Sign in here", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", "Continue with previous account": "Continue with previous account", From 2f64160a0e2fad9163cb0859b33530e337b25683 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 24 Nov 2020 15:58:34 +0000 Subject: [PATCH 08/19] Remove backwards compatibility in ServerConfig for m.require_identity_server --- res/css/views/auth/_ServerConfig.scss | 10 --- src/PasswordReset.js | 7 -- .../structures/auth/ForgotPassword.js | 22 ------ .../structures/auth/Registration.tsx | 12 ---- .../auth/InteractiveAuthEntryComponents.js | 13 ---- .../views/auth/RegistrationForm.tsx | 41 +---------- src/components/views/auth/ServerConfig.js | 68 ------------------- 7 files changed, 3 insertions(+), 170 deletions(-) diff --git a/res/css/views/auth/_ServerConfig.scss b/res/css/views/auth/_ServerConfig.scss index a7e0057ab33..573171e4e73 100644 --- a/res/css/views/auth/_ServerConfig.scss +++ b/res/css/views/auth/_ServerConfig.scss @@ -23,13 +23,3 @@ limitations under the License. display: block; color: $warning-color; } - -.mx_ServerConfig_identityServer { - transform: scaleY(0); - transform-origin: top; - transition: transform 0.25s; - - &.mx_ServerConfig_identityServer_shown { - transform: scaleY(1); - } -} diff --git a/src/PasswordReset.js b/src/PasswordReset.js index 9472ddc6332..b38a9de9607 100644 --- a/src/PasswordReset.js +++ b/src/PasswordReset.js @@ -40,10 +40,6 @@ export default class PasswordReset { this.identityServerDomain = identityUrl ? identityUrl.split("://")[1] : null; } - doesServerRequireIdServerParam() { - return this.client.doesServerRequireIdServerParam(); - } - /** * Attempt to reset the user's password. This will trigger a side-effect of * sending an email to the provided email address. @@ -78,9 +74,6 @@ export default class PasswordReset { sid: this.sessionId, client_secret: this.clientSecret, }; - if (await this.doesServerRequireIdServerParam()) { - creds.id_server = this.identityServerDomain; - } try { await this.client.setPassword({ diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index e599808f0d6..e3bae7e38dd 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -62,7 +62,6 @@ export default class ForgotPassword extends React.Component { serverIsAlive: true, serverErrorIsFatal: false, serverDeadError: "", - serverRequiresIdServer: null, }; constructor(props) { @@ -93,12 +92,8 @@ export default class ForgotPassword extends React.Component { serverConfig.isUrl, ); - const pwReset = new PasswordReset(serverConfig.hsUrl, serverConfig.isUrl); - const serverRequiresIdServer = await pwReset.doesServerRequireIdServerParam(); - this.setState({ serverIsAlive: true, - serverRequiresIdServer, }); } catch (e) { this.setState(AutoDiscoveryUtils.authComponentStateForError(e, "forgot_password")); @@ -216,7 +211,6 @@ export default class ForgotPassword extends React.Component { serverConfig={this.props.serverConfig} onServerConfigChange={this.props.onServerConfigChange} delayTimeMs={0} - showIdentityServerIfRequiredByHomeserver={true} onAfterSubmit={this.onServerDetailsNextPhaseClick} submitText={_t("Next")} submitClass="mx_Login_submit" @@ -274,22 +268,6 @@ export default class ForgotPassword extends React.Component { ; } - if (!this.props.serverConfig.isUrl && this.state.serverRequiresIdServer) { - return
-

- {yourMatrixAccountText} - {editLink} -

- {_t( - "No identity server is configured: " + - "add one in server settings to reset your password.", - )} - - {_t('Sign in instead')} - -
; - } - return
{errorText} {serverDeadSection} diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 45bfbcef46e..a31a07a96b9 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -111,8 +111,6 @@ interface IState { // Our matrix client - part of state because we can't render the UI auth // component without it. matrixClient?: MatrixClient; - // whether the HS requires an ID server to register with a threepid - serverRequiresIdServer?: boolean; // The user ID we've just registered registeredUsername?: string; // if a different user ID to the one we just registered is logged in, @@ -254,13 +252,6 @@ export default class Registration extends React.Component { idBaseUrl: isUrl, }); - let serverRequiresIdServer = true; - try { - serverRequiresIdServer = await cli.doesServerRequireIdServerParam(); - } catch (e) { - console.log("Unable to determine is server needs id_server param", e); - } - this.loginLogic.setHomeserverUrl(hsUrl); this.loginLogic.setIdentityServerUrl(isUrl); @@ -274,7 +265,6 @@ export default class Registration extends React.Component { this.setState({ matrixClient: cli, - serverRequiresIdServer, ssoFlow, busy: false, }); @@ -573,7 +563,6 @@ export default class Registration extends React.Component { serverConfig={this.props.serverConfig} onServerConfigChange={this.props.onServerConfigChange} delayTimeMs={250} - showIdentityServerIfRequiredByHomeserver={true} onAfterSubmit={this.onServerDetailsNextPhaseClick} submitText={_t("Next")} submitClass="mx_Login_submit" @@ -644,7 +633,6 @@ export default class Registration extends React.Component { flows={this.state.flows} serverConfig={this.props.serverConfig} canSubmit={!this.state.serverErrorIsFatal} - serverRequiresIdServer={this.state.serverRequiresIdServer} /> ; } diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.js index 6628ca71205..60e57afc98f 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.js +++ b/src/components/views/auth/InteractiveAuthEntryComponents.js @@ -18,7 +18,6 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import url from 'url'; import classnames from 'classnames'; import * as sdk from '../../../index'; @@ -500,17 +499,11 @@ export class MsisdnAuthEntry extends React.Component { }); try { - const requiresIdServerParam = - await this.props.matrixClient.doesServerRequireIdServerParam(); let result; if (this._submitUrl) { result = await this.props.matrixClient.submitMsisdnTokenOtherUrl( this._submitUrl, this._sid, this.props.clientSecret, this.state.token, ); - } else if (requiresIdServerParam) { - result = await this.props.matrixClient.submitMsisdnToken( - this._sid, this.props.clientSecret, this.state.token, - ); } else { throw new Error("The registration with MSISDN flow is misconfigured"); } @@ -519,12 +512,6 @@ export class MsisdnAuthEntry extends React.Component { sid: this._sid, client_secret: this.props.clientSecret, }; - if (requiresIdServerParam) { - const idServerParsedUrl = url.parse( - this.props.matrixClient.getIdentityServerUrl(), - ); - creds.id_server = idServerParsedUrl.host; - } this.props.submitAuthDict({ type: MsisdnAuthEntry.LOGIN_TYPE, // TODO: Remove `threepid_creds` once servers support proper UIA diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 610618bb3e2..b005c8e0e29 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -54,7 +54,6 @@ interface IProps { }[]; serverConfig: ValidatedServerConfig; canSubmit?: boolean; - serverRequiresIdServer?: boolean; onRegisterClick(params: { username: string; @@ -118,21 +117,7 @@ export default class RegistrationForm extends React.PureComponent { - if (confirmed) this.doSubmit(ev); - }, - }); - } else if (this.showEmail()) { + if (this.showEmail()) { CountlyAnalytics.instance.track("onboarding_registration_submit_warn"); Modal.createTrackedDialog("Email prompt dialog", '', RegistrationEmailPromptDialog, { onFinished: async (confirmed: boolean, email?: string) => { @@ -420,11 +405,7 @@ export default class RegistrationForm extends React.PureComponent; } } - const haveIs = Boolean(this.props.serverConfig.isUrl); - let noIsText = null; - if (this.props.serverRequiresIdServer && !haveIs) { - noIsText =
- {_t( - "No identity server is configured so you cannot add an email address in order to " + - "reset your password in the future.", - )} -
; - } return (
@@ -582,7 +548,6 @@ export default class RegistrationForm extends React.PureComponent { emailHelperText } - { noIsText } { registerButton }
diff --git a/src/components/views/auth/ServerConfig.js b/src/components/views/auth/ServerConfig.js index e04bf9e25a3..448616af159 100644 --- a/src/components/views/auth/ServerConfig.js +++ b/src/components/views/auth/ServerConfig.js @@ -24,8 +24,6 @@ import { _t } from '../../../languageHandler'; import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; import SdkConfig from "../../../SdkConfig"; -import { createClient } from 'matrix-js-sdk/src/matrix'; -import classNames from 'classnames'; import CountlyAnalytics from "../../../CountlyAnalytics"; /* @@ -50,10 +48,6 @@ export default class ServerConfig extends React.PureComponent { // Optional class for the submit button. Only applies if the submit button // is to be rendered. submitClass: PropTypes.string, - - // Whether the flow this component is embedded in requires an identity - // server when the homeserver says it will need one. Default false. - showIdentityServerIfRequiredByHomeserver: PropTypes.bool, }; static defaultProps = { @@ -69,7 +63,6 @@ export default class ServerConfig extends React.PureComponent { errorText: "", hsUrl: props.serverConfig.hsUrl, isUrl: props.serverConfig.isUrl, - showIdentityServer: false, }; CountlyAnalytics.instance.track("onboarding_custom_server"); @@ -92,23 +85,6 @@ export default class ServerConfig extends React.PureComponent { return result; } - // If the UI flow this component is embedded in requires an identity - // server when the homeserver says it will need one, check first and - // reveal this field if not already shown. - // XXX: This a backward compatibility path for homeservers that require - // an identity server to be passed during certain flows. - // See also https://github.com/matrix-org/synapse/pull/5868. - if ( - this.props.showIdentityServerIfRequiredByHomeserver && - !this.state.showIdentityServer && - await this.isIdentityServerRequiredByHomeserver() - ) { - this.setState({ - showIdentityServer: true, - }); - return null; - } - return result; } @@ -165,15 +141,6 @@ export default class ServerConfig extends React.PureComponent { } } - async isIdentityServerRequiredByHomeserver() { - // XXX: We shouldn't have to create a whole new MatrixClient just to - // check if the homeserver requires an identity server... Should it be - // extracted to a static utils function...? - return createClient({ - baseUrl: this.state.hsUrl, - }).doesServerRequireIdServerParam(); - } - onHomeserverBlur = (ev) => { this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => { this.validateServer(); @@ -185,17 +152,6 @@ export default class ServerConfig extends React.PureComponent { this.setState({ hsUrl }); }; - onIdentityServerBlur = (ev) => { - this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, () => { - this.validateServer(); - }); - }; - - onIdentityServerChange = (ev) => { - const isUrl = ev.target.value; - this.setState({ isUrl }); - }; - onSubmit = async (ev) => { ev.preventDefault(); ev.stopPropagation(); @@ -239,29 +195,6 @@ export default class ServerConfig extends React.PureComponent {
; } - _renderIdentityServerSection() { - const Field = sdk.getComponent('elements.Field'); - const classes = classNames({ - "mx_ServerConfig_identityServer": true, - "mx_ServerConfig_identityServer_shown": this.state.showIdentityServer, - }); - return
- {_t("Enter your custom identity server URL What does this mean?", {}, { - a: sub => - {sub} - , - })} - -
; - } - render() { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); @@ -283,7 +216,6 @@ export default class ServerConfig extends React.PureComponent {

{_t("Other servers")}

{errorText} {this._renderHomeserverSection()} - {this._renderIdentityServerSection()} {submitButton} ); From 225d5414871c06a5ece90365e6e808924deaeec3 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 25 Nov 2020 09:19:08 +0000 Subject: [PATCH 09/19] Extend Field and InfoDialog with more configurability --- src/components/views/dialogs/InfoDialog.js | 2 ++ src/components/views/elements/Field.tsx | 27 ++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/components/views/dialogs/InfoDialog.js b/src/components/views/dialogs/InfoDialog.js index 8125bc3edd4..97ae968ff34 100644 --- a/src/components/views/dialogs/InfoDialog.js +++ b/src/components/views/dialogs/InfoDialog.js @@ -31,6 +31,7 @@ export default class InfoDialog extends React.Component { onFinished: PropTypes.func, hasCloseButton: PropTypes.bool, onKeyDown: PropTypes.func, + fixedWidth: PropTypes.bool, }; static defaultProps = { @@ -54,6 +55,7 @@ export default class InfoDialog extends React.Component { contentId='mx_Dialog_content' hasCancel={this.props.hasCloseButton} onKeyDown={this.props.onKeyDown} + fixedWidth={this.props.fixedWidth} >
{ this.props.description } diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx index 58bd5226b6e..4335cc46ace 100644 --- a/src/components/views/elements/Field.tsx +++ b/src/components/views/elements/Field.tsx @@ -61,6 +61,10 @@ interface IProps { tooltipClassName?: string; // If specified, an additional class name to apply to the field container className?: string; + // On what events should validation occur; by default on all + validateOnFocus?: boolean; + validateOnBlur?: boolean; + validateOnChange?: boolean; // All other props pass through to the . } @@ -100,6 +104,9 @@ export default class Field extends React.PureComponent { public static readonly defaultProps = { element: "input", type: "text", + validateOnFocus: true, + validateOnBlur: true, + validateOnChange: true, }; /* @@ -137,9 +144,11 @@ export default class Field extends React.PureComponent { this.setState({ focused: true, }); - this.validate({ - focused: true, - }); + if (this.props.validateOnFocus) { + this.validate({ + focused: true, + }); + } // Parent component may have supplied its own `onFocus` as well if (this.props.onFocus) { this.props.onFocus(ev); @@ -147,7 +156,9 @@ export default class Field extends React.PureComponent { }; private onChange = (ev) => { - this.validateOnChange(); + if (this.props.validateOnChange) { + this.validateOnChange(); + } // Parent component may have supplied its own `onChange` as well if (this.props.onChange) { this.props.onChange(ev); @@ -158,9 +169,11 @@ export default class Field extends React.PureComponent { this.setState({ focused: false, }); - this.validate({ - focused: false, - }); + if (this.props.validateOnBlur) { + this.validate({ + focused: false, + }); + } // Parent component may have supplied its own `onBlur` as well if (this.props.onBlur) { this.props.onBlur(ev); From 6a315e80b61f958b7d940a61fdc4ea3939294c47 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 25 Nov 2020 09:24:24 +0000 Subject: [PATCH 10/19] Improve auth error messages --- src/components/structures/auth/Login.tsx | 4 ++-- src/components/structures/auth/Registration.tsx | 2 ++ src/components/views/auth/PasswordLogin.tsx | 4 ++-- src/components/views/auth/RegistrationForm.tsx | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index cb09ade8959..f50f2167b5d 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -476,8 +476,8 @@ export default class LoginComponent extends React.PureComponent errCode = "HTTP " + err.httpStatus; } - let errorText: ReactNode = _t("Error: Problem communicating with the given homeserver.") + - (errCode ? " (" + errCode + ")" : ""); + let errorText: ReactNode = _t("There was a problem communicating with the homeserver, " + + "please try again later.") + (errCode ? " (" + errCode + ")" : ""); if (err.cors === 'rejected') { if (window.location.protocol === 'https:' && diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index a31a07a96b9..f954c50b139 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -366,6 +366,8 @@ export default class Registration extends React.Component { if (!msisdnAvailable) { msg = _t('This server does not support authentication with a phone number.'); } + } else if (response.errcode === "M_USER_IN_USE") { + msg = _t("That username already exists, please try another."); } this.setState({ busy: false, diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx index 80384ba26ea..84e583c3a5e 100644 --- a/src/components/views/auth/PasswordLogin.tsx +++ b/src/components/views/auth/PasswordLogin.tsx @@ -1,5 +1,5 @@ /* -Copyright 2015, 2016, 2017, 2019 New Vector Ltd. +Copyright 2015, 2016, 2017, 2019 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -293,7 +293,7 @@ export default class PasswordLogin extends React.PureComponent { }, { key: "number", test: ({ value }) => !value || PHONE_NUMBER_REGEX.test(value), - invalid: () => _t("Doesn't look like a valid phone number"), + invalid: () => _t("That phone number doesn't look quite right, please check and try again"), }, ], }); diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index b005c8e0e29..8c8103fd098 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -346,7 +346,7 @@ export default class RegistrationForm extends React.PureComponent !value || phoneNumberLooksValid(value), - invalid: () => _t("Doesn't look like a valid phone number"), + invalid: () => _t("That phone number doesn't look quite right, please check and try again"), }, ], }); From 758b47c64dfabdce3149b18de4d252b02933e55d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 25 Nov 2020 09:46:56 +0000 Subject: [PATCH 11/19] Replace *ServerConfig and SignInToText with ServerPicker --- res/css/_components.scss | 4 +- res/css/views/auth/_AuthBody.scss | 8 +- res/css/views/auth/_ServerConfig.scss | 25 -- res/css/views/auth/_ServerTypeSelector.scss | 69 ------ .../views/dialogs/_ServerPickerDialog.scss | 78 ++++++ res/css/views/elements/_SSOButtons.scss | 12 +- res/css/views/elements/_ServerPicker.scss | 88 +++++++ res/img/element-icons/i.svg | 3 + .../structures/auth/ForgotPassword.js | 74 +----- src/components/structures/auth/Login.tsx | 68 +----- .../structures/auth/Registration.tsx | 199 ++-------------- .../views/auth/ModularServerConfig.js | 124 ---------- src/components/views/auth/ServerConfig.js | 223 ------------------ .../views/auth/ServerTypeSelector.js | 153 ------------ src/components/views/auth/SignInToText.js | 62 ----- .../views/dialogs/ServerPickerDialog.tsx | 203 ++++++++++++++++ .../views/elements/ServerPicker.tsx | 94 ++++++++ src/i18n/strings/en_EN.json | 40 ++-- 18 files changed, 527 insertions(+), 1000 deletions(-) delete mode 100644 res/css/views/auth/_ServerConfig.scss delete mode 100644 res/css/views/auth/_ServerTypeSelector.scss create mode 100644 res/css/views/dialogs/_ServerPickerDialog.scss create mode 100644 res/css/views/elements/_ServerPicker.scss create mode 100644 res/img/element-icons/i.svg delete mode 100644 src/components/views/auth/ModularServerConfig.js delete mode 100644 src/components/views/auth/ServerConfig.js delete mode 100644 src/components/views/auth/ServerTypeSelector.js delete mode 100644 src/components/views/auth/SignInToText.js create mode 100644 src/components/views/dialogs/ServerPickerDialog.tsx create mode 100644 src/components/views/elements/ServerPicker.tsx diff --git a/res/css/_components.scss b/res/css/_components.scss index 53ca14de4aa..707f73247d7 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -45,8 +45,6 @@ @import "./views/auth/_InteractiveAuthEntryComponents.scss"; @import "./views/auth/_LanguageSelector.scss"; @import "./views/auth/_PassphraseField.scss"; -@import "./views/auth/_ServerConfig.scss"; -@import "./views/auth/_ServerTypeSelector.scss"; @import "./views/auth/_Welcome.scss"; @import "./views/avatars/_BaseAvatar.scss"; @import "./views/avatars/_DecoratedRoomAvatar.scss"; @@ -84,6 +82,7 @@ @import "./views/dialogs/_RoomUpgradeDialog.scss"; @import "./views/dialogs/_RoomUpgradeWarningDialog.scss"; @import "./views/dialogs/_ServerOfflineDialog.scss"; +@import "./views/dialogs/_ServerPickerDialog.scss"; @import "./views/dialogs/_SetEmailDialog.scss"; @import "./views/dialogs/_SettingsDialog.scss"; @import "./views/dialogs/_ShareDialog.scss"; @@ -126,6 +125,7 @@ @import "./views/elements/_RoleButton.scss"; @import "./views/elements/_RoomAliasField.scss"; @import "./views/elements/_SSOButtons.scss"; +@import "./views/elements/_ServerPicker.scss"; @import "./views/elements/_Slider.scss"; @import "./views/elements/_Spinner.scss"; @import "./views/elements/_StyledCheckbox.scss"; diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index 67c8df0fa81..8f0c758e7af 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -37,7 +37,7 @@ limitations under the License. color: $authpage-primary-color; } - h4 { + h3.mx_AuthBody_centered { text-align: center; } @@ -100,12 +100,6 @@ limitations under the License. } } -.mx_AuthBody_editServerDetails { - padding-left: 1em; - font-size: $font-12px; - font-weight: normal; -} - .mx_AuthBody_fieldRow { display: flex; margin-bottom: 10px; diff --git a/res/css/views/auth/_ServerConfig.scss b/res/css/views/auth/_ServerConfig.scss deleted file mode 100644 index 573171e4e73..00000000000 --- a/res/css/views/auth/_ServerConfig.scss +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_ServerConfig_help:link { - opacity: 0.8; -} - -.mx_ServerConfig_error { - display: block; - color: $warning-color; -} diff --git a/res/css/views/auth/_ServerTypeSelector.scss b/res/css/views/auth/_ServerTypeSelector.scss deleted file mode 100644 index fbd3d2655de..00000000000 --- a/res/css/views/auth/_ServerTypeSelector.scss +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_ServerTypeSelector { - display: flex; - margin-bottom: 28px; -} - -.mx_ServerTypeSelector_type { - margin: 0 5px; -} - -.mx_ServerTypeSelector_type:first-child { - margin-left: 0; -} - -.mx_ServerTypeSelector_type:last-child { - margin-right: 0; -} - -.mx_ServerTypeSelector_label { - text-align: center; - font-weight: 600; - color: $authpage-primary-color; - margin: 8px 0; -} - -.mx_ServerTypeSelector_type .mx_AccessibleButton { - padding: 10px; - border: 1px solid $input-border-color; - border-radius: 4px; -} - -.mx_ServerTypeSelector_type.mx_ServerTypeSelector_type_selected .mx_AccessibleButton { - border-color: $input-valid-border-color; -} - -.mx_ServerTypeSelector_logo { - display: flex; - justify-content: center; - height: 18px; - margin-bottom: 12px; - font-weight: 600; - color: $authpage-primary-color; -} - -.mx_ServerTypeSelector_logo > div { - display: flex; - width: 70%; - align-items: center; - justify-content: space-evenly; -} - -.mx_ServerTypeSelector_description { - font-size: $font-10px; -} diff --git a/res/css/views/dialogs/_ServerPickerDialog.scss b/res/css/views/dialogs/_ServerPickerDialog.scss new file mode 100644 index 00000000000..b01b49d7af3 --- /dev/null +++ b/res/css/views/dialogs/_ServerPickerDialog.scss @@ -0,0 +1,78 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_ServerPickerDialog { + width: 468px; + box-sizing: border-box; + + .mx_Dialog_content { + margin-bottom: 0; + + > p { + color: $secondary-fg-color; + font-size: $font-14px; + margin: 16px 0; + + &:first-of-type { + margin-bottom: 40px; + } + + &:last-of-type { + margin: 0 24px 24px; + } + } + + > h4 { + font-size: $font-15px; + font-weight: $font-semi-bold; + color: $secondary-fg-color; + margin-left: 8px; + } + + > a { + color: $accent-color; + margin-left: 8px; + } + } + + .mx_ServerPickerDialog_otherHomeserverRadio { + input[type="radio"] + div { + margin-top: auto; + margin-bottom: auto; + } + } + + .mx_ServerPickerDialog_otherHomeserver { + border-top: none; + border-left: none; + border-right: none; + border-radius: unset; + + > input { + padding-left: 0; + } + + > label { + margin-left: 0; + } + } + + .mx_AccessibleButton_kind_primary { + width: calc(100% - 64px); + margin: 0 8px; + padding: 15px 18px; + } +} diff --git a/res/css/views/elements/_SSOButtons.scss b/res/css/views/elements/_SSOButtons.scss index 8dc5d30257f..f762468c7f8 100644 --- a/res/css/views/elements/_SSOButtons.scss +++ b/res/css/views/elements/_SSOButtons.scss @@ -20,12 +20,15 @@ limitations under the License. .mx_SSOButton { position: relative; + width: 100%; + padding-left: 32px; + padding-right: 32px; > img { object-fit: contain; position: absolute; - left: 12px; - top: 12px; + left: 8px; + top: 4px; } } @@ -34,6 +37,11 @@ limitations under the License. width: 50px; // 48px + 1px border on all sides height: 50px; // 48px + 1px border on all sides + > img { + left: 12px; + top: 12px; + } + & + .mx_SSOButton_mini { margin-left: 24px; } diff --git a/res/css/views/elements/_ServerPicker.scss b/res/css/views/elements/_ServerPicker.scss new file mode 100644 index 00000000000..d3d56a5cd74 --- /dev/null +++ b/res/css/views/elements/_ServerPicker.scss @@ -0,0 +1,88 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.mx_ServerPicker { + margin-bottom: 14px; + border-bottom: 1px solid rgba(141, 151, 165, 0.2); + display: grid; + grid-template-columns: auto min-content; + grid-template-rows: auto auto auto; + font-size: $font-14px; + line-height: $font-20px; + + > h3 { + font-weight: $font-semi-bold; + margin: 0 0 20px; + grid-column: 1; + grid-row: 1; + } + + .mx_ServerPicker_help { + width: 20px; + height: 20px; + background-color: $icon-button-color; + border-radius: 10px; + grid-column: 2; + grid-row: 1; + margin-left: auto; + text-align: center; + color: #ffffff; + font-size: 16px; + position: relative; + + &::before { + content: ''; + width: 24px; + height: 24px; + position: absolute; + top: -2px; + left: -2px; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + mask-image: url('$(res)/img/element-icons/i.svg'); + background: #ffffff; + } + } + + .mx_ServerPicker_server { + color: $primary-fg-color; + grid-column: 1; + grid-row: 2; + margin-bottom: 16px; + } + + .mx_AccessibleButton_kind_link { + padding: 0; + font-size: inherit; + grid-column: 2; + grid-row: 2; + } + + .mx_ServerPicker_desc { + margin-top: -12px; + color: $tertiary-fg-color; + grid-column: 1 / 2; + grid-row: 3; + margin-bottom: 16px; + } +} + +.mx_ServerPicker_helpDialog { + .mx_Dialog_content { + width: 456px; + } +} diff --git a/res/img/element-icons/i.svg b/res/img/element-icons/i.svg new file mode 100644 index 00000000000..6674f1ed8d4 --- /dev/null +++ b/res/img/element-icons/i.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/structures/auth/ForgotPassword.js b/src/components/structures/auth/ForgotPassword.js index e3bae7e38dd..5a39fe9fd9a 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -21,16 +21,14 @@ import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import * as sdk from '../../../index'; import Modal from "../../../Modal"; -import SdkConfig from "../../../SdkConfig"; import PasswordReset from "../../../PasswordReset"; import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import classNames from 'classnames'; import AuthPage from "../../views/auth/AuthPage"; import CountlyAnalytics from "../../../CountlyAnalytics"; +import ServerPicker from "../../views/elements/ServerPicker"; // Phases -// Show controls to configure server details -const PHASE_SERVER_DETAILS = 0; // Show the forgot password inputs const PHASE_FORGOT = 1; // Email is in the process of being sent @@ -172,20 +170,6 @@ export default class ForgotPassword extends React.Component { }); }; - onServerDetailsNextPhaseClick = async () => { - this.setState({ - phase: PHASE_FORGOT, - }); - }; - - onEditServerDetailsClick = ev => { - ev.preventDefault(); - ev.stopPropagation(); - this.setState({ - phase: PHASE_SERVER_DETAILS, - }); - }; - onLoginClick = ev => { ev.preventDefault(); ev.stopPropagation(); @@ -200,23 +184,6 @@ export default class ForgotPassword extends React.Component { }); } - renderServerDetails() { - const ServerConfig = sdk.getComponent("auth.ServerConfig"); - - if (SdkConfig.get()['disable_custom_urls']) { - return null; - } - - return ; - } - renderForgot() { const Field = sdk.getComponent('elements.Field'); @@ -240,41 +207,13 @@ export default class ForgotPassword extends React.Component { ); } - let yourMatrixAccountText = _t('Your Matrix account on %(serverName)s', { - serverName: this.props.serverConfig.hsName, - }); - if (this.props.serverConfig.hsNameIsDifferent) { - const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip"); - - yourMatrixAccountText = _t('Your Matrix account on ', {}, { - 'underlinedServerName': () => { - return ; - }, - }); - } - - // If custom URLs are allowed, wire up the server details edit link. - let editLink = null; - if (!SdkConfig.get()['disable_custom_urls']) { - editLink = - {_t('Change')} - ; - } - return
{errorText} {serverDeadSection} -

- {yourMatrixAccountText} - {editLink} -

+
loginIncorrect: false, canTryLogin: true, - phase: Phase.Login, flows: null, username: "", @@ -369,20 +356,6 @@ export default class LoginComponent extends React.PureComponent } }; - private onServerDetailsNextPhaseClick = () => { - this.setState({ - phase: Phase.Login, - }); - }; - - private onEditServerDetailsClick = ev => { - ev.preventDefault(); - ev.stopPropagation(); - this.setState({ - phase: Phase.ServerDetails, - }); - }; - private async initLoginLogic({hsUrl, isUrl}: ValidatedServerConfig) { let isDefaultServer = false; if (this.props.serverConfig.isDefault @@ -423,13 +396,6 @@ export default class LoginComponent extends React.PureComponent busy: false, ...AutoDiscoveryUtils.authComponentStateForError(e), }); - if (this.state.serverErrorIsFatal) { - // Server is dead: show server details prompt instead - this.setState({ - phase: Phase.ServerDetails, - }); - return; - } } loginLogic.getFlows().then((flows) => { @@ -515,21 +481,6 @@ export default class LoginComponent extends React.PureComponent return errorText; } - private renderServerComponent() { - if (SdkConfig.get()['disable_custom_urls']) { - return null; - } - - return ; - } - renderLoginComponentForFlows() { if (!this.state.flows) return null; @@ -539,18 +490,8 @@ export default class LoginComponent extends React.PureComponent "m.login.sso", ]; - let onEditServerDetailsClick = null; - // If custom URLs are allowed, wire up the server details edit link. - if (!SdkConfig.get()['disable_custom_urls']) { - onEditServerDetailsClick = this.onEditServerDetailsClick; - } - const flows = order.map(type => this.state.flows.find(flow => flow.type === type)).filter(Boolean); return - { flows.map(flow => { const stepRenderer = this.stepRendererMap[flow.type]; return { stepRenderer() } @@ -654,7 +595,10 @@ export default class LoginComponent extends React.PureComponent { errorTextSection } { serverDeadSection } - { this.renderServerComponent() } + { this.renderLoginComponentForFlows() } { footer } diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index f954c50b139..bf3e4a51d34 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -22,7 +22,6 @@ import * as sdk from '../../../index'; import { _t, _td } from '../../../languageHandler'; import SdkConfig from '../../../SdkConfig'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; -import * as ServerType from '../../views/auth/ServerTypeSelector'; import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import classNames from "classnames"; import * as Lifecycle from '../../../Lifecycle'; @@ -31,14 +30,7 @@ import AuthPage from "../../views/auth/AuthPage"; import Login, {ISSOFlow} from "../../../Login"; import dis from "../../../dispatcher/dispatcher"; import SSOButtons from "../../views/elements/SSOButtons"; - -// Phases -enum Phase { - // Show controls to configure server details - ServerDetails = 0, - // Show the appropriate registration flow(s) for the server - Registration = 1, -} +import ServerPicker from '../../views/elements/ServerPicker'; interface IProps { serverConfig: ValidatedServerConfig; @@ -94,9 +86,6 @@ interface IState { // If set, we've registered but are not going to log // the user in to their new account automatically. completedNoSignin: boolean; - serverType: ServerType.FREE | ServerType.PREMIUM | ServerType.ADVANCED; - // Phase of the overall registration dialog. - phase: Phase; flows: { stages: string[]; }[]; @@ -127,7 +116,6 @@ export default class Registration extends React.Component { constructor(props) { super(props); - const serverType = ServerType.getTypeFromServerConfig(this.props.serverConfig); this.state = { busy: false, errorText: null, @@ -135,8 +123,6 @@ export default class Registration extends React.Component { email: this.props.email, }, doingUIAuth: Boolean(this.props.sessionId), - serverType, - phase: Phase.Registration, flows: null, completedNoSignin: false, serverIsAlive: true, @@ -161,61 +147,8 @@ export default class Registration extends React.Component { newProps.serverConfig.isUrl === this.props.serverConfig.isUrl) return; this.replaceClient(newProps.serverConfig); - - // Handle cases where the user enters "https://matrix.org" for their server - // from the advanced option - we should default to FREE at that point. - const serverType = ServerType.getTypeFromServerConfig(newProps.serverConfig); - if (serverType !== this.state.serverType) { - // Reset the phase to default phase for the server type. - this.setState({ - serverType, - phase: Registration.getDefaultPhaseForServerType(serverType), - }); - } - } - - private static getDefaultPhaseForServerType(type: IState["serverType"]) { - switch (type) { - case ServerType.FREE: { - // Move directly to the registration phase since the server - // details are fixed. - return Phase.Registration; - } - case ServerType.PREMIUM: - case ServerType.ADVANCED: - return Phase.ServerDetails; - } } - private onServerTypeChange = (type: IState["serverType"]) => { - this.setState({ - serverType: type, - }); - - // When changing server types, set the HS / IS URLs to reasonable defaults for the - // the new type. - switch (type) { - case ServerType.FREE: { - const { serverConfig } = ServerType.TYPES.FREE; - this.props.onServerConfigChange(serverConfig); - break; - } - case ServerType.PREMIUM: - // We can accept whatever server config was the default here as this essentially - // acts as a slightly different "custom server"/ADVANCED option. - break; - case ServerType.ADVANCED: - // Use the default config from the config - this.props.onServerConfigChange(SdkConfig.get()["validated_server_config"]); - break; - } - - // Reset the phase to default phase for the server type. - this.setState({ - phase: Registration.getDefaultPhaseForServerType(type), - }); - }; - private async replaceClient(serverConfig: ValidatedServerConfig) { this.setState({ errorText: null, @@ -456,21 +389,6 @@ export default class Registration extends React.Component { this.setState({ busy: false, doingUIAuth: false, - phase: Phase.Registration, - }); - }; - - private onServerDetailsNextPhaseClick = async () => { - this.setState({ - phase: Phase.Registration, - }); - }; - - private onEditServerDetailsClick = ev => { - ev.preventDefault(); - ev.stopPropagation(); - this.setState({ - phase: Phase.ServerDetails, }); }; @@ -520,72 +438,19 @@ export default class Registration extends React.Component { }; private renderServerComponent() { - const ServerTypeSelector = sdk.getComponent("auth.ServerTypeSelector"); - const ServerConfig = sdk.getComponent("auth.ServerConfig"); - const ModularServerConfig = sdk.getComponent("auth.ModularServerConfig"); - if (SdkConfig.get()['disable_custom_urls']) { return null; } - // Hide the server picker once the user is doing UI Auth unless encountered a fatal server error - if (this.state.phase !== Phase.ServerDetails && this.state.doingUIAuth && !this.state.serverErrorIsFatal) { - return null; - } - - // If we're on a different phase, we only show the server type selector, - // which is always shown if we allow custom URLs at all. - // (if there's a fatal server error, we need to show the full server - // config as the user may need to change servers to resolve the error). - if (this.state.phase !== Phase.ServerDetails && !this.state.serverErrorIsFatal) { - return
- -
; - } - - let serverDetails = null; - switch (this.state.serverType) { - case ServerType.FREE: - break; - case ServerType.PREMIUM: - serverDetails = ; - break; - case ServerType.ADVANCED: - serverDetails = ; - break; - } - - return
- - {serverDetails} -
; + return ; } private renderRegisterComponent() { - if (this.state.phase !== Phase.Registration) { - return null; - } - const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); const Spinner = sdk.getComponent('elements.Spinner'); const RegistrationForm = sdk.getComponent('auth.RegistrationForm'); @@ -609,17 +474,25 @@ export default class Registration extends React.Component {
; } else if (this.state.flows.length) { + let continueWithSection; + const providers = this.state.ssoFlow["org.matrix.msc2858.identity_providers"] + || this.state.ssoFlow.identity_providers || []; + // when there is only a single (or 0) providers we show a wide button with `Continue with X` text + if (providers.length > 1) { + continueWithSection =

{_t("Continue with")}

; + } + let ssoSection; if (this.state.ssoFlow) { ssoSection = -

{_t("Continue with")}

+ { continueWithSection } -

{_t("Or")}

+

{_t("Or")}

; } @@ -673,7 +546,7 @@ export default class Registration extends React.Component { // Only show the 'go back' button if you're not looking at the form let goBack; - if (this.state.phase !== Phase.Registration || this.state.doingUIAuth) { + if (this.state.doingUIAuth) { goBack = { _t('Go back') } ; @@ -719,47 +592,11 @@ export default class Registration extends React.Component { { regDoneText }
; } else { - let yourMatrixAccountText: ReactNode = _t('Create your Matrix account on %(serverName)s', { - serverName: this.props.serverConfig.hsName, - }); - if (this.props.serverConfig.hsNameIsDifferent) { - const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip"); - - yourMatrixAccountText = _t('Create your Matrix account on ', {}, { - 'underlinedServerName': () => { - return ; - }, - }); - } - - // If custom URLs are allowed, user is not doing UIA flows and they haven't selected the Free server type, - // wire up the server details edit link. - let editLink = null; - if (!SdkConfig.get()['disable_custom_urls'] && - this.state.serverType !== ServerType.FREE && - !this.state.doingUIAuth - ) { - editLink = ( - - {_t('Change')} - - ); - } - body =

{ _t('Create account') }

{ errorText } { serverDeadSection } { this.renderServerComponent() } - { this.state.phase !== Phase.ServerDetails &&

- {yourMatrixAccountText} - {editLink} -

} { this.renderRegisterComponent() } { goBack } { signIn } diff --git a/src/components/views/auth/ModularServerConfig.js b/src/components/views/auth/ModularServerConfig.js deleted file mode 100644 index 28fd16379d5..00000000000 --- a/src/components/views/auth/ModularServerConfig.js +++ /dev/null @@ -1,124 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; -import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; -import SdkConfig from "../../../SdkConfig"; -import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; -import * as ServerType from '../../views/auth/ServerTypeSelector'; -import ServerConfig from "./ServerConfig"; - -const MODULAR_URL = 'https://element.io/matrix-services' + - '?utm_source=element-web&utm_medium=web&utm_campaign=element-web-authentication'; - -// TODO: TravisR - Can this extend ServerConfig for most things? - -/* - * Configure the Modular server name. - * - * This is a variant of ServerConfig with only the HS field and different body - * text that is specific to the Modular case. - */ -export default class ModularServerConfig extends ServerConfig { - static propTypes = ServerConfig.propTypes; - - async validateAndApplyServer(hsUrl, isUrl) { - // Always try and use the defaults first - const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"]; - if (defaultConfig.hsUrl === hsUrl && defaultConfig.isUrl === isUrl) { - this.setState({busy: false, errorText: ""}); - this.props.onServerConfigChange(defaultConfig); - return defaultConfig; - } - - this.setState({ - hsUrl, - isUrl, - busy: true, - errorText: "", - }); - - try { - const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl); - this.setState({busy: false, errorText: ""}); - this.props.onServerConfigChange(result); - return result; - } catch (e) { - console.error(e); - let message = _t("Unable to validate homeserver/identity server"); - if (e.translatedMessage) { - message = e.translatedMessage; - } - this.setState({ - busy: false, - errorText: message, - }); - - return null; - } - } - - async validateServer() { - // TODO: Do we want to support .well-known lookups here? - // If for some reason someone enters "matrix.org" for a URL, we could do a lookup to - // find their homeserver without demanding they use "https://matrix.org" - return this.validateAndApplyServer(this.state.hsUrl, ServerType.TYPES.PREMIUM.identityServerUrl); - } - - render() { - const Field = sdk.getComponent('elements.Field'); - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - - const submitButton = this.props.submitText - ? {this.props.submitText} - : null; - - return ( -
-

{_t("Your server")}

- {_t( - "Enter the location of your Element Matrix Services homeserver. It may use your own " + - "domain name or be a subdomain of element.io.", - {}, { - a: sub => - {sub} - , - }, - )} - -
- -
- {submitButton} - -
- ); - } -} diff --git a/src/components/views/auth/ServerConfig.js b/src/components/views/auth/ServerConfig.js deleted file mode 100644 index 448616af159..00000000000 --- a/src/components/views/auth/ServerConfig.js +++ /dev/null @@ -1,223 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019 New Vector Ltd -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import Modal from '../../../Modal'; -import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; -import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; -import AutoDiscoveryUtils from "../../../utils/AutoDiscoveryUtils"; -import SdkConfig from "../../../SdkConfig"; -import CountlyAnalytics from "../../../CountlyAnalytics"; - -/* - * A pure UI component which displays the HS and IS to use. - */ - -export default class ServerConfig extends React.PureComponent { - static propTypes = { - onServerConfigChange: PropTypes.func.isRequired, - - // The current configuration that the user is expecting to change. - serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, - - delayTimeMs: PropTypes.number, // time to wait before invoking onChanged - - // Called after the component calls onServerConfigChange - onAfterSubmit: PropTypes.func, - - // Optional text for the submit button. If falsey, no button will be shown. - submitText: PropTypes.string, - - // Optional class for the submit button. Only applies if the submit button - // is to be rendered. - submitClass: PropTypes.string, - }; - - static defaultProps = { - onServerConfigChange: function() {}, - delayTimeMs: 0, - }; - - constructor(props) { - super(props); - - this.state = { - busy: false, - errorText: "", - hsUrl: props.serverConfig.hsUrl, - isUrl: props.serverConfig.isUrl, - }; - - CountlyAnalytics.instance.track("onboarding_custom_server"); - } - - // TODO: [REACT-WARNING] Replace with appropriate lifecycle event - UNSAFE_componentWillReceiveProps(newProps) { // eslint-disable-line camelcase - if (newProps.serverConfig.hsUrl === this.state.hsUrl && - newProps.serverConfig.isUrl === this.state.isUrl) return; - - this.validateAndApplyServer(newProps.serverConfig.hsUrl, newProps.serverConfig.isUrl); - } - - async validateServer() { - // TODO: Do we want to support .well-known lookups here? - // If for some reason someone enters "matrix.org" for a URL, we could do a lookup to - // find their homeserver without demanding they use "https://matrix.org" - const result = this.validateAndApplyServer(this.state.hsUrl, this.state.isUrl); - if (!result) { - return result; - } - - return result; - } - - async validateAndApplyServer(hsUrl, isUrl) { - // Always try and use the defaults first - const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"]; - if (defaultConfig.hsUrl === hsUrl && defaultConfig.isUrl === isUrl) { - this.setState({ - hsUrl: defaultConfig.hsUrl, - isUrl: defaultConfig.isUrl, - busy: false, - errorText: "", - }); - this.props.onServerConfigChange(defaultConfig); - return defaultConfig; - } - - this.setState({ - hsUrl, - isUrl, - busy: true, - errorText: "", - }); - - try { - const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl); - this.setState({busy: false, errorText: ""}); - this.props.onServerConfigChange(result); - return result; - } catch (e) { - console.error(e); - - const stateForError = AutoDiscoveryUtils.authComponentStateForError(e); - if (!stateForError.isFatalError) { - this.setState({ - busy: false, - }); - // carry on anyway - const result = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, isUrl, true); - this.props.onServerConfigChange(result); - return result; - } else { - let message = _t("Unable to validate homeserver/identity server"); - if (e.translatedMessage) { - message = e.translatedMessage; - } - this.setState({ - busy: false, - errorText: message, - }); - - return null; - } - } - } - - onHomeserverBlur = (ev) => { - this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, () => { - this.validateServer(); - }); - }; - - onHomeserverChange = (ev) => { - const hsUrl = ev.target.value; - this.setState({ hsUrl }); - }; - - onSubmit = async (ev) => { - ev.preventDefault(); - ev.stopPropagation(); - const result = await this.validateServer(); - if (!result) return; // Do not continue. - - if (this.props.onAfterSubmit) { - this.props.onAfterSubmit(); - } - }; - - _waitThenInvoke(existingTimeoutId, fn) { - if (existingTimeoutId) { - clearTimeout(existingTimeoutId); - } - return setTimeout(fn.bind(this), this.props.delayTimeMs); - } - - showHelpPopup = () => { - const CustomServerDialog = sdk.getComponent('auth.CustomServerDialog'); - Modal.createTrackedDialog('Custom Server Dialog', '', CustomServerDialog); - }; - - _renderHomeserverSection() { - const Field = sdk.getComponent('elements.Field'); - return
- {_t("Enter your custom homeserver URL What does this mean?", {}, { - a: sub => - {sub} - , - })} - -
; - } - - render() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - - const errorText = this.state.errorText - ? {this.state.errorText} - : null; - - const submitButton = this.props.submitText - ? {this.props.submitText} - : null; - - return ( -
-

{_t("Other servers")}

- {errorText} - {this._renderHomeserverSection()} - {submitButton} -
- ); - } -} diff --git a/src/components/views/auth/ServerTypeSelector.js b/src/components/views/auth/ServerTypeSelector.js deleted file mode 100644 index 71e7ac7f0ea..00000000000 --- a/src/components/views/auth/ServerTypeSelector.js +++ /dev/null @@ -1,153 +0,0 @@ -/* -Copyright 2019 New Vector Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { _t } from '../../../languageHandler'; -import * as sdk from '../../../index'; -import classnames from 'classnames'; -import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; -import {makeType} from "../../../utils/TypeUtils"; - -const MODULAR_URL = 'https://element.io/matrix-services' + - '?utm_source=element-web&utm_medium=web&utm_campaign=element-web-authentication'; - -export const FREE = 'Free'; -export const PREMIUM = 'Premium'; -export const ADVANCED = 'Advanced'; - -export const TYPES = { - FREE: { - id: FREE, - label: () => _t('Free'), - logo: () => , - description: () => _t('Join millions for free on the largest public server'), - serverConfig: makeType(ValidatedServerConfig, { - hsUrl: "https://matrix-client.matrix.org", - hsName: "matrix.org", - hsNameIsDifferent: false, - isUrl: "https://vector.im", - }), - }, - PREMIUM: { - id: PREMIUM, - label: () => _t('Premium'), - logo: () => , - description: () => _t('Premium hosting for organisations Learn more', {}, { - a: sub => - {sub} - , - }), - identityServerUrl: "https://vector.im", - }, - ADVANCED: { - id: ADVANCED, - label: () => _t('Advanced'), - logo: () =>
- - {_t('Other')} -
, - description: () => _t('Find other public servers or use a custom server'), - }, -}; - -export function getTypeFromServerConfig(config) { - const {hsUrl} = config; - if (!hsUrl) { - return null; - } else if (hsUrl === TYPES.FREE.serverConfig.hsUrl) { - return FREE; - } else if (new URL(hsUrl).hostname.endsWith('.modular.im')) { - // This is an unlikely case to reach, as Modular defaults to hiding the - // server type selector. - return PREMIUM; - } else { - return ADVANCED; - } -} - -export default class ServerTypeSelector extends React.PureComponent { - static propTypes = { - // The default selected type. - selected: PropTypes.string, - // Handler called when the selected type changes. - onChange: PropTypes.func.isRequired, - }; - - constructor(props) { - super(props); - - const { - selected, - } = props; - - this.state = { - selected, - }; - } - - updateSelectedType(type) { - if (this.state.selected === type) { - return; - } - this.setState({ - selected: type, - }); - if (this.props.onChange) { - this.props.onChange(type); - } - } - - onClick = (e) => { - e.stopPropagation(); - const type = e.currentTarget.dataset.id; - this.updateSelectedType(type); - }; - - render() { - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - - const serverTypes = []; - for (const type of Object.values(TYPES)) { - const { id, label, logo, description } = type; - const classes = classnames( - "mx_ServerTypeSelector_type", - `mx_ServerTypeSelector_type_${id}`, - { - "mx_ServerTypeSelector_type_selected": id === this.state.selected, - }, - ); - - serverTypes.push(
-
- {label()} -
- -
- {logo()} -
-
- {description()} -
-
-
); - } - - return
- {serverTypes} -
; - } -} diff --git a/src/components/views/auth/SignInToText.js b/src/components/views/auth/SignInToText.js deleted file mode 100644 index 7564096b7db..00000000000 --- a/src/components/views/auth/SignInToText.js +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright 2019 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import {_t} from "../../../languageHandler"; -import * as sdk from "../../../index"; -import PropTypes from "prop-types"; -import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; - -export default class SignInToText extends React.PureComponent { - static propTypes = { - serverConfig: PropTypes.instanceOf(ValidatedServerConfig).isRequired, - onEditServerDetailsClick: PropTypes.func, - }; - - render() { - let signInToText = _t('Sign in to your Matrix account on %(serverName)s', { - serverName: this.props.serverConfig.hsName, - }); - if (this.props.serverConfig.hsNameIsDifferent) { - const TextWithTooltip = sdk.getComponent("elements.TextWithTooltip"); - - signInToText = _t('Sign in to your Matrix account on ', {}, { - 'underlinedServerName': () => { - return ; - }, - }); - } - - let editLink = null; - if (this.props.onEditServerDetailsClick) { - editLink = - {_t('Change')} - ; - } - - return

- {signInToText} - {editLink} -

; - } -} diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx new file mode 100644 index 00000000000..8d3ea29be99 --- /dev/null +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -0,0 +1,203 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, {createRef} from 'react'; + +import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; +import BaseDialog from './BaseDialog'; +import { _t } from '../../../languageHandler'; +import AccessibleButton from "../elements/AccessibleButton"; +import SdkConfig from "../../../SdkConfig"; +import Field from "../elements/Field"; +import StyledRadioButton from "../elements/StyledRadioButton"; +import TextWithTooltip from "../elements/TextWithTooltip"; +import withValidation, {IFieldState} from "../elements/Validation"; + +interface IProps { + title?: string; + serverConfig: ValidatedServerConfig; + onFinished(config?: ValidatedServerConfig): void; +} + +interface IState { + defaultChosen: boolean; + otherHomeserver: string; +} + +export default class ServerPickerDialog extends React.PureComponent { + private readonly defaultServer: ValidatedServerConfig; + private readonly fieldRef = createRef(); + private validatedConf: ValidatedServerConfig; + + constructor(props) { + super(props); + + const config = SdkConfig.get(); + this.defaultServer = config["validated_server_config"] as ValidatedServerConfig; + this.state = { + defaultChosen: this.props.serverConfig.isDefault, + otherHomeserver: this.props.serverConfig.isDefault ? "" : this.props.serverConfig.hsUrl, + }; + } + + private onDefaultChosen = () => { + this.setState({ defaultChosen: true }); + }; + + private onOtherChosen = () => { + this.setState({ defaultChosen: false }); + }; + + private onHomeserverChange = (ev) => { + this.setState({ otherHomeserver: ev.target.value }); + }; + + // TODO: Do we want to support .well-known lookups here? + // If for some reason someone enters "matrix.org" for a URL, we could do a lookup to + // find their homeserver without demanding they use "https://matrix.org" + private validate = withValidation({ + deriveData: async ({ value: hsUrl }) => { + // Always try and use the defaults first + const defaultConfig: ValidatedServerConfig = SdkConfig.get()["validated_server_config"]; + if (defaultConfig.hsUrl === hsUrl) return {}; + + try { + this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl); + return {}; + } catch (e) { + console.error(e); + + const stateForError = AutoDiscoveryUtils.authComponentStateForError(e); + if (!stateForError.isFatalError) { + // carry on anyway + this.validatedConf = await AutoDiscoveryUtils.validateServerConfigWithStaticUrls(hsUrl, null, true); + return {}; + } else { + let error = _t("Unable to validate homeserver/identity server"); + if (e.translatedMessage) { + error = e.translatedMessage; + } + return { error }; + } + } + }, + rules: [ + { + key: "required", + test: ({ value, allowEmpty }) => allowEmpty || !!value, + invalid: () => _t("Specify a homeserver"), + }, { + key: "valid", + test: async function({ value }, { error }) { + if (!value) return true; + return !error; + }, + invalid: function({ error }) { + return error; + }, + }, + ], + }); + + private onHomeserverValidate = (fieldState: IFieldState) => this.validate(fieldState); + + private onSubmit = async (ev) => { + ev.preventDefault(); + + const valid = await this.fieldRef.current.validate({ allowEmpty: false }); + + if (!valid) { + this.fieldRef.current.focus(); + this.fieldRef.current.validate({ allowEmpty: false, focused: true }); + return; + } + + this.props.onFinished(this.validatedConf); // TODO verify this even works + }; + + public render() { + let text; + if (this.defaultServer.hsName === "matrix.org") { + text = _t("Matrix.org is the biggest public homeserver in the world, so it’s a good place for many."); + } + + let defaultServerName = this.defaultServer.hsName; + if (this.defaultServer.hsNameIsDifferent) { + defaultServerName = ( + + ); + } + + return +
+

+ {_t("We call the places you where you can host your account ‘homeservers’.")} {text} +

+ + + {defaultServerName} + + + + + +

+ {_t("Use your preferred Matrix homeserver if you have one, or host your own.")} +

+ + + {_t("Continue")} + + +

{_t("Learn more")}

+ + {_t("About homeservers")} + +
+
; + } +} diff --git a/src/components/views/elements/ServerPicker.tsx b/src/components/views/elements/ServerPicker.tsx new file mode 100644 index 00000000000..95ad9030b24 --- /dev/null +++ b/src/components/views/elements/ServerPicker.tsx @@ -0,0 +1,94 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; + +import AccessibleButton from "./AccessibleButton"; +import {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; +import {_t} from "../../../languageHandler"; +import TextWithTooltip from "./TextWithTooltip"; +import SdkConfig from "../../../SdkConfig"; +import Modal from "../../../Modal"; +import ServerPickerDialog from "../dialogs/ServerPickerDialog"; +import InfoDialog from "../dialogs/InfoDialog"; + +interface IProps { + title?: string; + dialogTitle?: string; + serverConfig: ValidatedServerConfig; + onServerConfigChange?(config: ValidatedServerConfig): void; +} + +const showPickerDialog = ( + title: string, + serverConfig: ValidatedServerConfig, + onFinished: (config: ValidatedServerConfig) => void, +) => { + Modal.createTrackedDialog("Server Picker", "", ServerPickerDialog, { title, serverConfig, onFinished }); +}; + +const onHelpClick = () => { + Modal.createTrackedDialog('Custom Server Dialog', '', InfoDialog, { + // TODO + title: _t("Server Options"), + description: _t("You can use the custom server options to sign into other Matrix servers by specifying " + + "a different homeserver URL. This allows you to use Element with an existing Matrix account on " + + "a different homeserver."), + button: _t("Dismiss"), + hasCloseButton: false, + fixedWidth: false, + }, "mx_ServerPicker_helpDialog"); +}; + +const ServerPicker = ({ title, dialogTitle, serverConfig, onServerConfigChange }: IProps) => { + let editBtn; + if (!SdkConfig.get()["disable_custom_urls"] && onServerConfigChange) { + const onClick = () => { + showPickerDialog(dialogTitle, serverConfig, (config?: ValidatedServerConfig) => { + if (config) { + onServerConfigChange(config); + } + }); + }; + editBtn = + {_t("Edit")} + ; + } + + let serverName = serverConfig.hsName; + if (serverConfig.hsNameIsDifferent) { + serverName = ; + } + + let desc; + if (serverConfig.hsName === "matrix.org") { + desc = + {_t("Join millions for free on the largest public server")} + ; + } + + return
+

{title || _t("Homeserver")}

+ + {serverName} + { editBtn } + { desc } +
+} + +export default ServerPicker; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f45f4c60cdd..04556b10ef1 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1887,6 +1887,10 @@ "This address is available to use": "This address is available to use", "This address is already in use": "This address is already in use", "Room directory": "Room directory", + "Server Options": "Server Options", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.", + "Join millions for free on the largest public server": "Join millions for free on the largest public server", + "Homeserver": "Homeserver", "Continue with %(provider)s": "Continue with %(provider)s", "Sign in with single sign-on": "Sign in with single sign-on", "And %(count)s more...|other": "And %(count)s more...", @@ -2143,6 +2147,15 @@ "A connection error occurred while trying to contact the server.": "A connection error occurred while trying to contact the server.", "The server is not configured to indicate what the problem is (CORS).": "The server is not configured to indicate what the problem is (CORS).", "Recent changes that have not yet been received": "Recent changes that have not yet been received", + "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", + "Specify a homeserver": "Specify a homeserver", + "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.": "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.", + "Sign into your homeserver": "Sign into your homeserver", + "We call the places you where you can host your account ‘homeservers’.": "We call the places you where you can host your account ‘homeservers’.", + "Other homeserver": "Other homeserver", + "Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.", + "Learn more": "Learn more", + "About homeservers": "About homeservers", "Sign out and remove encryption keys?": "Sign out and remove encryption keys?", "Clear Storage and Sign Out": "Clear Storage and Sign Out", "Send Logs": "Send Logs", @@ -2286,9 +2299,6 @@ "Code": "Code", "Submit": "Submit", "Start authentication": "Start authentication", - "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", - "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.", - "Server Name": "Server Name", "Enter password": "Enter password", "Nice, strong password!": "Nice, strong password!", "Password is allowed, but unsafe": "Password is allowed, but unsafe", @@ -2296,14 +2306,13 @@ "Enter username": "Enter username", "Enter email address": "Enter email address", "Enter phone number": "Enter phone number", - "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", + "That phone number doesn't look quite right, please check and try again": "That phone number doesn't look quite right, please check and try again", "Email": "Email", "Username": "Username", "Phone": "Phone", "Forgot password?": "Forgot password?", "Sign in with": "Sign in with", "Sign in": "Sign in", - "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "No identity server is configured so you cannot add an email address in order to reset your password in the future.", "Use an email address to recover your account": "Use an email address to recover your account", "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", "Passwords don't match": "Passwords don't match", @@ -2317,16 +2326,7 @@ "Use email to optionally be discoverable by existing contacts.": "Use email to optionally be discoverable by existing contacts.", "Enter your custom homeserver URL What does this mean?": "Enter your custom homeserver URL What does this mean?", "Homeserver URL": "Homeserver URL", - "Enter your custom identity server URL What does this mean?": "Enter your custom identity server URL What does this mean?", - "Identity Server URL": "Identity Server URL", "Other servers": "Other servers", - "Free": "Free", - "Join millions for free on the largest public server": "Join millions for free on the largest public server", - "Premium": "Premium", - "Premium hosting for organisations Learn more": "Premium hosting for organisations Learn more", - "Find other public servers or use a custom server": "Find other public servers or use a custom server", - "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", - "Sign in to your Matrix account on ": "Sign in to your Matrix account on ", "Sign in with SSO": "Sign in with SSO", "Couldn't load page": "Couldn't load page", "You must register to use this functionality": "You must register to use this functionality", @@ -2480,12 +2480,9 @@ "A new password must be entered.": "A new password must be entered.", "New passwords must match each other.": "New passwords must match each other.", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.", - "Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s", - "Your Matrix account on ": "Your Matrix account on ", - "No identity server is configured: add one in server settings to reset your password.": "No identity server is configured: add one in server settings to reset your password.", - "Sign in instead": "Sign in instead", "A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.", "Send Reset Email": "Send Reset Email", + "Sign in instead": "Sign in instead", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", "I have verified my email address": "I have verified my email address", "Your password has been reset.": "Your password has been reset.", @@ -2507,7 +2504,7 @@ "Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.", "Failed to perform homeserver discovery": "Failed to perform homeserver discovery", "This homeserver doesn't offer any login flows which are supported by this client.": "This homeserver doesn't offer any login flows which are supported by this client.", - "Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.", + "There was a problem communicating with the homeserver, please try again later.": "There was a problem communicating with the homeserver, please try again later.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", "Syncing...": "Syncing...", @@ -2517,6 +2514,9 @@ "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", + "That username already exists, please try another.": "That username already exists, please try another.", + "Host account on": "Host account on", + "Decide where your account is hosted": "Decide where your account is hosted", "Continue with": "Continue with", "Or": "Or", "Already have an account? Sign in here": "Already have an account? Sign in here", @@ -2525,8 +2525,6 @@ "Log in to your new account.": "Log in to your new account.", "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", - "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Create your Matrix account on ": "Create your Matrix account on ", "Create account": "Create account", "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", "Use Recovery Key": "Use Recovery Key", From 1b1c482f9cb55b1753993894bac527496862df25 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 25 Nov 2020 10:22:16 +0000 Subject: [PATCH 12/19] Iterate tests --- res/css/views/elements/_ServerPicker.scss | 2 +- .../structures/auth/Registration.tsx | 36 ++++----- .../views/elements/ServerPicker.tsx | 2 +- test/components/structures/auth/Login-test.js | 77 ++++++++++++++++--- .../structures/auth/Registration-test.js | 30 ++++++-- 5 files changed, 108 insertions(+), 39 deletions(-) diff --git a/res/css/views/elements/_ServerPicker.scss b/res/css/views/elements/_ServerPicker.scss index d3d56a5cd74..ae1e445a9fb 100644 --- a/res/css/views/elements/_ServerPicker.scss +++ b/res/css/views/elements/_ServerPicker.scss @@ -65,7 +65,7 @@ limitations under the License. margin-bottom: 16px; } - .mx_AccessibleButton_kind_link { + .mx_ServerPicker_change { padding: 0; font-size: inherit; grid-column: 2; diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index bf3e4a51d34..750e4bb88be 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -437,19 +437,6 @@ export default class Registration extends React.Component { } }; - private renderServerComponent() { - if (SdkConfig.get()['disable_custom_urls']) { - return null; - } - - return ; - } - private renderRegisterComponent() { const InteractiveAuth = sdk.getComponent('structures.InteractiveAuth'); const Spinner = sdk.getComponent('elements.Spinner'); @@ -474,16 +461,16 @@ export default class Registration extends React.Component {
; } else if (this.state.flows.length) { - let continueWithSection; - const providers = this.state.ssoFlow["org.matrix.msc2858.identity_providers"] - || this.state.ssoFlow.identity_providers || []; - // when there is only a single (or 0) providers we show a wide button with `Continue with X` text - if (providers.length > 1) { - continueWithSection =

{_t("Continue with")}

; - } - let ssoSection; if (this.state.ssoFlow) { + let continueWithSection; + const providers = this.state.ssoFlow["org.matrix.msc2858.identity_providers"] + || this.state.ssoFlow["identity_providers"] || []; + // when there is only a single (or 0) providers we show a wide button with `Continue with X` text + if (providers.length > 1) { + continueWithSection =

{_t("Continue with")}

; + } + ssoSection = { continueWithSection } {

{ _t('Create account') }

{ errorText } { serverDeadSection } - { this.renderServerComponent() } + { this.renderRegisterComponent() } { goBack } { signIn } diff --git a/src/components/views/elements/ServerPicker.tsx b/src/components/views/elements/ServerPicker.tsx index 95ad9030b24..b7fe7e8e849 100644 --- a/src/components/views/elements/ServerPicker.tsx +++ b/src/components/views/elements/ServerPicker.tsx @@ -63,7 +63,7 @@ const ServerPicker = ({ title, dialogTitle, serverConfig, onServerConfigChange } } }); }; - editBtn = + editBtn = {_t("Edit")} ; } diff --git a/test/components/structures/auth/Login-test.js b/test/components/structures/auth/Login-test.js index 7ca210ff93f..0631e26cbda 100644 --- a/test/components/structures/auth/Login-test.js +++ b/test/components/structures/auth/Login-test.js @@ -52,7 +52,7 @@ describe('Login', function() { // Set non-empty flows & matrixClient to get past the loading spinner root.setState({ - currentFlow: "m.login.password", + flows: [{ type: "m.login.password" }], }); const form = ReactTestUtils.findRenderedComponentWithType( @@ -61,10 +61,7 @@ describe('Login', function() { ); expect(form).toBeTruthy(); - const changeServerLink = ReactTestUtils.findRenderedDOMComponentWithClass( - root, - 'mx_AuthBody_editServerDetails', - ); + const changeServerLink = ReactTestUtils.findRenderedDOMComponentWithClass(root, 'mx_ServerPicker_change'); expect(changeServerLink).toBeTruthy(); }); @@ -77,7 +74,7 @@ describe('Login', function() { // Set non-empty flows & matrixClient to get past the loading spinner root.setState({ - currentFlow: "m.login.password", + flows: [{ type: "m.login.password" }], }); const form = ReactTestUtils.findRenderedComponentWithType( @@ -86,10 +83,70 @@ describe('Login', function() { ); expect(form).toBeTruthy(); - const changeServerLinks = ReactTestUtils.scryRenderedDOMComponentsWithClass( - root, - 'mx_AuthBody_editServerDetails', - ); + const changeServerLinks = ReactTestUtils.scryRenderedDOMComponentsWithClass(root, 'mx_ServerPicker_change'); expect(changeServerLinks).toHaveLength(0); }); + + it("should show SSO button if that flow is available", () => { + jest.spyOn(SdkConfig, "get").mockReturnValue({ + disable_custom_urls: true, + }); + + const root = render(); + + // Set non-empty flows & matrixClient to get past the loading spinner + root.setState({ + flows: [{ type: "m.login.sso" }], + }); + + const ssoButton = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_SSOButton"); + expect(ssoButton).toBeTruthy(); + }); + + it("should show both SSO button and username+password if both are available", () => { + jest.spyOn(SdkConfig, "get").mockReturnValue({ + disable_custom_urls: true, + }); + + const root = render(); + + // Set non-empty flows & matrixClient to get past the loading spinner + root.setState({ + flows: [{ type: "m.login.password" }, { type: "m.login.sso" }], + }); + + const form = ReactTestUtils.findRenderedComponentWithType(root, sdk.getComponent('auth.PasswordLogin')); + expect(form).toBeTruthy(); + + const ssoButton = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_SSOButton"); + expect(ssoButton).toBeTruthy(); + }); + + it("should show multiple SSO buttons if multiple identity_providers are available", () => { + jest.spyOn(SdkConfig, "get").mockReturnValue({ + disable_custom_urls: true, + }); + + const root = render(); + + // Set non-empty flows & matrixClient to get past the loading spinner + root.setState({ + flows: [{ + type: "m.login.sso", + identity_providers: [{ + id: "a", + name: "Provider 1", + }, { + id: "b", + name: "Provider 2", + }, { + id: "c", + name: "Provider 3", + }], + }], + }); + + const ssoButtons = ReactTestUtils.scryRenderedDOMComponentsWithClass(root, "mx_SSOButton"); + expect(ssoButtons.length).toBe(3); + }); }); diff --git a/test/components/structures/auth/Registration-test.js b/test/components/structures/auth/Registration-test.js index bf26763a794..3e8e887329c 100644 --- a/test/components/structures/auth/Registration-test.js +++ b/test/components/structures/auth/Registration-test.js @@ -48,12 +48,9 @@ describe('Registration', function() { />, parentDiv); } - it('should show server type selector', function() { + it('should show server picker', function() { const root = render(); - const selector = ReactTestUtils.findRenderedComponentWithType( - root, - sdk.getComponent('auth.ServerTypeSelector'), - ); + const selector = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_ServerPicker"); expect(selector).toBeTruthy(); }); @@ -79,4 +76,27 @@ describe('Registration', function() { ); expect(form).toBeTruthy(); }); + + it("should show SSO options if those are available", () => { + jest.spyOn(SdkConfig, "get").mockReturnValue({ + disable_custom_urls: true, + }); + + const root = render(); + + // Set non-empty flows & matrixClient to get past the loading spinner + root.setState({ + flows: [{ + stages: [], + }], + ssoFlow: { + type: "m.login.sso", + }, + matrixClient: {}, + busy: false, + }); + + const ssoButton = ReactTestUtils.findRenderedDOMComponentWithClass(root, "mx_SSOButton"); + expect(ssoButton).toBeTruthy(); + }); }); From c4084196d11c87870dcbde57d3e96641060c8f5f Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 25 Nov 2020 10:39:44 +0000 Subject: [PATCH 13/19] delint --- .../structures/auth/Registration.tsx | 1 - .../views/auth/RegistrationForm.tsx | 1 - .../views/dialogs/ServerPickerDialog.tsx | 2 +- src/i18n/strings/en_EN.json | 7 ++----- test/end-to-end-tests/src/usecases/signup.js | 20 ++++--------------- 5 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 750e4bb88be..512972d0b42 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -20,7 +20,6 @@ import {MatrixClient} from "matrix-js-sdk/src/client"; import * as sdk from '../../../index'; import { _t, _td } from '../../../languageHandler'; -import SdkConfig from '../../../SdkConfig'; import { messageForResourceLimitError } from '../../../utils/ErrorUtils'; import AutoDiscoveryUtils, {ValidatedServerConfig} from "../../../utils/AutoDiscoveryUtils"; import classNames from "classnames"; diff --git a/src/components/views/auth/RegistrationForm.tsx b/src/components/views/auth/RegistrationForm.tsx index 8c8103fd098..a0c7ab7b4f7 100644 --- a/src/components/views/auth/RegistrationForm.tsx +++ b/src/components/views/auth/RegistrationForm.tsx @@ -30,7 +30,6 @@ import PassphraseField from "./PassphraseField"; import CountlyAnalytics from "../../../CountlyAnalytics"; import Field from '../elements/Field'; import RegistrationEmailPromptDialog from '../dialogs/RegistrationEmailPromptDialog'; -import QuestionDialog from '../dialogs/QuestionDialog'; enum RegistrationField { Email = "field_email", diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index 8d3ea29be99..5a3a08670f1 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -189,7 +189,7 @@ export default class ServerPickerDialog extends React.PureComponent - + {_t("Continue")} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 04556b10ef1..e008f4d3656 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2324,9 +2324,6 @@ "Add an email to be able to reset your password.": "Add an email to be able to reset your password.", "Use email or phone to optionally be discoverable by existing contacts.": "Use email or phone to optionally be discoverable by existing contacts.", "Use email to optionally be discoverable by existing contacts.": "Use email to optionally be discoverable by existing contacts.", - "Enter your custom homeserver URL What does this mean?": "Enter your custom homeserver URL What does this mean?", - "Homeserver URL": "Homeserver URL", - "Other servers": "Other servers", "Sign in with SSO": "Sign in with SSO", "Couldn't load page": "Couldn't load page", "You must register to use this functionality": "You must register to use this functionality", @@ -2515,8 +2512,6 @@ "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", "That username already exists, please try another.": "That username already exists, please try another.", - "Host account on": "Host account on", - "Decide where your account is hosted": "Decide where your account is hosted", "Continue with": "Continue with", "Or": "Or", "Already have an account? Sign in here": "Already have an account? Sign in here", @@ -2526,6 +2521,8 @@ "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", "Create account": "Create account", + "Host account on": "Host account on", + "Decide where your account is hosted": "Decide where your account is hosted", "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", "Use Recovery Key": "Use Recovery Key", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.", diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index ef8a259091d..dd4404f55c5 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -22,24 +22,12 @@ module.exports = async function signup(session, username, password, homeserver) await session.goto(session.url('/#/register')); // change the homeserver by clicking the advanced section if (homeserver) { - const advancedButton = await session.query('.mx_ServerTypeSelector_type_Advanced'); - await advancedButton.click(); + const changeButton = await session.query('.mx_ServerPicker_change'); + await changeButton.click(); - // depending on what HS is configured as the default, the advanced registration - // goes the HS/IS entry directly (for matrix.org) or takes you to the user/pass entry (not matrix.org). - // To work with both, we look for the "Change" link in the user/pass entry but don't fail when we can't find it - // As this link should be visible immediately, and to not slow down the case where it isn't present, - // pick a lower timeout of 5000ms - try { - const changeHsField = await session.query('.mx_AuthBody_editServerDetails', 5000); - if (changeHsField) { - await changeHsField.click(); - } - } catch (err) {} - - const hsInputField = await session.query('#mx_ServerConfig_hsUrl'); + const hsInputField = await session.query('.mx_ServerPickerDialog_otherHomeserver'); await session.replaceInputText(hsInputField, homeserver); - const nextButton = await session.query('.mx_Login_submit'); + const nextButton = await session.query('.mx_ServerPickerDialog_continue'); // accept homeserver await nextButton.click(); } From 3bdedd73f77eae1611accbfe6b4e88a40e053215 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 25 Nov 2020 11:38:43 +0000 Subject: [PATCH 14/19] fix another test --- test/end-to-end-tests/src/usecases/signup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js index dd4404f55c5..804cee9599a 100644 --- a/test/end-to-end-tests/src/usecases/signup.js +++ b/test/end-to-end-tests/src/usecases/signup.js @@ -56,7 +56,7 @@ module.exports = async function signup(session, username, password, homeserver) await registerButton.click(); //confirm dialog saying you cant log back in without e-mail - const continueButton = await session.query('.mx_QuestionDialog button.mx_Dialog_primary'); + const continueButton = await session.query('.mx_RegistrationEmailPromptDialog button.mx_Dialog_primary'); await continueButton.click(); //find the privacy policy checkbox and check it From 86025459f40c661e4118d54d9e34eaaf19773e43 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 26 Nov 2020 15:01:12 +0000 Subject: [PATCH 15/19] Remove unused dialog, todo comments and other tiny tweaks --- .../views/auth/CustomServerDialog.js | 47 ------------------- .../dialogs/RegistrationEmailPromptDialog.tsx | 2 +- .../views/dialogs/ServerPickerDialog.tsx | 2 +- src/components/views/elements/SSOButtons.tsx | 1 - .../views/elements/ServerPicker.tsx | 1 - 5 files changed, 2 insertions(+), 51 deletions(-) delete mode 100644 src/components/views/auth/CustomServerDialog.js diff --git a/src/components/views/auth/CustomServerDialog.js b/src/components/views/auth/CustomServerDialog.js deleted file mode 100644 index 138f8c4689f..00000000000 --- a/src/components/views/auth/CustomServerDialog.js +++ /dev/null @@ -1,47 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from 'react'; -import { _t } from '../../../languageHandler'; -import SdkConfig from '../../../SdkConfig'; - -export default class CustomServerDialog extends React.Component { - render() { - const brand = SdkConfig.get().brand; - return ( -
-
- { _t("Custom Server Options") } -
-
-

{_t( - "You can use the custom server options to sign into other " + - "Matrix servers by specifying a different homeserver URL. This " + - "allows you to use %(brand)s with an existing Matrix account on a " + - "different homeserver.", - { brand }, - )}

-
-
- -
-
- ); - } -} diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx index 8e91a07bf5c..b7cc81c1134 100644 --- a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx +++ b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx @@ -67,7 +67,7 @@ const RegistrationEmailPromptDialog: React.FC = ({onFinished}) => { >

{_t("Just a heads up, if you don't add an email and forget your password, you could " + - "permanently lose access to your account.", {}, { + "permanently lose access to your account.", {}, { b: sub => {sub}, })}

diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx index 5a3a08670f1..9eb819c98ee 100644 --- a/src/components/views/dialogs/ServerPickerDialog.tsx +++ b/src/components/views/dialogs/ServerPickerDialog.tsx @@ -125,7 +125,7 @@ export default class ServerPickerDialog extends React.PureComponent = ({ let icon; if (idp && idp.icon && idp.icon.startsWith("https://")) { - // TODO sanitize images icon = {label}; } diff --git a/src/components/views/elements/ServerPicker.tsx b/src/components/views/elements/ServerPicker.tsx index b7fe7e8e849..7637ab7b8da 100644 --- a/src/components/views/elements/ServerPicker.tsx +++ b/src/components/views/elements/ServerPicker.tsx @@ -42,7 +42,6 @@ const showPickerDialog = ( const onHelpClick = () => { Modal.createTrackedDialog('Custom Server Dialog', '', InfoDialog, { - // TODO title: _t("Server Options"), description: _t("You can use the custom server options to sign into other Matrix servers by specifying " + "a different homeserver URL. This allows you to use Element with an existing Matrix account on " + From 5f03cbd88fc3e0546f2d76b32e9d22901afdac38 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 26 Nov 2020 15:45:15 +0000 Subject: [PATCH 16/19] Iterate PR some more --- src/components/structures/auth/Login.tsx | 11 ++++++----- src/i18n/strings/en_EN.json | 4 +--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 9e2105d0c25..606aeb44ab2 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -341,13 +341,14 @@ export default class LoginComponent extends React.PureComponent onTryRegisterClick = ev => { const hasPasswordFlow = this.state.flows.find(flow => flow.type === "m.login.password"); - if (!hasPasswordFlow) { - // If we're showing SSO it means that registration is also probably disabled, - // so intercept the click and instead pretend the user clicked 'Sign in with SSO'. + 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 step = this.state.flows.find(flow => flow.type === "m.login.sso" || flow.type === "m.login.cas"); - const ssoKind = step.type === 'm.login.sso' ? 'sso' : 'cas'; + const ssoKind = ssoFlow.type === 'm.login.sso' ? 'sso' : 'cas'; PlatformPeg.get().startSingleSignOn(this.loginLogic.createTemporaryClient(), ssoKind, this.props.fragmentAfterLogin); } else { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index e008f4d3656..4ae0019e5ea 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2112,7 +2112,7 @@ "This wasn't me": "This wasn't me", "Doesn't look like a valid email address": "Doesn't look like a valid email address", "Continuing without email": "Continuing without email", - "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.": "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.", + "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.": "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.", "Email (optional)": "Email (optional)", "Please fill why you're reporting.": "Please fill why you're reporting.", "Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator", @@ -2284,8 +2284,6 @@ "powered by Matrix": "powered by Matrix", "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", "Country Dropdown": "Country Dropdown", - "Custom Server Options": "Custom Server Options", - "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.", "Confirm your identity by entering your account password below.": "Confirm your identity by entering your account password below.", "Password": "Password", "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.", From 26e1cdb82ccd59fcc1c08335f543a71577a954ae Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 1 Dec 2020 12:04:41 +0000 Subject: [PATCH 17/19] Update i18n --- src/components/structures/auth/Registration.tsx | 10 ++++++++-- src/i18n/strings/en_EN.json | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 512972d0b42..e1a2fc55901 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -467,9 +467,13 @@ export default class Registration extends React.Component { || this.state.ssoFlow["identity_providers"] || []; // when there is only a single (or 0) providers we show a wide button with `Continue with X` text if (providers.length > 1) { - continueWithSection =

{_t("Continue with")}

; + // i18n: ssoButtons is a placeholder to help translators understand context + continueWithSection =

+ { _t("Continue with %(ssoButtons)s", { ssoButtons: "" }).trim() } +

; } + // i18n: ssoButtons & usernamePassword are placeholders to help translators understand context ssoSection = { continueWithSection } { loginType={this.state.ssoFlow.type === "m.login.sso" ? "sso" : "cas"} fragmentAfterLogin={this.props.fragmentAfterLogin} /> -

{_t("Or")}

+

+ { _t("%(ssoButtons)s Or %(usernamePassword)s", { ssoButtons: "", usernamePassword: ""}).trim() } +

; } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4ae0019e5ea..5760cf9ca10 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2510,8 +2510,8 @@ "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", "That username already exists, please try another.": "That username already exists, please try another.", - "Continue with": "Continue with", - "Or": "Or", + "Continue with %(ssoButtons)s": "Continue with %(ssoButtons)s", + "%(ssoButtons)s Or %(usernamePassword)s": "%(ssoButtons)s Or %(usernamePassword)s", "Already have an account? Sign in here": "Already have an account? Sign in here", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", "Continue with previous account": "Continue with previous account", From e0b68441bc4040b39941ca0fefbc348309c58852 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 1 Dec 2020 16:39:26 +0000 Subject: [PATCH 18/19] i18n --- src/i18n/strings/en_EN.json | 68 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index b33cbffb8fa..81fc932fc7f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1897,6 +1897,11 @@ "This address is available to use": "This address is available to use", "This address is already in use": "This address is already in use", "Room directory": "Room directory", + "Server Options": "Server Options", + "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use Element with an existing Matrix account on a different homeserver.", + "Join millions for free on the largest public server": "Join millions for free on the largest public server", + "Homeserver": "Homeserver", + "Continue with %(provider)s": "Continue with %(provider)s", "Sign in with single sign-on": "Sign in with single sign-on", "And %(count)s more...|other": "And %(count)s more...", "Home": "Home", @@ -2113,6 +2118,10 @@ "Use this session to verify your new one, granting it access to encrypted messages:": "Use this session to verify your new one, granting it access to encrypted messages:", "If you didn’t sign in to this session, your account may be compromised.": "If you didn’t sign in to this session, your account may be compromised.", "This wasn't me": "This wasn't me", + "Doesn't look like a valid email address": "Doesn't look like a valid email address", + "Continuing without email": "Continuing without email", + "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.": "Just a heads up, if you don't add an email and forget your password, you could permanently lose access to your account.", + "Email (optional)": "Email (optional)", "Please fill why you're reporting.": "Please fill why you're reporting.", "Report Content to Your Homeserver Administrator": "Report Content to Your Homeserver Administrator", "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.": "Reporting this message will send its unique 'event ID' to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.", @@ -2146,6 +2155,15 @@ "A connection error occurred while trying to contact the server.": "A connection error occurred while trying to contact the server.", "The server is not configured to indicate what the problem is (CORS).": "The server is not configured to indicate what the problem is (CORS).", "Recent changes that have not yet been received": "Recent changes that have not yet been received", + "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", + "Specify a homeserver": "Specify a homeserver", + "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.": "Matrix.org is the biggest public homeserver in the world, so it’s a good place for many.", + "Sign into your homeserver": "Sign into your homeserver", + "We call the places you where you can host your account ‘homeservers’.": "We call the places you where you can host your account ‘homeservers’.", + "Other homeserver": "Other homeserver", + "Use your preferred Matrix homeserver if you have one, or host your own.": "Use your preferred Matrix homeserver if you have one, or host your own.", + "Learn more": "Learn more", + "About homeservers": "About homeservers", "Sign out and remove encryption keys?": "Sign out and remove encryption keys?", "Clear Storage and Sign Out": "Clear Storage and Sign Out", "Send Logs": "Send Logs", @@ -2274,8 +2292,6 @@ "powered by Matrix": "powered by Matrix", "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", "Country Dropdown": "Country Dropdown", - "Custom Server Options": "Custom Server Options", - "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.": "You can use the custom server options to sign into other Matrix servers by specifying a different homeserver URL. This allows you to use %(brand)s with an existing Matrix account on a different homeserver.", "Confirm your identity by entering your account password below.": "Confirm your identity by entering your account password below.", "Password": "Password", "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.": "Missing captcha public key in homeserver configuration. Please report this to your homeserver administrator.", @@ -2289,48 +2305,30 @@ "Code": "Code", "Submit": "Submit", "Start authentication": "Start authentication", - "Unable to validate homeserver/identity server": "Unable to validate homeserver/identity server", - "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.": "Enter the location of your Element Matrix Services homeserver. It may use your own domain name or be a subdomain of element.io.", - "Server Name": "Server Name", "Enter password": "Enter password", "Nice, strong password!": "Nice, strong password!", "Password is allowed, but unsafe": "Password is allowed, but unsafe", "Keep going...": "Keep going...", "Enter username": "Enter username", "Enter email address": "Enter email address", - "Doesn't look like a valid email address": "Doesn't look like a valid email address", "Enter phone number": "Enter phone number", - "Doesn't look like a valid phone number": "Doesn't look like a valid phone number", + "That phone number doesn't look quite right, please check and try again": "That phone number doesn't look quite right, please check and try again", "Email": "Email", "Username": "Username", "Phone": "Phone", - "Not sure of your password? Set a new one": "Not sure of your password? Set a new one", + "Forgot password?": "Forgot password?", "Sign in with": "Sign in with", "Sign in": "Sign in", - "No identity server is configured so you cannot add an email address in order to reset your password in the future.": "No identity server is configured so you cannot add an email address in order to reset your password in the future.", - "If you don't specify an email address, you won't be able to reset your password. Are you sure?": "If you don't specify an email address, you won't be able to reset your password. Are you sure?", "Use an email address to recover your account": "Use an email address to recover your account", "Enter email address (required on this homeserver)": "Enter email address (required on this homeserver)", "Other users can invite you to rooms using your contact details": "Other users can invite you to rooms using your contact details", "Enter phone number (required on this homeserver)": "Enter phone number (required on this homeserver)", "Use lowercase letters, numbers, dashes and underscores only": "Use lowercase letters, numbers, dashes and underscores only", - "Email (optional)": "Email (optional)", "Phone (optional)": "Phone (optional)", "Register": "Register", - "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email or phone to optionally be discoverable by existing contacts.", - "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.": "Set an email for account recovery. Use email to optionally be discoverable by existing contacts.", - "Enter your custom homeserver URL What does this mean?": "Enter your custom homeserver URL What does this mean?", - "Homeserver URL": "Homeserver URL", - "Enter your custom identity server URL What does this mean?": "Enter your custom identity server URL What does this mean?", - "Identity Server URL": "Identity Server URL", - "Other servers": "Other servers", - "Free": "Free", - "Join millions for free on the largest public server": "Join millions for free on the largest public server", - "Premium": "Premium", - "Premium hosting for organisations Learn more": "Premium hosting for organisations Learn more", - "Find other public servers or use a custom server": "Find other public servers or use a custom server", - "Sign in to your Matrix account on %(serverName)s": "Sign in to your Matrix account on %(serverName)s", - "Sign in to your Matrix account on ": "Sign in to your Matrix account on ", + "Add an email to be able to reset your password.": "Add an email to be able to reset your password.", + "Use email or phone to optionally be discoverable by existing contacts.": "Use email or phone to optionally be discoverable by existing contacts.", + "Use email to optionally be discoverable by existing contacts.": "Use email to optionally be discoverable by existing contacts.", "Sign in with SSO": "Sign in with SSO", "Couldn't load page": "Couldn't load page", "You must register to use this functionality": "You must register to use this functionality", @@ -2377,7 +2375,6 @@ "Everyone": "Everyone", "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members.
Click here to open settings and give it one!", "Long Description (HTML)": "Long Description (HTML)", - "Upload avatar": "Upload avatar", "Description": "Description", "Community %(groupId)s not found": "Community %(groupId)s not found", "This homeserver does not support communities": "This homeserver does not support communities", @@ -2486,13 +2483,10 @@ "A new password must be entered.": "A new password must be entered.", "New passwords must match each other.": "New passwords must match each other.", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.", - "Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s", - "Your Matrix account on ": "Your Matrix account on ", - "No identity server is configured: add one in server settings to reset your password.": "No identity server is configured: add one in server settings to reset your password.", - "Sign in instead": "Sign in instead", "New Password": "New Password", "A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.", "Send Reset Email": "Send Reset Email", + "Sign in instead": "Sign in instead", "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.": "An email has been sent to %(emailAddress)s. Once you've followed the link it contains, click below.", "I have verified my email address": "I have verified my email address", "Your password has been reset.": "Your password has been reset.", @@ -2514,24 +2508,28 @@ "Please note you are logging into the %(hs)s server, not matrix.org.": "Please note you are logging into the %(hs)s server, not matrix.org.", "Failed to perform homeserver discovery": "Failed to perform homeserver discovery", "This homeserver doesn't offer any login flows which are supported by this client.": "This homeserver doesn't offer any login flows which are supported by this client.", - "Error: Problem communicating with the given homeserver.": "Error: Problem communicating with the given homeserver.", + "There was a problem communicating with the homeserver, please try again later.": "There was a problem communicating with the homeserver, please try again later.", "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.": "Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. Either use HTTPS or enable unsafe scripts.", "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.": "Can't connect to homeserver - please check your connectivity, ensure your homeserver's SSL certificate is trusted, and that a browser extension is not blocking requests.", "Syncing...": "Syncing...", "Signing In...": "Signing In...", "If you've joined lots of rooms, this might take a while": "If you've joined lots of rooms, this might take a while", - "Create account": "Create account", + "New? Create account": "New? Create account", "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", + "That username already exists, please try another.": "That username already exists, please try another.", + "Continue with %(ssoButtons)s": "Continue with %(ssoButtons)s", + "%(ssoButtons)s Or %(usernamePassword)s": "%(ssoButtons)s Or %(usernamePassword)s", + "Already have an account? Sign in here": "Already have an account? Sign in here", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", "Continue with previous account": "Continue with previous account", "Log in to your new account.": "Log in to your new account.", "You can now close this window or log in to your new account.": "You can now close this window or log in to your new account.", "Registration Successful": "Registration Successful", - "Create your Matrix account on %(serverName)s": "Create your Matrix account on %(serverName)s", - "Create your Matrix account on ": "Create your Matrix account on ", - "Create your account": "Create your account", + "Create account": "Create account", + "Host account on": "Host account on", + "Decide where your account is hosted": "Decide where your account is hosted", "Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase", "Use Recovery Key": "Use Recovery Key", "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.", From 8593845e3d1120bb8d9f7f9744f4d9a3a809a578 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 2 Dec 2020 15:12:16 +0000 Subject: [PATCH 19/19] i18n --- src/i18n/strings/en_EN.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index ec822117891..d44c01756a0 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2484,10 +2484,6 @@ "A new password must be entered.": "A new password must be entered.", "New passwords must match each other.": "New passwords must match each other.", "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.": "Changing your password will reset any end-to-end encryption keys on all of your sessions, making encrypted chat history unreadable. Set up Key Backup or export your room keys from another session before resetting your password.", - "Your Matrix account on %(serverName)s": "Your Matrix account on %(serverName)s", - "Your Matrix account on ": "Your Matrix account on ", - "No identity server is configured: add one in server settings to reset your password.": "No identity server is configured: add one in server settings to reset your password.", - "Sign in instead": "Sign in instead", "New Password": "New Password", "A verification email will be sent to your inbox to confirm setting your new password.": "A verification email will be sent to your inbox to confirm setting your new password.", "Send Reset Email": "Send Reset Email",