Improve SSO error messaging (#6339)

This commit is contained in:
Luke Heath 2022-06-23 10:32:20 -07:00 committed by GitHub
parent bfb650a8da
commit e16010f1f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 97 additions and 11 deletions

View file

@ -0,0 +1 @@
- Improve SSO login failure messaging.

View file

@ -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");
});
});

View file

@ -0,0 +1,11 @@
import React from "react";
interface IAppProps {
children: JSX.Element;
}
export const UnauthenticatedRoutes = ({ children }: IAppProps): JSX.Element => {
return <div>{children}</div>;
};
export default UnauthenticatedRoutes;

View file

@ -0,0 +1 @@
export { default } from "./UnauthenticatedRoutes";

View file

@ -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 (
<div className="gated-layout">
<FlashMessage
fullWidth
notification={notification}
onRemoveFlash={hideFlash}
/>
{children}
</div>
);
};
export default GatedLayout;

View file

@ -0,0 +1,5 @@
.gated-layout {
.flash-message {
border: 0;
}
}

View file

@ -0,0 +1 @@
export { default } from "./GatedLayout";

View file

@ -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<boolean>(true);
const [ssoSettings, setSSOSettings] = useState<ISSOSettings>();
const [pageStatus, setPageStatus] = useState<string | null>(
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 = () => {

View file

@ -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 = (
<Router history={browserHistory}>
<Route path={PATHS.ROOT} component={AppWrapper}>
<Route path="setup" component={RegistrationPage} />
<Route path="previewlogin" component={LoginPreviewPage} />
<Route path="login" component={LoginPage} />
<Route path="login/invites/:invite_token" component={ConfirmInvitePage} />
<Route
path="login/ssoinvites/:invite_token"
component={ConfirmSSOInvitePage}
/>
<Route path="login/forgot" component={ForgotPasswordPage} />
<Route path="login/reset" component={ResetPasswordPage} />
<Route component={UnauthenticatedRoutes as RouteComponent}>
<Route component={GatedLayout}>
<Route path="setup" component={RegistrationPage} />
<Route path="previewlogin" component={LoginPreviewPage} />
<Route path="login" component={LoginPage} />
<Route
path="login/invites/:invite_token"
component={ConfirmInvitePage}
/>
<Route
path="login/ssoinvites/:invite_token"
component={ConfirmSSOInvitePage}
/>
<Route path="login/forgot" component={ForgotPasswordPage} />
<Route path="login/reset" component={ResetPasswordPage} />
</Route>
</Route>
<Route component={AuthenticatedRoutes as RouteComponent}>
<Route path="email/change/:token" component={EmailTokenRedirect} />
<Route path="logout" component={LogoutPage} />