Skip to content

Commit

Permalink
require email verification before accepting auth
Browse files Browse the repository at this point in the history
  • Loading branch information
macfarlandian committed Oct 16, 2020
1 parent 03f7c06 commit 7485a81
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 37 deletions.
101 changes: 67 additions & 34 deletions spotlight-client/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,49 +50,82 @@ jest.mock("@auth0/auth0-react", () => {
};
});

test("require auth", async () => {
// mock the environment configuration to enable auth
describe("with auth required", () => {
const MOCK_DOMAIN = "test.local";
const MOCK_CLIENT_ID = "abcdef";
getAuthSettingsMock.mockReturnValue({
domain: MOCK_DOMAIN,
clientId: MOCK_CLIENT_ID,
});
isAuthEnabledMock.mockReturnValue(true);

// mock the auth0 provider
const PROVIDER_TEST_ID = "Auth0Provider";
(Auth0Provider as jest.Mock).mockImplementation(({ children }) => {
// here we mock just enough to verify that the context provider is being included;
// we can use this as a proxy for the relationship between provider and hook.
return <div data-testid={PROVIDER_TEST_ID}>{children}</div>;

beforeEach(() => {
// mock the environment configuration to enable auth
getAuthSettingsMock.mockReturnValue({
domain: MOCK_DOMAIN,
clientId: MOCK_CLIENT_ID,
});
isAuthEnabledMock.mockReturnValue(true);
});

// mock the auth0 hook
const mockLoginWithRedirect = jest.fn();
(useAuth0 as jest.Mock).mockReturnValue({
// this isn't the full hook API, just what's relevant to this test
isAuthenticated: false,
loginWithRedirect: mockLoginWithRedirect,
afterEach(() => {
jest.restoreAllMocks();
});

// now we test
const { queryByRole, getByTestId } = render(<App />);
test("require auth for the entire site", async () => {
// mock the auth0 provider
const PROVIDER_TEST_ID = "Auth0Provider";
(Auth0Provider as jest.Mock).mockImplementation(({ children }) => {
// here we mock just enough to verify that the context provider is being included;
// we can use this as a proxy for the relationship between provider and hook.
return <div data-testid={PROVIDER_TEST_ID}>{children}</div>;
});

// mock the auth0 hook
const mockLoginWithRedirect = jest.fn();
(useAuth0 as jest.Mock).mockReturnValue({
isAuthenticated: false,
loginWithRedirect: mockLoginWithRedirect,
});

// verify that we have included an Auth0Provider
expect(getByTestId(PROVIDER_TEST_ID)).toBeInTheDocument();
const { queryByRole, getByRole, getByTestId } = render(<App />);

// verify that we supplied that provider with
// the settings designated by our mock environment
expect((Auth0Provider as jest.Mock).mock.calls[0][0]).toEqual(
expect.objectContaining({ domain: MOCK_DOMAIN, clientId: MOCK_CLIENT_ID })
);
// verify that we have included an Auth0Provider
expect(getByTestId(PROVIDER_TEST_ID)).toBeInTheDocument();

// verify that we have initiated an Auth0 login
expect(mockLoginWithRedirect.mock.calls.length).toBe(1);
// verify that we supplied that provider with
// the settings designated by our mock environment
expect((Auth0Provider as jest.Mock).mock.calls[0][0]).toEqual(
expect.objectContaining({ domain: MOCK_DOMAIN, clientId: MOCK_CLIENT_ID })
);

// verify that we have initiated an Auth0 login
expect(mockLoginWithRedirect.mock.calls.length).toBe(1);

// application contents should not have been rendered unauthed
expect(queryByRole("heading", { name: /spotlight/i })).toBeNull();
expect(getByRole("status", { name: /loading/i })).toBeInTheDocument();
});

// application contents should not have been rendered unauthed
expect(queryByRole("heading", /spotlight/i)).toBeNull();
test("require email verification for authed users", () => {
(useAuth0 as jest.Mock).mockReturnValue({
isAuthenticated: true,
user: {
email_verified: false,
},
});

jest.restoreAllMocks();
const { getByRole, queryByRole } = render(<App />);
// application contents should not have been rendered without verification
expect(queryByRole("heading", { name: /spotlight/i })).toBeNull();
// there should be a message about the verification requirement
expect(getByRole("heading", { name: /verification/i })).toBeInTheDocument();
});

test("loading state", () => {
(useAuth0 as jest.Mock).mockReturnValue({
isLoading: true,
});

const { queryByRole, getByRole } = render(<App />);

// application contents should not have been rendered while auth is pending
expect(queryByRole("heading", { name: /spotlight/i })).toBeNull();
expect(getByRole("status", { name: /loading/i })).toBeInTheDocument();
});
});
12 changes: 9 additions & 3 deletions spotlight-client/src/AuthWall/AuthWall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,28 @@

import { useAuth0 } from "@auth0/auth0-react";
import React from "react";
import Loading from "../Loading";
import isAuthEnabled from "../utils/isAuthEnabled";
import VerificationRequired from "../VerificationRequired";

/**
* Requires that the user is authenticated before rendering its children,
* and redirects unauthenticated users to an Auth0 login domain.
*/
const AuthChecker: React.FC = ({ children }) => {
const { isAuthenticated, isLoading, loginWithRedirect } = useAuth0();
const { isAuthenticated, isLoading, loginWithRedirect, user } = useAuth0();

if (isLoading) {
return <>Loading...</>;
return <Loading />;
}

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

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

return <>{children}</>;
Expand Down
28 changes: 28 additions & 0 deletions spotlight-client/src/Loading/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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 <https://www.gnu.org/licenses/>.
// =============================================================================

import React from "react";

const Loading: React.FC = () => {
return (
<div role="status" aria-label="loading">
Loading …
</div>
);
};

export default Loading;
18 changes: 18 additions & 0 deletions spotlight-client/src/Loading/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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 <https://www.gnu.org/licenses/>.
// =============================================================================

export { default } from './Loading';
33 changes: 33 additions & 0 deletions spotlight-client/src/VerificationRequired/VerificationRequired.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 <https://www.gnu.org/licenses/>.
// =============================================================================

import React from "react";

const VerificationRequired: React.FC = () => {
return (
<article>
<h1>Account Verification Required</h1>
<p>
Look for a verification email in your inbox and click the link in that
email. After you verify your email, refresh this page and you should be
able to proceed.
</p>
</article>
);
};

export default VerificationRequired;
18 changes: 18 additions & 0 deletions spotlight-client/src/VerificationRequired/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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 <https://www.gnu.org/licenses/>.
// =============================================================================

export { default } from './VerificationRequired';

0 comments on commit 7485a81

Please sign in to comment.