From 4e398fb8ed91c40aada9c4dd21bb5f5aaef2482a Mon Sep 17 00:00:00 2001 From: Agustina Chaer Date: Wed, 29 Aug 2018 03:03:28 -0300 Subject: [PATCH] feat: Enhancement/custom token fetch fn (#29) * fix access_token passthrough * changed fetch function * Custom fetch functions fix: #28, #30 --- README.md | 16 +++++ src/OauthReceiver/OauthReceiver.test.js | 81 +++++++++++++++++++++++++ src/OauthReceiver/index.js | 9 ++- 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8c9d11..92c9f8b 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,8 @@ redirected from the OAuth2-provider. | `location` | `{ search: string }` | no | - | Used to extract info from querystring [(read more below)](#location-and-querystring) | | `querystring` | `string` | no | - | Used to extract info from querystring [(read more below)](#location-and-querystring) | | `tokenFetchArgs` | `object` | no | `{}` | Used to fetch the token endpoint [(read more below)](#tokenfetchargs) | +| `tokenFn` | `func` | no | `null` | Used to bypass default fetch function to fetch the token [(read more below)](#tokenfn) | + #### Events @@ -274,6 +276,20 @@ merged and overriden with the `tokenFetchArgs`: { method: 'GET', headers: { 'Content-Type': 'application/json' }} ``` +#### `tokenFn` + +The prop `tokenFn` can be used to change how the token is fetched and received from +the service. It's a way to bypass the default fetch all together and use your own. +For example, if your `access-token` comes in the headers instead of the response body +you will have to use your own fetch function to get those. Or perhaps you already +have a custom built fetch function that communicates with your backend and you want +to make use of it. + +Your function will receive the `url` from the OauthReceiver, it takes the +`tokenUrl` and builds it up with all the other needed parameters so you don't have to. +It will also receive the `tokenFetchArgs` parameter just in case you need it. if you don't, +just ignore it. + ### `createOauthFlow` ```js diff --git a/src/OauthReceiver/OauthReceiver.test.js b/src/OauthReceiver/OauthReceiver.test.js index 8f7a880..4b4d7c4 100644 --- a/src/OauthReceiver/OauthReceiver.test.js +++ b/src/OauthReceiver/OauthReceiver.test.js @@ -77,6 +77,87 @@ describe('Component ', () => { await waitForElement(() => getByTestId('done')); + expect(fetch2).toHaveBeenCalledWith( + expect.stringContaining(props.tokenUrl), + expect.objectContaining({ + method: expect.stringMatching('POST'), + cache: expect.stringMatching('no-cache'), + headers: expect.objectContaining({ + 'Content-Type': expect.stringMatching( + 'application/x-www-form-urlencoded', + ), + }), + }), + ); + }); + test('with custom fetch function, default args', async () => { + const onAuthSuccess = jest.fn(); + const onAuthError = jest.fn(); + + const props = { + tokenUrl: 'https://api.service.com/oauth2/token', + tokenFn: fetch2, + clientId: 'abc', + clientSecret: 'abcdef', + redirectUri: 'https://www.test.com/redirect', + querystring: `?${qs.stringify({ + code: 'abc', + state: JSON.stringify({ from: '/success' }), + })}`, + onAuthSuccess, + onAuthError, + }; + + const { getByTestId } = render( + ( +
+ {processing && done} + {state && state.from} +
+ )} + />, + ); + + await waitForElement(() => getByTestId('done')); + + expect(onAuthSuccess).toHaveBeenCalledTimes(1); + expect(onAuthError).not.toHaveBeenCalled(); + + expect(getByTestId('state')).toHaveTextContent('/success'); + }); + + test('with custom token function and token uri fetch args', async () => { + fetch2.mockClear(); + + const props = { + tokenUrl: 'https://api.service.com/oauth2/token', + tokenFn: fetch2, + tokenFetchArgs: { + cache: 'no-cache', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + }, + clientId: 'abc', + clientSecret: 'abcdef', + redirectUri: 'https://www.test.com/redirect', + querystring: `?${qs.stringify({ + code: 'abc', + state: JSON.stringify({ from: '/settings' }), + })}`, + }; + + const { getByTestId } = render( + ( +
{processing && done}
+ )} + />, + ); + + await waitForElement(() => getByTestId('done')); + expect(fetch2).toHaveBeenCalledWith( expect.stringContaining(props.tokenUrl), expect.objectContaining({ diff --git a/src/OauthReceiver/index.js b/src/OauthReceiver/index.js index 6d18ad8..1d48bbe 100644 --- a/src/OauthReceiver/index.js +++ b/src/OauthReceiver/index.js @@ -32,6 +32,7 @@ class OauthReceiver extends React.Component { clientSecret, redirectUri, args, + tokenFn, onAuthSuccess, } = this.props; @@ -60,8 +61,10 @@ class OauthReceiver extends React.Component { const defaultFetchArgs = { method: 'POST', headers }; const fetchArgs = Object.assign(defaultFetchArgs, tokenFetchArgs); - fetch2(url, fetchArgs) - .then(response => { + (typeof tokenFn === 'function' ? + tokenFn(url, fetchArgs) : + fetch2(url, fetchArgs) + ).then(response => { const accessToken = response.access_token; if (typeof onAuthSuccess === 'function') { @@ -140,6 +143,7 @@ OauthReceiver.propTypes = { ), location: PropTypes.shape({ search: PropTypes.string.isRequired }), querystring: PropTypes.string, + tokenFn: PropTypes.func, onAuthSuccess: PropTypes.func, onAuthError: PropTypes.func, render: PropTypes.func, @@ -154,6 +158,7 @@ OauthReceiver.defaultProps = { args: {}, location: null, querystring: null, + tokenFn: null, onAuthSuccess: null, onAuthError: null, render: null,