diff --git a/res/css/_components.scss b/res/css/_components.scss
index 445ed70ff41..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";
@@ -78,11 +76,13 @@
@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";
@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";
@@ -124,6 +124,8 @@
@import "./views/elements/_RichText.scss";
@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/structures/auth/_Login.scss b/res/css/structures/auth/_Login.scss
index 02436833a2e..a8cb7d7eee7 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;
@@ -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;
@@ -91,6 +85,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..8f0c758e7af 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;
}
+ h3.mx_AuthBody_centered {
+ text-align: center;
+ }
+
a:link,
a:hover,
a:visited {
@@ -96,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;
@@ -146,6 +144,14 @@ limitations under the License.
display: block;
text-align: center;
width: 100%;
+
+ > a {
+ font-weight: $font-semi-bold;
+ }
+}
+
+.mx_SSOButtons + .mx_AuthBody_changeFlow {
+ margin-top: 24px;
}
.mx_AuthBody_spinner {
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/auth/_ServerConfig.scss b/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss
similarity index 57%
rename from res/css/views/auth/_ServerConfig.scss
rename to res/css/views/dialogs/_RegistrationEmailPromptDialog.scss
index a7e0057ab33..31fc6d7a047 100644
--- a/res/css/views/auth/_ServerConfig.scss
+++ b/res/css/views/dialogs/_RegistrationEmailPromptDialog.scss
@@ -1,6 +1,5 @@
/*
-Copyright 2015, 2016 OpenMarket Ltd
-Copyright 2019 The Matrix.org Foundation C.I.C.
+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.
@@ -15,21 +14,15 @@ 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;
-}
+.mx_RegistrationEmailPromptDialog {
+ width: 417px;
-.mx_ServerConfig_identityServer {
- transform: scaleY(0);
- transform-origin: top;
- transition: transform 0.25s;
+ .mx_Dialog_content {
+ margin-bottom: 24px;
+ color: $tertiary-fg-color;
+ }
- &.mx_ServerConfig_identityServer_shown {
- transform: scaleY(1);
+ .mx_Dialog_primary {
+ width: 100%;
}
}
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
new file mode 100644
index 00000000000..f762468c7f8
--- /dev/null
+++ b/res/css/views/elements/_SSOButtons.scss
@@ -0,0 +1,49 @@
+/*
+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;
+ width: 100%;
+ padding-left: 32px;
+ padding-right: 32px;
+
+ > img {
+ object-fit: contain;
+ position: absolute;
+ left: 8px;
+ top: 4px;
+ }
+ }
+
+ .mx_SSOButton_mini {
+ box-sizing: border-box;
+ 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..ae1e445a9fb
--- /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_ServerPicker_change {
+ 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/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index 1b7ff9598d7..08fe2e9f572 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -217,7 +217,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 932a37b46e8..3e3c299af95 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.scss
+++ b/res/themes/legacy-dark/css/_legacy-dark.scss
@@ -208,7 +208,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 dba8fa6415e..085d6d7f103 100644
--- a/res/themes/legacy-light/css/_legacy-light.scss
+++ b/res/themes/legacy-light/css/_legacy-light.scss
@@ -331,7 +331,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 f89b9f2c755..4cfeeae05ed 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -335,7 +335,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;
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 {
diff --git a/src/Login.ts b/src/Login.ts
index ae4aa226edb..281906d8610 100644
--- a/src/Login.ts
+++ b/src/Login.ts
@@ -29,10 +29,25 @@ interface ILoginOptions {
}
// TODO: Move this to JS SDK
-interface ILoginFlow {
- type: string;
+interface IPasswordFlow {
+ type: "m.login.password";
}
+export interface IIdentityProvider {
+ id: string;
+ name: string;
+ icon?: string;
+}
+
+export interface ISSOFlow {
+ 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 | IPasswordFlow;
+
// 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/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/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..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
@@ -62,7 +60,6 @@ export default class ForgotPassword extends React.Component {
serverIsAlive: true,
serverErrorIsFatal: false,
serverDeadError: "",
- serverRequiresIdServer: null,
};
constructor(props) {
@@ -93,12 +90,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"));
@@ -177,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();
@@ -205,24 +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');
@@ -246,57 +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
- {this.props.serverConfig.hsName}
- ;
- },
- });
- }
-
- // If custom URLs are allowed, wire up the server details edit link.
- let editLink = null;
- if (!SdkConfig.get()['disable_custom_urls']) {
- editLink =
- {_t('Change')}
- ;
- }
-
- 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}
-
- {yourMatrixAccountText}
- {editLink}
-
+
;
} else if (SettingsStore.getValue(UIFeature.Registration)) {
footer = (
-
- { _t('Create account') }
-
+
+ {_t("New? Create account", {}, {
+ a: sub => { sub },
+ })}
+
);
}
@@ -686,8 +596,11 @@ export default class LoginComponent extends React.Component {
{ errorTextSection }
{ serverDeadSection }
- { this.renderServerComponent() }
- { this.renderLoginComponentForStep() }
+
+ { this.renderLoginComponentForFlows() }
{ footer }
diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx
index f97f20cf598..e1a2fc55901 100644
--- a/src/components/structures/auth/Registration.tsx
+++ b/src/components/structures/auth/Registration.tsx
@@ -15,29 +15,21 @@ 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';
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';
import {MatrixClientPeg} from "../../../MatrixClientPeg";
import AuthPage from "../../views/auth/AuthPage";
-import Login from "../../../Login";
+import Login, {ISSOFlow} from "../../../Login";
import dis from "../../../dispatcher/dispatcher";
-
-// Phases
-enum Phase {
- // Show controls to configure server details
- ServerDetails = 0,
- // Show the appropriate registration flow(s) for the server
- Registration = 1,
-}
+import SSOButtons from "../../views/elements/SSOButtons";
+import ServerPicker from '../../views/elements/ServerPicker';
interface IProps {
serverConfig: ValidatedServerConfig;
@@ -47,6 +39,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
@@ -92,9 +85,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[];
}[];
@@ -109,23 +99,22 @@ 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,
// 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);
- const serverType = ServerType.getTypeFromServerConfig(this.props.serverConfig);
this.state = {
busy: false,
errorText: null,
@@ -133,14 +122,17 @@ 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,
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() {
@@ -154,61 +146,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,
@@ -245,16 +184,20 @@ export default class Registration extends React.Component {
idBaseUrl: isUrl,
});
- let serverRequiresIdServer = true;
+ this.loginLogic.setHomeserverUrl(hsUrl);
+ this.loginLogic.setIdentityServerUrl(isUrl);
+
+ let ssoFlow: ISSOFlow;
try {
- serverRequiresIdServer = await cli.doesServerRequireIdServerParam();
+ 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.log("Unable to determine is server needs id_server param", 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 +225,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);
@@ -365,6 +298,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,
@@ -453,21 +388,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,
});
};
@@ -516,77 +436,7 @@ 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 (PHASES_ENABLED && this.state.phase !== Phase.ServerDetails && !this.state.serverErrorIsFatal) {
- return
;
- }
-
private renderRegisterComponent() {
- if (PHASES_ENABLED && 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');
@@ -610,18 +460,48 @@ export default class Registration extends React.Component {
;
} else if (this.state.flows.length) {
- return ;
+ 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) {
+ // i18n: ssoButtons is a placeholder to help translators understand context
+ continueWithSection =
}
+
{ this.renderRegisterComponent() }
{ goBack }
{ signIn }
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/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/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/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}
- ,
- },
- )}
-
-
- );
- }
-}
diff --git a/src/components/views/auth/PasswordLogin.tsx b/src/components/views/auth/PasswordLogin.tsx
index fced2e08d01..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.
@@ -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() {},
@@ -296,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"),
},
],
});
@@ -357,6 +354,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}
@@ -410,20 +408,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({
@@ -465,8 +457,6 @@ export default class PasswordLogin extends React.PureComponent {
return (
-
;
} 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/components/views/auth/ServerConfig.js b/src/components/views/auth/ServerConfig.js
deleted file mode 100644
index e04bf9e25a3..00000000000
--- a/src/components/views/auth/ServerConfig.js
+++ /dev/null
@@ -1,291 +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 { createClient } from 'matrix-js-sdk/src/matrix';
-import classNames from 'classnames';
-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,
-
- // 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 = {
- onServerConfigChange: function() {},
- delayTimeMs: 0,
- };
-
- constructor(props) {
- super(props);
-
- this.state = {
- busy: false,
- errorText: "",
- hsUrl: props.serverConfig.hsUrl,
- isUrl: props.serverConfig.isUrl,
- showIdentityServer: false,
- };
-
- 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;
- }
-
- // 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;
- }
-
- 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;
- }
- }
- }
-
- 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();
- });
- };
-
- onHomeserverChange = (ev) => {
- const hsUrl = ev.target.value;
- 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();
- 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
;
- }
-
- 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 (
-
- );
- }
-}
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
- {this.props.serverConfig.hsName}
- ;
- },
- });
- }
-
- let editLink = null;
- if (this.props.onEditServerDetailsClick) {
- editLink =
- {_t('Change')}
- ;
- }
-
- return
{ this.props.description }
diff --git a/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx b/src/components/views/dialogs/RegistrationEmailPromptDialog.tsx
new file mode 100644
index 00000000000..b7cc81c1134
--- /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},
+ })}
+
+
+
+ ;
+};
+
+export default RegistrationEmailPromptDialog;
diff --git a/src/components/views/dialogs/ServerPickerDialog.tsx b/src/components/views/dialogs/ServerPickerDialog.tsx
new file mode 100644
index 00000000000..9eb819c98ee
--- /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);
+ };
+
+ 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 = (
+
+ {this.defaultServer.hsName}
+
+ );
+ }
+
+ return
+
+ ;
+ }
+}
diff --git a/src/components/views/elements/Field.tsx b/src/components/views/elements/Field.tsx
index fb34f51b604..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,16 +169,18 @@ 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);
}
};
- 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 +209,8 @@ export default class Field extends React.PureComponent {
feedbackVisible: false,
});
}
+
+ return valid;
}
public render() {
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..a8bcc884125
--- /dev/null
+++ b/src/components/views/elements/SSOButtons.tsx
@@ -0,0 +1,110 @@
+/*
+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://")) {
+ icon = ;
+ }
+
+ 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/components/views/elements/ServerPicker.tsx b/src/components/views/elements/ServerPicker.tsx
new file mode 100644
index 00000000000..7637ab7b8da
--- /dev/null
+++ b/src/components/views/elements/ServerPicker.tsx
@@ -0,0 +1,93 @@
+/*
+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, {
+ 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 =
+ {serverConfig.hsName}
+ ;
+ }
+
+ 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 b33cbffb8fa..d44c01756a0 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",
@@ -2486,13 +2484,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 +2509,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.",
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();
+ });
});
diff --git a/test/end-to-end-tests/src/usecases/signup.js b/test/end-to-end-tests/src/usecases/signup.js
index ef8a259091d..804cee9599a 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();
}
@@ -68,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