Skip to content

Commit

Permalink
consolidate auth into a single component
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian committed Oct 17, 2020
1 parent e9217d0 commit 847e4f6
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 165 deletions.
9 changes: 5 additions & 4 deletions spotlight-client/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(<App />);
Expand All @@ -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
>;
Expand Down
17 changes: 7 additions & 10 deletions spotlight-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@
// =============================================================================

import React from "react";
import AuthProvider from "./AuthProvider";
import AuthWall from "./AuthWall";

const App: React.FC = () => {
return (
<AuthProvider>
<AuthWall>
<div>
<header>
<h1>Spotlight</h1>
</header>
</div>
</AuthWall>
</AuthProvider>
<AuthWall>
<div>
<header>
<h1>Spotlight</h1>
</header>
</div>
</AuthWall>
);
};

Expand Down
44 changes: 0 additions & 44 deletions spotlight-client/src/AuthProvider/AuthProvider.tsx

This file was deleted.

18 changes: 0 additions & 18 deletions spotlight-client/src/AuthProvider/index.ts

This file was deleted.

80 changes: 57 additions & 23 deletions spotlight-client/src/AuthWall/AuthWall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,81 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =============================================================================

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 ? (
<Auth0Provider
domain={authSettings.domain}
clientId={authSettings.clientId}
redirectUri={window.location.href}
>
{children}
</Auth0Provider>
) : (
<>{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 <Loading />;
}
if (enabled) {
if (isLoading) {
return <Loading />;
}

if (!isAuthenticated) {
loginWithRedirect();
return <Loading />;
}
if (!isAuthenticated && loginWithRedirect) {
loginWithRedirect();
return <Loading />;
}

if (!user.email_verified) {
return <VerificationRequired />;
if (user && !user.email_verified) {
return <VerificationRequired />;
}
}

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 <AuthChecker>{children}</AuthChecker>;
}
return <>{children}</>;
const enabled = isAuthEnabled();

return (
<AuthProvider enabled={enabled}>
<AuthChecker enabled={enabled}>{children}</AuthChecker>
</AuthProvider>
);
};

export default AuthWall;
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
44 changes: 0 additions & 44 deletions spotlight-client/src/utils/isAuthEnabled.test.ts

This file was deleted.

20 changes: 0 additions & 20 deletions spotlight-client/src/utils/isAuthEnabled.ts

This file was deleted.

0 comments on commit 847e4f6

Please sign in to comment.