From 847e4f6e187e92aae4fa9e5c843615bb1e1257a1 Mon Sep 17 00:00:00 2001 From: Ian MacFarland Date: Fri, 16 Oct 2020 17:56:27 -0700 Subject: [PATCH] consolidate auth into a single component --- spotlight-client/src/App.test.tsx | 9 ++- spotlight-client/src/App.tsx | 17 ++-- .../src/AuthProvider/AuthProvider.tsx | 44 ---------- spotlight-client/src/AuthProvider/index.ts | 18 ----- spotlight-client/src/AuthWall/AuthWall.tsx | 80 +++++++++++++------ .../utils.test.ts} | 29 ++++++- .../getAuthSettings.ts => AuthWall/utils.ts} | 6 +- .../src/utils/isAuthEnabled.test.ts | 44 ---------- spotlight-client/src/utils/isAuthEnabled.ts | 20 ----- 9 files changed, 102 insertions(+), 165 deletions(-) delete mode 100644 spotlight-client/src/AuthProvider/AuthProvider.tsx delete mode 100644 spotlight-client/src/AuthProvider/index.ts rename spotlight-client/src/{AuthProvider/getAuthSettings.test.ts => AuthWall/utils.test.ts} (65%) rename spotlight-client/src/{AuthProvider/getAuthSettings.ts => AuthWall/utils.ts} (88%) delete mode 100644 spotlight-client/src/utils/isAuthEnabled.test.ts delete mode 100644 spotlight-client/src/utils/isAuthEnabled.ts diff --git a/spotlight-client/src/App.test.tsx b/spotlight-client/src/App.test.tsx index 3095f465..6ceba538 100644 --- a/spotlight-client/src/App.test.tsx +++ b/spotlight-client/src/App.test.tsx @@ -19,8 +19,7 @@ import { Auth0Provider, useAuth0 } from "@auth0/auth0-react"; import { render } from "@testing-library/react"; import React from "react"; import App from "./App"; -import getAuthSettings from "./AuthProvider/getAuthSettings"; -import isAuthEnabled from "./utils/isAuthEnabled"; +import { getAuthSettings, isAuthEnabled } from "./AuthWall/utils"; test("does not explode", () => { const { getByRole } = render(); @@ -29,12 +28,14 @@ test("does not explode", () => { expect(websiteName).toBeInTheDocument(); }); -jest.mock("./AuthProvider/getAuthSettings"); +jest.mock("./AuthWall/utils", () => ({ + getAuthSettings: jest.fn(), + isAuthEnabled: jest.fn(), +})); const getAuthSettingsMock = getAuthSettings as jest.MockedFunction< typeof getAuthSettings >; -jest.mock("./utils/isAuthEnabled"); const isAuthEnabledMock = isAuthEnabled as jest.MockedFunction< typeof isAuthEnabled >; diff --git a/spotlight-client/src/App.tsx b/spotlight-client/src/App.tsx index bb7d188e..cd99de52 100644 --- a/spotlight-client/src/App.tsx +++ b/spotlight-client/src/App.tsx @@ -16,20 +16,17 @@ // ============================================================================= import React from "react"; -import AuthProvider from "./AuthProvider"; import AuthWall from "./AuthWall"; const App: React.FC = () => { return ( - - -
-
-

Spotlight

-
-
-
-
+ +
+
+

Spotlight

+
+
+
); }; diff --git a/spotlight-client/src/AuthProvider/AuthProvider.tsx b/spotlight-client/src/AuthProvider/AuthProvider.tsx deleted file mode 100644 index a1c300f8..00000000 --- a/spotlight-client/src/AuthProvider/AuthProvider.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Recidiviz - a data platform for criminal justice reform -// Copyright (C) 2020 Recidiviz, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// ============================================================================= - -import { Auth0Provider } from "@auth0/auth0-react"; -import React from "react"; -import isAuthEnabled from "../utils/isAuthEnabled"; -import getAuthSettings from "./getAuthSettings"; - -/** - * If auth is enabled for the current environment, wraps its children - * in an Auth0Provider to enable the Auth0 React context. - * If auth is disabled, renders its children unwrapped. - */ -const AuthProvider: React.FC = ({ children }) => { - const authSettings = getAuthSettings(); - if (isAuthEnabled() && authSettings) { - return ( - - {children} - - ); - } - return <>{children}; -}; - -export default AuthProvider; diff --git a/spotlight-client/src/AuthProvider/index.ts b/spotlight-client/src/AuthProvider/index.ts deleted file mode 100644 index 98001164..00000000 --- a/spotlight-client/src/AuthProvider/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Recidiviz - a data platform for criminal justice reform -// Copyright (C) 2020 Recidiviz, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// ============================================================================= - -export { default } from "./AuthProvider"; diff --git a/spotlight-client/src/AuthWall/AuthWall.tsx b/spotlight-client/src/AuthWall/AuthWall.tsx index 3b6d0ed0..d76636e8 100644 --- a/spotlight-client/src/AuthWall/AuthWall.tsx +++ b/spotlight-client/src/AuthWall/AuthWall.tsx @@ -15,47 +15,81 @@ // along with this program. If not, see . // ============================================================================= -import { useAuth0 } from "@auth0/auth0-react"; +import { Auth0Provider, useAuth0 } from "@auth0/auth0-react"; import React from "react"; import Loading from "../Loading"; -import isAuthEnabled from "../utils/isAuthEnabled"; +import { getAuthSettings, isAuthEnabled } from "./utils"; import VerificationRequired from "../VerificationRequired"; /** - * Requires that the user is authenticated before rendering its children, + * If auth is enabled for the current environment, wraps its children + * in an Auth0Provider to enable the Auth0 React context. + * If auth is disabled, renders its children unwrapped. + */ +const AuthProvider: React.FC<{ enabled: boolean }> = ({ + children, + enabled, +}) => { + const authSettings = getAuthSettings(); + + return enabled && authSettings ? ( + + {children} + + ) : ( + <>{children} + ); +}; + +/** + * If enabled is true, requires that + * the user is authenticated before rendering its children, * and redirects unauthenticated users to an Auth0 login domain. + * Otherwise, it simply renders its children without intervention. */ -const AuthChecker: React.FC = ({ children }) => { - const { isAuthenticated, isLoading, loginWithRedirect, user } = useAuth0(); +const AuthChecker: React.FC<{ enabled: boolean }> = ({ children, enabled }) => { + // providing a fallback because this hook may return undefined if auth0 is not enabled; + // seems to mainly happen in the test environment + const { isAuthenticated, isLoading, loginWithRedirect, user } = + useAuth0() || {}; - if (isLoading) { - return ; - } + if (enabled) { + if (isLoading) { + return ; + } - if (!isAuthenticated) { - loginWithRedirect(); - return ; - } + if (!isAuthenticated && loginWithRedirect) { + loginWithRedirect(); + return ; + } - if (!user.email_verified) { - return ; + if (user && !user.email_verified) { + return ; + } } return <>{children}; }; /** - * If auth is enabled in the current environment, wraps its children - * in the AuthWall to require authentication. If auth is disabled, - * renders its children unwrapped. + * If auth is enabled in the current environment, requires that + * the user is authenticated before rendering its children, + * and redirects unauthenticated users to an Auth0 login domain. + * If auth is disabled, it simply renders its children without intervention. + * MUST be a descendent of an AuthProvider. */ const AuthWall: React.FC = ({ children }) => { - // because AuthWall relies on hooks, we don't want to render it at all - // if auth is not enabled in this environment - if (isAuthEnabled()) { - return {children}; - } - return <>{children}; + const enabled = isAuthEnabled(); + + return ( + + {children} + + ); }; export default AuthWall; diff --git a/spotlight-client/src/AuthProvider/getAuthSettings.test.ts b/spotlight-client/src/AuthWall/utils.test.ts similarity index 65% rename from spotlight-client/src/AuthProvider/getAuthSettings.test.ts rename to spotlight-client/src/AuthWall/utils.test.ts index 7ae00a31..20cd204e 100644 --- a/spotlight-client/src/AuthProvider/getAuthSettings.test.ts +++ b/spotlight-client/src/AuthWall/utils.test.ts @@ -17,7 +17,7 @@ afterEach(() => { * for your test, if any. */ async function getGetAuthSettings() { - return (await import("./getAuthSettings")).default; + return (await import("./utils")).getAuthSettings; } test("returns nothing when the value is unset", async () => { @@ -47,6 +47,33 @@ test("returns a settings object when the value is supported", async () => { expect(typeof settings.domain).toBe("string"); expect(typeof settings.clientId).toBe("string"); }); +/** + * Dynamically imports the isAuthEnabled for testing. + * Should be used after setting the desired environment variables + * for your test, if any. + */ +async function getIsAuthEnabled() { + return (await import("./utils")).isAuthEnabled; +} + +test("returns false when the value is unset", async () => { + const isAuthEnabled = await getIsAuthEnabled(); + expect(isAuthEnabled()).toBe(false); +}); + +test("returns false when the value is not 'true'", async () => { + process.env.REACT_APP_AUTH_ENABLED = "false"; + + const isAuthEnabled = await getIsAuthEnabled(); + expect(isAuthEnabled()).toBe(false); +}); + +test("returns true when the value is 'true'", async () => { + process.env.REACT_APP_AUTH_ENABLED = "true"; + + const isAuthEnabled = await getIsAuthEnabled(); + expect(isAuthEnabled()).toBe(true); +}); // this doesn't do anything except appease Typescript; // because there are no top level imports it thinks this is not an "isolated module" diff --git a/spotlight-client/src/AuthProvider/getAuthSettings.ts b/spotlight-client/src/AuthWall/utils.ts similarity index 88% rename from spotlight-client/src/AuthProvider/getAuthSettings.ts rename to spotlight-client/src/AuthWall/utils.ts index 7a5a2b2b..9764fe57 100644 --- a/spotlight-client/src/AuthProvider/getAuthSettings.ts +++ b/spotlight-client/src/AuthWall/utils.ts @@ -33,6 +33,10 @@ if (process.env.REACT_APP_AUTH_ENV === "development") { /** * Returns the auth settings configured for the current environment, if any. */ -export default function getAuthSettings(): typeof AUTH_SETTINGS { +export function getAuthSettings(): typeof AUTH_SETTINGS { return AUTH_SETTINGS; } + +export function isAuthEnabled(): boolean { + return process.env.REACT_APP_AUTH_ENABLED === "true"; +} diff --git a/spotlight-client/src/utils/isAuthEnabled.test.ts b/spotlight-client/src/utils/isAuthEnabled.test.ts deleted file mode 100644 index bb835563..00000000 --- a/spotlight-client/src/utils/isAuthEnabled.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -// mocking the node env is esoteric, see https://stackoverflow.com/a/48042799 -const ORIGINAL_ENV = process.env; - -beforeEach(() => { - jest.resetModules(); - // make a copy that we can modify - process.env = { ...ORIGINAL_ENV }; -}); - -afterEach(() => { - process.env = ORIGINAL_ENV; -}); - -/** - * Dynamically imports the isAuthEnabled for testing. - * Should be used after setting the desired environment variables - * for your test, if any. - */ -async function getIsAuthEnabled() { - return (await import("./isAuthEnabled")).default; -} - -test("returns false when the value is unset", async () => { - const isAuthEnabled = await getIsAuthEnabled(); - expect(isAuthEnabled()).toBe(false); -}); - -test("returns false when the value is not 'true'", async () => { - process.env.REACT_APP_AUTH_ENABLED = "false"; - - const isAuthEnabled = await getIsAuthEnabled(); - expect(isAuthEnabled()).toBe(false); -}); - -test("returns true when the value is 'true'", async () => { - process.env.REACT_APP_AUTH_ENABLED = "true"; - - const isAuthEnabled = await getIsAuthEnabled(); - expect(isAuthEnabled()).toBe(true); -}); - -// this doesn't do anything except appease Typescript; -// because there are no top level imports it thinks this is not an "isolated module" -export {}; diff --git a/spotlight-client/src/utils/isAuthEnabled.ts b/spotlight-client/src/utils/isAuthEnabled.ts deleted file mode 100644 index a22ecb8a..00000000 --- a/spotlight-client/src/utils/isAuthEnabled.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Recidiviz - a data platform for criminal justice reform -// Copyright (C) 2020 Recidiviz, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// ============================================================================= - -export default function isAuthEnabled(): boolean { - return process.env.REACT_APP_AUTH_ENABLED === "true"; -}