diff --git a/res/css/views/auth/_AuthBody.scss b/res/css/views/auth/_AuthBody.scss index b51511a671..67c8df0fa8 100644 --- a/res/css/views/auth/_AuthBody.scss +++ b/res/css/views/auth/_AuthBody.scss @@ -37,6 +37,10 @@ limitations under the License. color: $authpage-primary-color; } + h4 { + text-align: center; + } + a:link, a:hover, a:visited { @@ -146,15 +150,14 @@ limitations under the License. display: block; text-align: center; width: 100%; - margin-top: 24px; > a { font-weight: $font-semi-bold; } } -form + .mx_AuthBody_changeFlow { - margin-top: 0; +.mx_SSOButtons + .mx_AuthBody_changeFlow { + margin-top: 24px; } .mx_AuthBody_spinner { diff --git a/src/Login.ts b/src/Login.ts index d5776da856..281906d861 100644 --- a/src/Login.ts +++ b/src/Login.ts @@ -29,8 +29,8 @@ interface ILoginOptions { } // TODO: Move this to JS SDK -interface ILoginFlow { - type: "m.login.password" | "m.login.cas"; +interface IPasswordFlow { + type: "m.login.password"; } export interface IIdentityProvider { @@ -40,13 +40,13 @@ export interface IIdentityProvider { } export interface ISSOFlow { - type: "m.login.sso"; + type: "m.login.sso" | "m.login.cas"; // eslint-disable-next-line camelcase identity_providers: IIdentityProvider[]; "org.matrix.msc2858.identity_providers": IIdentityProvider[]; // Unstable prefix for MSC2858 } -export type LoginFlow = ISSOFlow | ILoginFlow; +export type LoginFlow = ISSOFlow | IPasswordFlow; // TODO: Move this to JS SDK /* eslint-disable camelcase */ diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 9fede15aa6..32b961296b 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 f9f5263f7e..e599808f0d 100644 --- a/src/components/structures/auth/ForgotPassword.js +++ b/src/components/structures/auth/ForgotPassword.js @@ -319,6 +319,7 @@ export default class ForgotPassword extends React.Component { onChange={this.onInputChanged.bind(this, "password")} onFocus={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_focus")} onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword_blur")} + autoComplete="new-password" /> CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword2_focus")} onBlur={() => CountlyAnalytics.instance.track("onboarding_forgot_password_newPassword2_blur")} + autoComplete="new-password" /> {_t( diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index dd1fcc4d9a..cb09ade895 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -438,7 +438,7 @@ export default class LoginComponent extends React.PureComponent if (supportedFlows.length > 0) { this.setState({ - currentFlow: this.getCurrentFlowStep(), + flows: supportedFlows, }); return; } @@ -520,22 +520,13 @@ export default class LoginComponent extends React.PureComponent return null; } - if (PHASES_ENABLED && this.state.phase !== Phase.ServerDetails) { - return null; - } - - const serverDetailsProps: ComponentProps = {}; - if (PHASES_ENABLED) { - serverDetailsProps.onAfterSubmit = this.onServerDetailsNextPhaseClick; - serverDetailsProps.submitText = _t("Next"); - serverDetailsProps.submitClass = "mx_Login_submit"; - } - return ; } @@ -591,15 +582,13 @@ export default class LoginComponent extends React.PureComponent const flow = this.state.flows.find(flow => flow.type === "m.login." + loginType) as ISSOFlow; return ( -
- flow.type === "m.login.password")} - /> -
+ flow.type === "m.login.password")} + /> ); }; diff --git a/src/components/structures/auth/Registration.tsx b/src/components/structures/auth/Registration.tsx index 004029c920..45bfbcef46 100644 --- a/src/components/structures/auth/Registration.tsx +++ b/src/components/structures/auth/Registration.tsx @@ -15,7 +15,7 @@ limitations under the License. */ import Matrix from 'matrix-js-sdk'; -import React, {ComponentProps, ReactNode} from 'react'; +import React, {ReactNode} from 'react'; import {MatrixClient} from "matrix-js-sdk/src/client"; import * as sdk from '../../../index'; @@ -28,8 +28,9 @@ import classNames from "classnames"; import * as Lifecycle from '../../../Lifecycle'; import {MatrixClientPeg} from "../../../MatrixClientPeg"; import AuthPage from "../../views/auth/AuthPage"; -import Login from "../../../Login"; +import Login, {ISSOFlow} from "../../../Login"; import dis from "../../../dispatcher/dispatcher"; +import SSOButtons from "../../views/elements/SSOButtons"; // Phases enum Phase { @@ -47,6 +48,7 @@ interface IProps { clientSecret?: string; sessionId?: string; idSid?: string; + fragmentAfterLogin?: string; // Called when the user has logged in. Params: // - object with userId, deviceId, homeserverUrl, identityServerUrl, accessToken @@ -116,12 +118,14 @@ interface IState { // if a different user ID to the one we just registered is logged in, // this is the user ID that's logged in. differentLoggedInUserId?: string; + // the SSO flow definition, this is fetched from /login as that's the only + // place it is exposed. + ssoFlow?: ISSOFlow; } -// Enable phases for registration -const PHASES_ENABLED = true; - export default class Registration extends React.Component { + loginLogic: Login; + constructor(props) { super(props); @@ -141,6 +145,11 @@ export default class Registration extends React.Component { serverErrorIsFatal: false, serverDeadError: "", }; + + const {hsUrl, isUrl} = this.props.serverConfig; + this.loginLogic = new Login(hsUrl, isUrl, null, { + defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used + }); } componentDidMount() { @@ -252,9 +261,21 @@ export default class Registration extends React.Component { console.log("Unable to determine is server needs id_server param", e); } + this.loginLogic.setHomeserverUrl(hsUrl); + this.loginLogic.setIdentityServerUrl(isUrl); + + let ssoFlow: ISSOFlow; + try { + const loginFlows = await this.loginLogic.getFlows(); + ssoFlow = loginFlows.find(f => f.type === "m.login.sso" || f.type === "m.login.cas") as ISSOFlow; + } catch (e) { + console.error("Failed to get login flows to check for SSO support", e); + } + this.setState({ matrixClient: cli, serverRequiresIdServer, + ssoFlow, busy: false, }); const showGenericError = (e) => { @@ -282,26 +303,16 @@ export default class Registration extends React.Component { // At this point registration is pretty much disabled, but before we do that let's // quickly check to see if the server supports SSO instead. If it does, we'll send // the user off to the login page to figure their account out. - try { - const loginLogic = new Login(hsUrl, isUrl, null, { - defaultDeviceDisplayName: "Element login check", // We shouldn't ever be used + if (ssoFlow) { + // Redirect to login page - server probably expects SSO only + dis.dispatch({action: 'start_login'}); + } else { + this.setState({ + serverErrorIsFatal: true, // fatal because user cannot continue on this server + errorText: _t("Registration has been disabled on this homeserver."), + // add empty flows array to get rid of spinner + flows: [], }); - const flows = await loginLogic.getFlows(); - const hasSsoFlow = flows.find(f => f.type === 'm.login.sso' || f.type === 'm.login.cas'); - if (hasSsoFlow) { - // Redirect to login page - server probably expects SSO only - dis.dispatch({action: 'start_login'}); - } else { - this.setState({ - serverErrorIsFatal: true, // fatal because user cannot continue on this server - errorText: _t("Registration has been disabled on this homeserver."), - // add empty flows array to get rid of spinner - flows: [], - }); - } - } catch (e) { - console.error("Failed to get login flows to check for SSO support", e); - showGenericError(e); } } else { console.log("Unable to query for supported registration methods.", e); @@ -534,7 +545,7 @@ export default class Registration extends React.Component { // which is always shown if we allow custom URLs at all. // (if there's a fatal server error, we need to show the full server // config as the user may need to change servers to resolve the error). - if (PHASES_ENABLED && this.state.phase !== Phase.ServerDetails && !this.state.serverErrorIsFatal) { + if (this.state.phase !== Phase.ServerDetails && !this.state.serverErrorIsFatal) { return
{
; } - const serverDetailsProps: ComponentProps = {}; - if (PHASES_ENABLED) { - serverDetailsProps.onAfterSubmit = this.onServerDetailsNextPhaseClick; - serverDetailsProps.submitText = _t("Next"); - serverDetailsProps.submitClass = "mx_Login_submit"; - } - let serverDetails = null; switch (this.state.serverType) { case ServerType.FREE: @@ -559,7 +563,9 @@ export default class Registration extends React.Component { serverConfig={this.props.serverConfig} onServerConfigChange={this.props.onServerConfigChange} delayTimeMs={250} - {...serverDetailsProps} + onAfterSubmit={this.onServerDetailsNextPhaseClick} + submitText={_t("Next")} + submitClass="mx_Login_submit" />; break; case ServerType.ADVANCED: @@ -568,7 +574,9 @@ export default class Registration extends React.Component { onServerConfigChange={this.props.onServerConfigChange} delayTimeMs={250} showIdentityServerIfRequiredByHomeserver={true} - {...serverDetailsProps} + onAfterSubmit={this.onServerDetailsNextPhaseClick} + submitText={_t("Next")} + submitClass="mx_Login_submit" />; break; } @@ -583,7 +591,7 @@ export default class Registration extends React.Component { } private renderRegisterComponent() { - if (PHASES_ENABLED && this.state.phase !== Phase.Registration) { + if (this.state.phase !== Phase.Registration) { return null; } @@ -610,18 +618,35 @@ export default class Registration extends React.Component { ; } else if (this.state.flows.length) { - return ; + let ssoSection; + if (this.state.ssoFlow) { + ssoSection = +

{_t("Continue with")}

+ +

{_t("Or")}

+
; + } + + return + { ssoSection } + + ; } } @@ -658,7 +683,7 @@ export default class Registration extends React.Component { // Only show the 'go back' button if you're not looking at the form let goBack; - if ((PHASES_ENABLED && this.state.phase !== Phase.Registration) || this.state.doingUIAuth) { + if (this.state.phase !== Phase.Registration || this.state.doingUIAuth) { goBack = { _t('Go back') } ; @@ -725,8 +750,7 @@ export default class Registration extends React.Component { // If custom URLs are allowed, user is not doing UIA flows and they haven't selected the Free server type, // wire up the server details edit link. let editLink = null; - if (PHASES_ENABLED && - !SdkConfig.get()['disable_custom_urls'] && + if (!SdkConfig.get()['disable_custom_urls'] && this.state.serverType !== ServerType.FREE && !this.state.doingUIAuth ) { diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index baf801b57b..f45f4c60cd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2517,6 +2517,8 @@ "Unable to query for supported registration methods.": "Unable to query for supported registration methods.", "Registration has been disabled on this homeserver.": "Registration has been disabled on this homeserver.", "This server does not support authentication with a phone number.": "This server does not support authentication with a phone number.", + "Continue with": "Continue with", + "Or": "Or", "Already have an account? Sign in here": "Already have an account? Sign in here", "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).": "Your new account (%(newAccountId)s) is registered, but you're already logged into a different account (%(loggedInUserId)s).", "Continue with previous account": "Continue with previous account",