Skip to content

Commit

Permalink
Merge pull request #592 from CBIIT/PBAC/navbar-fixes
Browse files Browse the repository at this point in the history
Refactor Login Flow & Fix Navbar Button Styling
  • Loading branch information
Alejandro-Vega authored Jan 15, 2025
2 parents fa469d1 + 8a6decd commit ab22a2a
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 54 deletions.
6 changes: 1 addition & 5 deletions src/components/Header/components/HeaderTabletAndMobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,11 +385,7 @@ const Header = () => {
{displayName}
</div>
) : (
<Link
id="navbar-link-login"
to="/login"
state={{ redirectURLOnLoginSuccess: restorePath }}
>
<Link id="navbar-link-login" to="/login" state={{ redirectState: restorePath }}>
<div
role="button"
tabIndex={0}
Expand Down
22 changes: 11 additions & 11 deletions src/components/Header/components/NavbarDesktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,9 @@ const NameDropdownContainer = styled("div")({
},
},
"& .dropdownItemButton": {
paddingBottom: 0,
textTransform: "none",
paddingLeft: "20px",
paddingRight: "20px",
"&:hover": {
background: "transparent",
},
Expand Down Expand Up @@ -494,7 +495,7 @@ const NavBar = () => {
<StyledLoginLink
id="header-navbar-login-button"
to="/login"
state={{ redirectURLOnLoginSuccess: restorePath }}
state={{ redirectState: restorePath }}
>
Login
</StyledLoginLink>
Expand Down Expand Up @@ -539,15 +540,14 @@ const NavBar = () => {

if (dropItem.onClick) {
return (
<span className="dropdownItem" key={dropItem.id}>
<Button
id={dropItem.id}
className="dropdownItem dropdownItemButton"
onClick={dropItem.onClick}
>
{dropItem.name}
</Button>
</span>
<Button
id={dropItem.id}
key={dropItem.id}
className="dropdownItem dropdownItemButton"
onClick={dropItem.onClick}
>
{dropItem.name}
</Button>
);
}

Expand Down
84 changes: 84 additions & 0 deletions src/content/Login/Controller.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { render } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import LoginController from "./Controller";

jest.mock("../../env", () => ({
...process.env,
REACT_APP_NIH_AUTHORIZE_URL: "https://mock-sso-url",
REACT_APP_NIH_CLIENT_ID: "mock-client-id",
REACT_APP_NIH_REDIRECT_URL: "mock-redirect-url",
}));

const mockUsePageTitle = jest.fn();
jest.mock("../../hooks/usePageTitle", () => ({
...jest.requireActual("../../hooks/usePageTitle"),
__esModule: true,
default: (p) => mockUsePageTitle(p),
}));

describe("LoginController", () => {
beforeEach(() => {
Object.defineProperty(window, "location", {
value: {
href: "/",
},
writable: true,
});
});

it("should set the page title to 'Login'", () => {
render(<LoginController />, { wrapper: MemoryRouter });

expect(mockUsePageTitle).toHaveBeenCalledWith("Login");
});

it("should render a loader while redirecting to the NIH SSO login page", () => {
const { getByTestId } = render(<LoginController />, { wrapper: MemoryRouter });

expect(getByTestId("login-flow-loader")).toBeVisible();
});

it("should redirect to the SSO login page with the correct params", () => {
render(<LoginController />, { wrapper: MemoryRouter });

expect(window.location.href).toContain("https://mock-sso-url");
expect(window.location.href).toContain("client_id=mock-client-id");
expect(window.location.href).toContain("redirect_uri=mock-redirect-url");
expect(window.location.href).toContain("response_type=code");
expect(window.location.href).toContain("scope=openid+email+profile");
expect(window.location.href).toContain("prompt=login");
expect(window.location.href).not.toContain("state");
});

it.each<string>(["/data-submissions", "/users/uuid-1234", "mock-redirect-state"])(
"should append the redirect state to the redirect URL if it exists as a valid string (%s)",
(redirectState) => {
render(<LoginController />, {
wrapper: ({ children }) => (
<MemoryRouter initialEntries={["/login", { state: { redirectState } }]}>
{children}
</MemoryRouter>
),
});

const serializedRedirectState = encodeURIComponent(redirectState);

expect(window.location.href).toContain(`state=${serializedRedirectState}`);
}
);

it.each([null, undefined, 1234, {}, [], ""])(
"should not append the redirect state to the redirect URL if it is not a valid string (%s)",
(redirectState) => {
render(<LoginController />, {
wrapper: ({ children }) => (
<MemoryRouter initialEntries={["/login", { state: { redirectState } }]}>
{children}
</MemoryRouter>
),
});

expect(window.location.href).not.toContain("state");
}
);
});
33 changes: 33 additions & 0 deletions src/content/Login/Controller.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useLocation } from "react-router-dom";
import env from "../../env";
import usePageTitle from "../../hooks/usePageTitle";
import SuspenseLoader from "../../components/SuspenseLoader";

/**
* Handles the NIH SSO redirect to the login page.
*
* @returns The LoginController component
*/
const LoginController = () => {
usePageTitle("Login");

const { state } = useLocation();

const params = new URLSearchParams({
client_id: env.REACT_APP_NIH_CLIENT_ID,
redirect_uri: env.REACT_APP_NIH_REDIRECT_URL,
response_type: "code",
scope: "openid email profile",
prompt: "login",
});

if (typeof state?.redirectState === "string" && !!state.redirectState) {
params.append("state", state.redirectState);
}

window.location.href = `${env.REACT_APP_NIH_AUTHORIZE_URL}?${params?.toString()}`;

return <SuspenseLoader data-testid="login-flow-loader" fullscreen />;
};

export default LoginController;
6 changes: 3 additions & 3 deletions src/content/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const Home: FC = () => {
<Link
id="loginDialogLinkToLogin"
to="/login"
state={{ redirectURLOnLoginSuccess: dialogRedirectPath }}
state={{ redirectState: dialogRedirectPath }}
onClick={() => setShowRedirectDialog(false)}
>
<strong>log in</strong>
Expand All @@ -161,7 +161,7 @@ const Home: FC = () => {
id="loginDialogLoginButton"
className="loginDialogButton"
to="/login"
state={{ redirectURLOnLoginSuccess: dialogRedirectPath }}
state={{ redirectState: dialogRedirectPath }}
onClick={() => setShowRedirectDialog(false)}
>
<strong>Log In</strong>
Expand All @@ -185,7 +185,7 @@ const Home: FC = () => {
id="loginPageLoginButton"
className="loginPageLoginButton"
to="/login"
state={{ redirectURLOnLoginSuccess: "/submissions" }}
state={{ redirectState: "/submissions" }}
>
<strong>Log In</strong>
</Link>
Expand Down
34 changes: 0 additions & 34 deletions src/content/login/Controller.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const MainLayout = withTracking(Layout);

// Pages
const Home = LazyLoader(lazy(() => import("./content")));
const Login = LazyLoader(lazy(() => import("./content/login/Controller")));
const Login = LazyLoader(lazy(() => import("./content/Login/Controller")));
const Questionnaire = LazyLoader(lazy(() => import("./content/questionnaire/Controller")));
const DataSubmissions = LazyLoader(lazy(() => import("./content/dataSubmissions/Controller")));
const Users = LazyLoader(lazy(() => import("./content/users/Controller")));
Expand Down

0 comments on commit ab22a2a

Please sign in to comment.