From 867d27490553adcfcbc5393edc6a59162f35adb4 Mon Sep 17 00:00:00 2001 From: "marc.sirisak" Date: Thu, 19 Sep 2024 09:44:30 +0200 Subject: [PATCH] feat(sso): add email domain precheck sso flow --- .../matrix-react-sdk/src/Views.ts | 3 + .../src/components/structures/MatrixChat.tsx | 39 ++++- .../src/dispatcher/actions.ts | 5 + .../tchap_translations.json | 32 ++++ patches/subtree-modifications.json | 6 + res/css/views/sso/TchapSSO.pcss | 44 +++++ res/welcome.html | 83 ++++++++- res/welcome/images/proconnect.svg | 22 +++ .../views/sso/EmailVerificationPage.tsx | 159 ++++++++++++++++++ 9 files changed, 389 insertions(+), 4 deletions(-) create mode 100644 res/css/views/sso/TchapSSO.pcss create mode 100644 res/welcome/images/proconnect.svg create mode 100644 src/tchap/components/views/sso/EmailVerificationPage.tsx diff --git a/linked-dependencies/matrix-react-sdk/src/Views.ts b/linked-dependencies/matrix-react-sdk/src/Views.ts index 4c7f002d50..a9c01f646f 100644 --- a/linked-dependencies/matrix-react-sdk/src/Views.ts +++ b/linked-dependencies/matrix-react-sdk/src/Views.ts @@ -54,6 +54,9 @@ enum Views { // Another instance of the application has started up. We just show an error page. LOCK_STOLEN, + + // :TCHAP: screen before launching sso + EMAIL_PRECHECK_SSO } export default Views; diff --git a/linked-dependencies/matrix-react-sdk/src/components/structures/MatrixChat.tsx b/linked-dependencies/matrix-react-sdk/src/components/structures/MatrixChat.tsx index c1d1396ce2..42e78af6aa 100644 --- a/linked-dependencies/matrix-react-sdk/src/components/structures/MatrixChat.tsx +++ b/linked-dependencies/matrix-react-sdk/src/components/structures/MatrixChat.tsx @@ -143,9 +143,11 @@ import { checkSessionLockFree, getSessionLock } from "../../utils/SessionLock"; import { SessionLockStolenView } from "./auth/SessionLockStolenView"; import { ConfirmSessionLockTheftView } from "./auth/ConfirmSessionLockTheftView"; import { LoginSplashView } from "./auth/LoginSplashView"; -import TchapUrls from "../../../../../src/tchap/util/TchapUrls"; // :TCHAP: activate-cross-signing-and-secure-storage-react import { cleanUpDraftsIfRequired } from "../../DraftCleaner"; +import TchapUrls from "../../../../../src/tchap/util/TchapUrls"; // :TCHAP: activate-cross-signing-and-secure-storage-react +import EmailVerificationPage from "../../../../../src/tchap/components/views/sso/EmailVerificationPage"; // :TCHAP: sso-agentconnect-flow + // legacy export export { default as Views } from "../../Views"; @@ -946,6 +948,15 @@ export default class MatrixChat extends React.PureComponent { true, ); break; + // :TCHAP: sso-agentconnect-flow + case Action.EmailPrecheckSSO: + if (Lifecycle.isSoftLogout()) { + this.onSoftLogout(); + break; + } + this.viewEmailPrecheckSSO(); + break; + // end :TCHAP: } }; @@ -1104,6 +1115,17 @@ export default class MatrixChat extends React.PureComponent { this.themeWatcher.recheck(); } + // :TCHAP: sso-agentconnect-flow + private viewEmailPrecheckSSO() { + this.setStateForNewView({ + view: Views.EMAIL_PRECHECK_SSO + }); + this.notifyNewScreen("email-precheck-sso"); + ThemeController.isLogin = true; + this.themeWatcher.recheck(); + } + // end :TCHAP: + private viewHome(justRegistered = false): void { // The home page requires the "logged in" view, so we'll set that. this.setStateForNewView({ @@ -1875,6 +1897,13 @@ export default class MatrixChat extends React.PureComponent { userId: userId, subAction: params?.action, }); + // :TCHAP: sso-agentconnect-flow + } else if (screen = "email-precheck-sso") { + dis.dispatch({ + action: "email_precheck_sso", + params + }); + // end :TCHAP: } else { logger.info(`Ignoring showScreen for '${screen}'`); } @@ -2017,7 +2046,9 @@ export default class MatrixChat extends React.PureComponent { if ( initialScreenAfterLogin && // XXX: workaround for https://github.com/vector-im/element-web/issues/11643 causing a login-loop - !["welcome", "login", "register", "start_sso", "start_cas"].includes(initialScreenAfterLogin.screen) + // :TCHAP: sso-agentconnect-flow !["welcome", "login", "register", "start_sso", "start_cas"].includes(initialScreenAfterLogin.screen) + !["welcome", "login", "register", "start_sso", "start_cas", "email-precheck-sso"].includes(initialScreenAfterLogin.screen) + // end :TCHAP: ) { fragmentAfterLogin = `/${initialScreenAfterLogin.screen}`; } @@ -2137,6 +2168,10 @@ export default class MatrixChat extends React.PureComponent { view = => this.onShowPostLoginScreen(useCase)} />; } else if (this.state.view === Views.LOCK_STOLEN) { view = ; + // :TCHAP: sso-agentconnect-flow + } else if (this.state.view === Views.EMAIL_PRECHECK_SSO) { + view = ; + // end :TCHAP: } else { logger.error(`Unknown view ${this.state.view}`); return null; diff --git a/linked-dependencies/matrix-react-sdk/src/dispatcher/actions.ts b/linked-dependencies/matrix-react-sdk/src/dispatcher/actions.ts index 7cff1cc859..267b934e97 100644 --- a/linked-dependencies/matrix-react-sdk/src/dispatcher/actions.ts +++ b/linked-dependencies/matrix-react-sdk/src/dispatcher/actions.ts @@ -393,4 +393,9 @@ export enum Action { * Opens right panel room summary and focuses the search input */ FocusMessageSearch = "focus_search", + + /** + * :TCHAP: Open new page to check email instance before launching SSO + */ + EmailPrecheckSSO = "email_precheck_sso" } diff --git a/modules/tchap-translations/tchap_translations.json b/modules/tchap-translations/tchap_translations.json index 8078e1b53b..072864f280 100644 --- a/modules/tchap-translations/tchap_translations.json +++ b/modules/tchap-translations/tchap_translations.json @@ -837,5 +837,37 @@ "incompatible_browser|continue": { "en": "Continue anyway", "fr": "Continuer tout de même" + }, + "auth|sso|sign_in_password_instead": { + "en": "Login with password", + "fr": "Se connecter par mot de passe" + }, + "auth|sso|email_title": { + "en": "Login with ProConnect", + "fr": "Se connecter avec ProConnect" + }, + "auth|sso|proconnect_continue": { + "en": "Continue with ProConnect", + "fr": "Continuer avec ProConnect" + }, + "auth|sso|email_placeholder": { + "en": "Your professional email", + "fr": "Votre adresse mail professionelle" + }, + "welcome|sso|proconnect_explanation": { + "en": "What is ProConnect", + "fr": "Qu'est-ce que ProConnect" + }, + "auth|sso|error": { + "en": "An error occured during SSO login", + "fr": "Une erreur est survenue lors de la connexion" + }, + "auth|sso|error_homeserver": { + "en": "There is an error with the homeserver configuration", + "fr": "Il y a une erreur avec la configuration du serveur" + }, + "auth|sso|error_email": { + "en": "You need to enter your professional email", + "fr": "Vous devez entrer votre adresse professionelle" } } diff --git a/patches/subtree-modifications.json b/patches/subtree-modifications.json index ac5a44887c..d19c7ce293 100644 --- a/patches/subtree-modifications.json +++ b/patches/subtree-modifications.json @@ -78,5 +78,11 @@ "files": [ "src/components/views/settings/tabs/user/AccountUserSettingsTab.tsx" ] + }, + "sso-agentconnect-flow": { + "issue": "https://github.com/tchapgouv/tchap-web-v4/issues/386", + "files": [ + "src/components/structures/MatrixChat.tsx" + ] } } \ No newline at end of file diff --git a/res/css/views/sso/TchapSSO.pcss b/res/css/views/sso/TchapSSO.pcss new file mode 100644 index 0000000000..23f98c5604 --- /dev/null +++ b/res/css/views/sso/TchapSSO.pcss @@ -0,0 +1,44 @@ +form { + .tc_ButtonParent { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 14px 20px; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + border-radius: 4px; + background-origin: content-box; + background-repeat: no-repeat; + background-position: 30px center; + text-decoration: none; + color: #2e2f32 !important; + width: 100%; + } + + .tc_ButtonProconnect { + background-color: #000091; + color: white !important; + margin-bottom: 40px; + } + + .tc_Button_iconPC { + background-image: url("../../../welcome/images/proconnect.svg"); + } + + .tc_bottomButton { + display: flex; + justify-content: center; + } +} \ No newline at end of file diff --git a/res/welcome.html b/res/welcome.html index 199c3afa04..cb29ddcc4a 100644 --- a/res/welcome.html +++ b/res/welcome.html @@ -105,7 +105,7 @@ } .mx_ButtonLabel { - margin-left: 20px; + margin-left: 30px; } .mx_Header_title { @@ -203,6 +203,69 @@ margin: 0 0 10px 0; } } + + /* :TCHAP: sso-agentconnect-flow*/ + + .tc_ButtonCol { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .tc_ButtonParent { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + padding: 14px 20px; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + border-radius: 4px; + width: 175px; + background-repeat: no-repeat; + background-position: 10px center; + text-decoration: none; + color: #2e2f32 !important; + } + + .tc_ButtonProconnect { + background-color: #000091; + color: white !important; + margin-bottom: 10px; + } + + .tc_Button { + color: #000091 !important; + font-weight: bold; + } + + .tc_ButtonBorder { + outline: 1px solid #000091; + outline-offset: -2px; + margin-top: 20px; + margin-bottom: 10px; + } + .tc_ButtonProconnect_explanation { + + } + + .tc_Button_iconPC { + background-image: url("welcome/images/proconnect.svg"); + } + + /* end :TCHAP: */
@@ -228,14 +291,30 @@

_t("Welcome to Tchap")
_t("action|learn_more")

-
+ + + +
diff --git a/res/welcome/images/proconnect.svg b/res/welcome/images/proconnect.svg new file mode 100644 index 0000000000..41c99b526d --- /dev/null +++ b/res/welcome/images/proconnect.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tchap/components/views/sso/EmailVerificationPage.tsx b/src/tchap/components/views/sso/EmailVerificationPage.tsx new file mode 100644 index 0000000000..d3c16b28b5 --- /dev/null +++ b/src/tchap/components/views/sso/EmailVerificationPage.tsx @@ -0,0 +1,159 @@ +/* +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, { useState, useRef } from "react"; +import { _t, _td } from "matrix-react-sdk/src/languageHandler"; + +import AuthPage from "matrix-react-sdk/src/components/views/auth/AuthPage"; +import AuthBody from "matrix-react-sdk/src/components/views/auth/AuthBody"; +import AuthHeader from "matrix-react-sdk/src/components/views/auth/AuthHeader"; +import EmailField from "matrix-react-sdk/src/components/views/auth/EmailField"; +import Field from "matrix-react-sdk/src/components/views/elements/Field"; +import Spinner from "matrix-react-sdk/src/components/views/elements/Spinner"; +import AccessibleButton, { ButtonEvent } from "matrix-react-sdk/src/components/views/elements/AccessibleButton"; +import PlatformPeg from "matrix-react-sdk/src/PlatformPeg"; + +import { ErrorMessage } from "matrix-react-sdk/src/components/structures/ErrorMessage"; +import { SSOAction } from "matrix-js-sdk/src/matrix"; +import Login from "matrix-react-sdk/src/Login"; +import TchapUtils from "../../../util/TchapUtils"; +import { ValidatedServerConfig } from "matrix-react-sdk/src/utils/ValidatedServerConfig"; + +import "../../../../../res/css/views/sso/TchapSSO.pcss"; + +export default function EmailVerificationPage() { + + const [loading, setLoading] = useState(false); + const [email, setEmail] = useState(""); + const [errorText, setErrorText] = useState(""); + + const submitButtonChild = loading ? : _t("auth|sso|proconnect_continue"); + + const emailFieldRef = useRef(null); + + const displayError = (errorString: string): void => { + emailFieldRef.current?.focus(); + emailFieldRef.current?.validate({ allowEmpty: false, focused: true }); + setErrorText(errorString); + setLoading(false); + } + + const setUpCurrentHs = async (hs: Record): Promise => { + try { + const validatedServerConfig: ValidatedServerConfig = await TchapUtils.makeValidatedServerConfig(hs); + return validatedServerConfig; + } catch(err) { + window.location.assign("email-precheck-sso") + return null + } + + } + + const onSubmit = async (event: React.FormEvent): Promise => { + event.preventDefault(); + setLoading(true); + const isFieldCorrect = await emailFieldRef.current?.validate({ allowEmpty: false }); + + if (!isFieldCorrect) { + emailFieldRef.current?.focus(); + emailFieldRef.current?.validate({ allowEmpty: false, focused: true }); + setErrorText(_td("auth|sso|error_email")); + setLoading(false); + return; + } + + // check email domain and start sso with agentconnect + try { + // get user homeserver from his email + const hs: Record | void = await TchapUtils.fetchHomeserverForEmail(email); + if (!hs) { + displayError("This email address cannot be used in Tchap"); + return; + } + + const validatedServerConfig = await setUpCurrentHs(hs); + + if (!validatedServerConfig) { + displayError(_td("auth|sso|error_homeserver")); + return + } + + const login = new Login(hs.base_url, hs.base_url, null, {}); + + const matrixClient= login.createTemporaryClient(); + + // start SSO flow since we got the homeserver + PlatformPeg.get()?.startSingleSignOn(matrixClient, "sso", "/home", "", SSOAction.LOGIN); + + setLoading(false); + + } catch(err) { + displayError(_td("auth|sso|error")); + } + } + + const onInputChanged = (event: React.FormEvent) => { + setEmail(event.currentTarget.value); + } + + const onLoginByPasswordClick = () => { + window.location.assign("#/login"); + } + + return ( + + + +

+ {_t("auth|sso|email_title")} +

+
+
+
+ ) => onInputChanged(event)} + fieldRef={emailFieldRef} + /> +
+ {errorText && } + +
+ { + e.preventDefault(); + onLoginByPasswordClick(); + }} + > + {_t("auth|sso|sign_in_password_instead")} + +
+
+
+
+
+ ); +}