-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optional Auth0 integration #229
Merged
Merged
Changes from 5 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
d206902
wrap entire application in optional Auth0 wall
macfarlandian 4a964a0
include copy of Auth0 config functions
macfarlandian 22d75df
document required environment variables
macfarlandian 779d8c3
add test for enabling auth
macfarlandian f9eb6fe
diff cleanup
macfarlandian eb59dd7
update readme
macfarlandian 03f7c06
add tests for mocked functions
macfarlandian 7485a81
require email verification before accepting auth
macfarlandian d54461d
use custom login domain
macfarlandian daadcec
fix lint-staged rules
macfarlandian e9217d0
fix lint errors
macfarlandian 847e4f6
consolidate auth into a single component
macfarlandian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
REACT_APP_AUTH_ENABLED={FLAG} | ||
REACT_APP_AUTH_ENV={ENV_NAME} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Auth0 Integration | ||
|
||
This application uses Auth0 as its authentication and authorization service. Auth0 provides a number | ||
of configuration options and can integrate with apps in a variety of ways. | ||
|
||
If you are so inclined to set up an Auth0 tenant, either in the event of disaster recovery or to | ||
support a new environment, these instructions should help. | ||
|
||
## Initial Setup | ||
|
||
Follow the initial setup instructions provided by Auth0 for creating a new account, or creating a | ||
new tenant within an existing account. Specifically, follow the quickstart for React apps | ||
[here](https://auth0.com/docs/quickstart/spa/react). Whether you are doing this only to test the | ||
app in full auth mode or for production purposes, you should at least start by configuring all of | ||
the various urls in that quickstart with the set of localhost urls, e.g. `http://localhost:3000` for | ||
the callback urls and logout urls. | ||
|
||
When going through the quickstart, you do not need to perform any of the coding-related steps. | ||
However, it would still be wise to read these steps to understand how the system works as a whole. | ||
|
||
### Connections | ||
|
||
At present, the app uses only the _Database_ connection, which provides basic username-password | ||
credential authentication. Other connections may be added in the future. | ||
|
||
## Rules and Hooks | ||
|
||
Auth0 has a system of [rules](https://auth0.com/docs/rules) and [hooks](https://auth0.com/docs/hooks) | ||
for expanding functionality. The names, order, and actual code for rules and hooks are configured in | ||
the Auth0 dashboard itself. However, for the sake of tracking updates, we commit those same rules | ||
and hooks in `auth0/`, even though the files therein _are not_ used by the app in any way. | ||
|
||
### Usage | ||
|
||
For each file in `auth0/rules/`, create a new rule. The name does not matter beyond reminding | ||
you what is in each rule at a glance. Copy and paste the full content of the file into the rule's | ||
code and save it. Order the rules in the same order they are in within the `/rules/` folder. | ||
|
||
Do the same thing for `auth0/hooks`, but creating a new hook for each file instead of a new | ||
rule. There are different kinds of hooks that execute at different points in the authentication | ||
workflow. Each hook file should have a suffix indicating which type of hook it should be created as. | ||
|
||
## Logging | ||
|
||
Auth0 maintains good logging for all interactions with Auth0 APIs. For compliance reasons, | ||
specifically the need to store authentication logs for longer retention periods, we copy Auth0 logs | ||
to segment. You can set this up by creating an Auth0 [extension](https://auth0.com/docs/extensions). | ||
If you are in a situation where you need to do this for Recidiviz, speak to someone internally about | ||
how to configure this. |
51 changes: 51 additions & 0 deletions
51
spotlight-client/auth0/hooks/01-domain-whitelist-pre-registration.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// 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/>. | ||
// ============================================================================= | ||
|
||
/** | ||
@param {object} user - The user being created | ||
@param {string} user.tenant - Auth0 tenant name | ||
@param {string} user.username - user name | ||
@param {string} user.password - user's password | ||
@param {string} user.email - email | ||
@param {boolean} user.emailVerified - is e-mail verified? | ||
@param {string} user.phoneNumber - phone number | ||
@param {boolean} user.phoneNumberVerified - is phone number verified? | ||
@param {object} context - Auth0 connection and other context info | ||
@param {string} context.requestLanguage - language of the client agent | ||
@param {object} context.connection - information about the Auth0 connection | ||
@param {object} context.connection.id - connection id | ||
@param {object} context.connection.name - connection name | ||
@param {object} context.connection.tenant - connection tenant | ||
@param {object} context.webtask - webtask context | ||
@param {function} cb - function (error, response) | ||
*/ | ||
module.exports = function (user, context, cb) { | ||
const response = {}; | ||
|
||
const whitelist = []; // add authorized domains here | ||
const userHasAccess = whitelist.some(function (domain) { | ||
const emailSplit = user.email.split("@"); | ||
return emailSplit[emailSplit.length - 1].toLowerCase() === domain; | ||
}); | ||
|
||
if (userHasAccess) { | ||
response.user = user; | ||
cb(null, response); | ||
} else { | ||
cb("Access denied.", null); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,13 +15,84 @@ | |
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
// ============================================================================= | ||
|
||
import React from "react"; | ||
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"; | ||
|
||
test("does not explode", () => { | ||
const { getByText } = render(<App />); | ||
const { getByRole } = render(<App />); | ||
// seems like a pretty safe bet this word will always be there somewhere! | ||
const websiteName = getByText(/spotlight/i); | ||
const websiteName = getByRole("heading", /spotlight/i); | ||
expect(websiteName).toBeInTheDocument(); | ||
}); | ||
|
||
jest.mock("./AuthProvider/getAuthSettings"); | ||
const getAuthSettingsMock = getAuthSettings as jest.MockedFunction< | ||
typeof getAuthSettings | ||
>; | ||
|
||
jest.mock("./utils/isAuthEnabled"); | ||
const isAuthEnabledMock = isAuthEnabled as jest.MockedFunction< | ||
typeof isAuthEnabled | ||
>; | ||
|
||
// Although mocking the Auth0 library is not necessarily a great practice, | ||
// it has a number of side effects (e.g. issuing XHRs, navigating to new URLs) | ||
// that are challenging to handle or even simulate in this test environment, | ||
// so this seemed like the better solution here | ||
jest.mock("@auth0/auth0-react", () => { | ||
return { | ||
Auth0Provider: jest.fn(), | ||
useAuth0: jest.fn(), | ||
}; | ||
}); | ||
|
||
test("require auth", async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Amazing test 🚀 |
||
// mock the environment configuration to enable auth | ||
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>; | ||
}); | ||
|
||
// 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, | ||
}); | ||
|
||
// now we test | ||
const { queryByRole, getByTestId } = render(<App />); | ||
|
||
// verify that we have included an Auth0Provider | ||
expect(getByTestId(PROVIDER_TEST_ID)).toBeInTheDocument(); | ||
|
||
// 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", /spotlight/i)).toBeNull(); | ||
|
||
jest.restoreAllMocks(); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// 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 { 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 ( | ||
<Auth0Provider | ||
domain={authSettings.domain} | ||
clientId={authSettings.clientId} | ||
redirectUri={window.location.href} | ||
> | ||
{children} | ||
</Auth0Provider> | ||
); | ||
} | ||
return <>{children}</>; | ||
}; | ||
|
||
export default AuthProvider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// 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/>. | ||
// ============================================================================= | ||
|
||
type Auth0Settings = { | ||
domain: string; | ||
clientId: string; | ||
}; | ||
|
||
let AUTH_SETTINGS: Auth0Settings | undefined; | ||
|
||
// NOTE: there is no production auth requirement! | ||
if (process.env.REACT_APP_AUTH_ENV === "development") { | ||
AUTH_SETTINGS = { | ||
domain: "recidiviz-spotlight-staging.us.auth0.com", | ||
clientId: "ID9plpd8j4vaUin9rPTGxWlJoknSkDX1", | ||
}; | ||
} | ||
|
||
/** | ||
* Returns the auth settings configured for the current environment, if any. | ||
*/ | ||
export default function getAuthSettings(): typeof AUTH_SETTINGS { | ||
return AUTH_SETTINGS; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 "./AuthProvider"; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this true and relevant? I thought we only had one auth environment for this app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes you are right ... I adapted this from the pulse readme and was a little unsure of how to handle the production case. But after sleeping on it, it seems pretty obvious to me that the docs should just reflect the current state of things (just the one environment) and not worry about being future-proof. I'll update this!