-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add bearer token just to user-profile
partially adresses #2263 1. Adds a new hook useAuthentcatedFetch which provides a version of fetch that includes the Authentication header when the user is logged in 2. Adds a new useAuthenticatedApiCall which provides a wrapped apiCall using that new authentication header 3. Uses that new authenticatedApiCall for the PUT/POST calls for user-profile Next step will be to integrate with other calls in the app. I also did not attempt to add component integration tests where they do not already exist.
- Loading branch information
Showing
6 changed files
with
231 additions
and
12 deletions.
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
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 @@ | ||
import { renderHook } from "@testing-library/react"; | ||
import { useAuthenticatedApiCall } from "../../api/call"; | ||
import * as authenticatedFetch from "../../hooks/useAuthenticatedFetch"; | ||
|
||
jest.mock("../../hooks/useAuthenticatedFetch"); | ||
let mock_authenticated_fetch; | ||
const DEFAULT_FETCH_RESULT = { | ||
status: 200, | ||
}; | ||
|
||
const SOME_REQUEST_BODY = { | ||
content: "BLAH", | ||
}; | ||
|
||
describe("useAuthenticatedApiCall", () => { | ||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
|
||
mock_authenticated_fetch = jest.fn(() => | ||
Promise.resolve(DEFAULT_FETCH_RESULT), | ||
); | ||
|
||
authenticatedFetch.useAuthenticatedFetch.mockReturnValue({ | ||
authenticatedFetch: mock_authenticated_fetch, | ||
}); | ||
}); | ||
|
||
test("it should wrap fetch with authenticatedFetch", async () => { | ||
const { result } = renderHook(() => useAuthenticatedApiCall()); | ||
|
||
const response = await result.current.authenticatedApiCall( | ||
"/test/path", | ||
SOME_REQUEST_BODY, | ||
"POST", | ||
); | ||
|
||
expect(response).toEqual(DEFAULT_FETCH_RESULT); | ||
expect(mock_authenticated_fetch.mock.calls[0]).toEqual([ | ||
"https://api.policyengine.org/test/path", | ||
{ | ||
body: JSON.stringify(SOME_REQUEST_BODY), | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
method: "POST", | ||
}, | ||
]); | ||
}); | ||
}); |
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,92 @@ | ||
import { renderHook } from "@testing-library/react"; | ||
import { useAuthenticatedFetch } from "../../hooks/useAuthenticatedFetch"; | ||
import auth0 from "@auth0/auth0-react"; | ||
|
||
jest.mock("@auth0/auth0-react"); | ||
const DEFAULT_FETCH_RESULT = "ok"; | ||
let mockFetch; | ||
|
||
describe("useAuthenticatedFetch", () => { | ||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
auth0.useAuth0.mockReturnValue({ | ||
isAuthenticated: false, | ||
getAccessTokenSilently: async () => { | ||
throw new Error("TEST ATTEMPTED TO CALL GET ACCESS TOKENS"); | ||
}, | ||
}); | ||
mockFetch = jest.fn(() => Promise.resolve(DEFAULT_FETCH_RESULT)); | ||
global.fetch = mockFetch; | ||
}); | ||
|
||
function givenTheUserIsLoggedIn(auth_token) { | ||
auth0.useAuth0.mockReturnValue({ | ||
isAuthenticated: true, | ||
getAccessTokenSilently: async () => auth_token ?? "TEST_AUTH_TOKEN", | ||
}); | ||
} | ||
|
||
function givenAuth0TokenCannotBeCreated() { | ||
auth0.useAuth0.mockReturnValue({ | ||
isAuthenticated: true, | ||
getAccessTokenSilently: async () => { | ||
throw new Error("TEST ATTEMPTED TO CALL GET ACCESS TOKENS"); | ||
}, | ||
}); | ||
} | ||
test("given the user is logged in then it adds the bearer token", async () => { | ||
givenTheUserIsLoggedIn("TEST_AUTH_TOKEN"); | ||
const requestOptions = { | ||
headers: { | ||
whatever: "value", | ||
}, | ||
}; | ||
|
||
const { result } = renderHook(() => useAuthenticatedFetch()); | ||
const response = await result.current.authenticatedFetch( | ||
"/test/path", | ||
requestOptions, | ||
); | ||
|
||
expect(response).toEqual(DEFAULT_FETCH_RESULT); | ||
expect(mockFetch.mock.calls[0]).toEqual([ | ||
"/test/path", | ||
{ | ||
...requestOptions, | ||
headers: { | ||
...requestOptions.headers, | ||
Authentication: "Bearer TEST_AUTH_TOKEN", | ||
}, | ||
}, | ||
]); | ||
}); | ||
test("given the user is not logged in then it adds nothing", async () => { | ||
const { result } = renderHook(() => useAuthenticatedFetch()); | ||
|
||
const response = await result.current.authenticatedFetch("/test/path", { | ||
headers: { whatever: "value" }, | ||
}); | ||
|
||
expect(response).toEqual(DEFAULT_FETCH_RESULT); | ||
expect(mockFetch.mock.calls[0]).toEqual([ | ||
"/test/path", | ||
{ headers: { whatever: "value" } }, | ||
]); | ||
}); | ||
|
||
test("given auth0 is not able to get a token then it ignores the error and adds nothing", async () => { | ||
givenAuth0TokenCannotBeCreated(); | ||
|
||
const { result } = renderHook(() => useAuthenticatedFetch()); | ||
|
||
const response = await result.current.authenticatedFetch("/test/path", { | ||
headers: { whatever: "value" }, | ||
}); | ||
|
||
expect(response).toEqual(DEFAULT_FETCH_RESULT); | ||
expect(mockFetch.mock.calls[0]).toEqual([ | ||
"/test/path", | ||
{ headers: { whatever: "value" } }, | ||
]); | ||
}); | ||
}); |
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,38 @@ | ||
import { useAuth0 } from "@auth0/auth0-react"; | ||
import { useCallback } from "react"; | ||
|
||
/** | ||
* Get an 'authenticatedFetch' function which, if the user is logged in, | ||
* will automatically attach an access token to any API request. | ||
* @returns {{authenticatedFetch:(path:string, opts:Record)=>Promise}} | ||
*/ | ||
export function useAuthenticatedFetch() { | ||
const { isAuthenticated, getAccessTokenSilently } = useAuth0(); | ||
|
||
const authenticatedFetch = useCallback( | ||
async (path, opts) => { | ||
opts = opts ?? {}; | ||
const headers = { ...(opts.headers ?? {}) }; | ||
|
||
if (isAuthenticated) { | ||
try { | ||
//as per https://auth0.com/docs/quickstart/spa/react/02-calling-an-api | ||
const accessToken = await getAccessTokenSilently(); | ||
headers["Authentication"] = `Bearer ${accessToken}`; | ||
} catch (error) { | ||
//IGNORE. If we can't get an access token we just call the API | ||
//without it. | ||
} | ||
} | ||
|
||
return await fetch(path, { | ||
...opts, | ||
headers, | ||
}); | ||
}, | ||
[isAuthenticated, getAccessTokenSilently], | ||
); | ||
return { | ||
authenticatedFetch, | ||
}; | ||
} |
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