From e16010f1f98574932b6479315ba692b93be6b1ab Mon Sep 17 00:00:00 2001 From: Luke Heath Date: Thu, 23 Jun 2022 10:32:20 -0700 Subject: [PATCH] Improve SSO error messaging (#6339) --- changes/issue-3359-improve-sso-messaging | 1 + cypress/integration/all/sessions/sso.spec.ts | 4 +++ .../UnauthenticatedRoutes.tsx | 11 +++++++ .../components/UnauthenticatedRoutes/index.ts | 1 + frontend/layouts/GatedLayout/GatedLayout.tsx | 24 ++++++++++++++ frontend/layouts/GatedLayout/_styles.scss | 5 +++ frontend/layouts/GatedLayout/index.ts | 1 + frontend/pages/LoginPage/LoginPage.tsx | 32 ++++++++++++++++++- frontend/router/index.tsx | 29 +++++++++++------ 9 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 changes/issue-3359-improve-sso-messaging create mode 100644 frontend/components/UnauthenticatedRoutes/UnauthenticatedRoutes.tsx create mode 100644 frontend/components/UnauthenticatedRoutes/index.ts create mode 100644 frontend/layouts/GatedLayout/GatedLayout.tsx create mode 100644 frontend/layouts/GatedLayout/_styles.scss create mode 100644 frontend/layouts/GatedLayout/index.ts diff --git a/changes/issue-3359-improve-sso-messaging b/changes/issue-3359-improve-sso-messaging new file mode 100644 index 0000000000..56b1179f00 --- /dev/null +++ b/changes/issue-3359-improve-sso-messaging @@ -0,0 +1 @@ +- Improve SSO login failure messaging. diff --git a/cypress/integration/all/sessions/sso.spec.ts b/cypress/integration/all/sessions/sso.spec.ts index a38742d457..c884100717 100644 --- a/cypress/integration/all/sessions/sso.spec.ts +++ b/cypress/integration/all/sessions/sso.spec.ts @@ -47,4 +47,8 @@ describe("SSO Sessions", () => { // Log in should fail cy.contains("Password"); }); + it("displays an error message when status is set", () => { + cy.visit("/login?status=account_disabled"); + cy.getAttached(".flash-message"); + }); }); diff --git a/frontend/components/UnauthenticatedRoutes/UnauthenticatedRoutes.tsx b/frontend/components/UnauthenticatedRoutes/UnauthenticatedRoutes.tsx new file mode 100644 index 0000000000..58ade9b646 --- /dev/null +++ b/frontend/components/UnauthenticatedRoutes/UnauthenticatedRoutes.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +interface IAppProps { + children: JSX.Element; +} + +export const UnauthenticatedRoutes = ({ children }: IAppProps): JSX.Element => { + return
{children}
; +}; + +export default UnauthenticatedRoutes; diff --git a/frontend/components/UnauthenticatedRoutes/index.ts b/frontend/components/UnauthenticatedRoutes/index.ts new file mode 100644 index 0000000000..b9642b9ed0 --- /dev/null +++ b/frontend/components/UnauthenticatedRoutes/index.ts @@ -0,0 +1 @@ +export { default } from "./UnauthenticatedRoutes"; diff --git a/frontend/layouts/GatedLayout/GatedLayout.tsx b/frontend/layouts/GatedLayout/GatedLayout.tsx new file mode 100644 index 0000000000..9dbf25b2c5 --- /dev/null +++ b/frontend/layouts/GatedLayout/GatedLayout.tsx @@ -0,0 +1,24 @@ +import React, { useContext } from "react"; +import { NotificationContext } from "context/notification"; +import FlashMessage from "components/FlashMessage"; + +interface IGatedLayoutProps { + children: React.ReactNode; +} + +const GatedLayout = ({ children }: IGatedLayoutProps): JSX.Element => { + const { notification, hideFlash } = useContext(NotificationContext); + + return ( +
+ + {children} +
+ ); +}; + +export default GatedLayout; diff --git a/frontend/layouts/GatedLayout/_styles.scss b/frontend/layouts/GatedLayout/_styles.scss new file mode 100644 index 0000000000..a1e1fc5ed9 --- /dev/null +++ b/frontend/layouts/GatedLayout/_styles.scss @@ -0,0 +1,5 @@ +.gated-layout { + .flash-message { + border: 0; + } +} \ No newline at end of file diff --git a/frontend/layouts/GatedLayout/index.ts b/frontend/layouts/GatedLayout/index.ts new file mode 100644 index 0000000000..3ceb094e11 --- /dev/null +++ b/frontend/layouts/GatedLayout/index.ts @@ -0,0 +1 @@ +export { default } from "./GatedLayout"; diff --git a/frontend/pages/LoginPage/LoginPage.tsx b/frontend/pages/LoginPage/LoginPage.tsx index 90c34fdb43..6b946bb847 100644 --- a/frontend/pages/LoginPage/LoginPage.tsx +++ b/frontend/pages/LoginPage/LoginPage.tsx @@ -4,6 +4,7 @@ import { size } from "lodash"; import paths from "router/paths"; import { AppContext } from "context/app"; +import { NotificationContext } from "context/notification"; import { RoutingContext } from "context/routing"; import { ISSOSettings } from "interfaces/ssoSettings"; import local from "utilities/local"; @@ -17,6 +18,11 @@ import LoginSuccessfulPage from "pages/LoginSuccessfulPage"; interface ILoginPageProps { router: InjectedRouter; // v3 + location: { + pathname: string; + query: { vulnerable?: boolean }; + search: string; + }; } interface ILoginData { @@ -24,16 +30,36 @@ interface ILoginData { password: string; } -const LoginPage = ({ router }: ILoginPageProps) => { +interface IStatusMessages { + account_disabled: string; + account_invalid: string; + org_disabled: string; + error: string; +} + +const statusMessages: IStatusMessages = { + account_disabled: + "Single sign-on is not enabled on your account. Please contact your Fleet administrator.", + account_invalid: "You do not have a Fleet account.", + org_disabled: "Single sign-on is not enabled for your organization.", + error: + "There was an error with single sign-on. Please contact your Fleet administrator.", +}; + +const LoginPage = ({ router, location }: ILoginPageProps) => { const { currentUser, setAvailableTeams, setCurrentUser, setCurrentTeam, } = useContext(AppContext); + const { renderFlash } = useContext(NotificationContext); const { redirectLocation } = useContext(RoutingContext); const [loginVisible, setLoginVisible] = useState(true); const [ssoSettings, setSSOSettings] = useState(); + const [pageStatus, setPageStatus] = useState( + new URLSearchParams(location.search).get("status") + ); const [errors, setErrors] = useState<{ [key: string]: string }>({}); useEffect(() => { @@ -53,6 +79,10 @@ const LoginPage = ({ router }: ILoginPageProps) => { } else { getSSO(); } + + if (pageStatus && pageStatus in statusMessages) { + renderFlash("error", statusMessages[pageStatus as keyof IStatusMessages]); + } }, [router]); const onChange = () => { diff --git a/frontend/router/index.tsx b/frontend/router/index.tsx index e658572b5b..e6b5e18bf5 100644 --- a/frontend/router/index.tsx +++ b/frontend/router/index.tsx @@ -18,11 +18,13 @@ import App from "components/App"; import AuthenticatedAdminRoutes from "components/AuthenticatedAdminRoutes"; import AuthAnyAdminRoutes from "components/AuthAnyAdminRoutes"; import AuthenticatedRoutes from "components/AuthenticatedRoutes"; +import UnauthenticatedRoutes from "components/UnauthenticatedRoutes"; import AuthGlobalAdminMaintainerRoutes from "components/AuthGlobalAdminMaintainerRoutes"; import AuthAnyMaintainerAnyAdminRoutes from "components/AuthAnyMaintainerAnyAdminRoutes"; import ConfirmInvitePage from "pages/ConfirmInvitePage"; import ConfirmSSOInvitePage from "pages/ConfirmSSOInvitePage"; import CoreLayout from "layouts/CoreLayout"; +import GatedLayout from "layouts/GatedLayout"; import DeviceUserPage from "pages/hosts/details/DeviceUserPage"; import EditPackPage from "pages/packs/EditPackPage"; import EmailTokenRedirect from "components/EmailTokenRedirect"; @@ -76,16 +78,23 @@ const AppWrapper = ({ children, router, location }: IAppWrapperProps) => ( const routes = ( - - - - - - - + + + + + + + + + + +