-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #101 from tasoskakour/customise-query-fn
Offer more customization over exchangeCodeForToken query
- Loading branch information
Showing
17 changed files
with
514 additions
and
106 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,91 @@ | ||
import puppeteer, { Browser } from 'puppeteer'; | ||
import { getTextContent } from './test-utils'; | ||
|
||
const URL = 'http://localhost:3000'; | ||
|
||
let browser: Browser; | ||
afterAll((done) => { | ||
browser.close(); | ||
|
||
done(); | ||
}); | ||
|
||
test('Login with authorization code flow and exchangeCodeForQueryFn works as expected', async () => { | ||
browser = await puppeteer.launch({ headless: 'new' }); | ||
const page = await browser.newPage(); | ||
|
||
await page.goto(URL); | ||
|
||
const nav = new Promise((response) => { | ||
browser.on('targetcreated', response); | ||
}); | ||
|
||
await page.click('#authorization-code-queryfn-login'); | ||
|
||
// Assess loading | ||
await page.waitForSelector('#authorization-code-queryfn-loading'); | ||
|
||
// Assess popup redirection | ||
await nav; | ||
const pages = await browser.pages(); | ||
expect(pages[2].url()).toMatch( | ||
/http:\/\/localhost:3000\/callback\?code=SOME_CODE&state=.*\S.*/ // any non-empty state | ||
); | ||
|
||
// Assess network call to exchange code for token | ||
await page.waitForResponse(async (response) => { | ||
if (response.request().method().toUpperCase() === 'OPTIONS') return false; | ||
|
||
const url = decodeURIComponent(response.url()); | ||
const json = await response.json(); | ||
const urlPath = url.split('?')[0]; | ||
|
||
return ( | ||
urlPath === 'http://localhost:3001/mock-token-form-data' && | ||
response.request().method().toUpperCase() === 'POST' && | ||
response.request().postData() === 'code=SOME_CODE&someOtherData=someOtherData' && | ||
json.code === 'SOME_CODE' && | ||
json.access_token === 'SOME_ACCESS_TOKEN' && | ||
json.expires_in === 3600 && | ||
json.refresh_token === 'SOME_REFRESH_TOKEN' && | ||
json.scope === 'SOME_SCOPE' && | ||
json.token_type === 'Bearer' | ||
); | ||
}); | ||
|
||
// Assess UI | ||
await page.waitForSelector('#authorization-code-queryfn-data'); | ||
expect(await getTextContent(page, '#authorization-code-queryfn-data')).toBe( | ||
'{"code":"SOME_CODE","access_token":"SOME_ACCESS_TOKEN","expires_in":3600,"refresh_token":"SOME_REFRESH_TOKEN","scope":"SOME_SCOPE","token_type":"Bearer"}' | ||
); | ||
|
||
// Assess localStorage | ||
expect( | ||
await page.evaluate(() => | ||
JSON.parse( | ||
window.localStorage.getItem( | ||
'code-http://localhost:3001/mock-authorize-SOME_CLIENT_ID_2-SOME_SCOPE' | ||
) || '' | ||
) | ||
) | ||
).toEqual({ | ||
code: 'SOME_CODE', | ||
access_token: 'SOME_ACCESS_TOKEN', | ||
expires_in: 3600, | ||
refresh_token: 'SOME_REFRESH_TOKEN', | ||
scope: 'SOME_SCOPE', | ||
token_type: 'Bearer', | ||
}); | ||
|
||
// Logout | ||
await page.click('#authorization-code-queryfn-logout'); | ||
expect(await page.$('#authorization-code-queryfn-data')).toBe(null); | ||
expect(await page.$('#authorization-code-queryfn-login')).not.toBe(null); | ||
expect( | ||
await page.evaluate(() => | ||
window.localStorage.getItem( | ||
'code-http://localhost:3001/mock-authorize-SOME_CLIENT_ID_2-SOME_SCOPE' | ||
) | ||
) | ||
).toEqual('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
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,81 @@ | ||
/* eslint-disable no-console */ | ||
import { useOAuth2 } from '../../src/components'; | ||
|
||
type TMyAuthData = { | ||
access_token: string; | ||
}; | ||
|
||
const LoginCode = () => { | ||
const { data, loading, error, getAuth, logout } = useOAuth2<TMyAuthData>({ | ||
authorizeUrl: 'http://localhost:3001/mock-authorize', | ||
clientId: 'SOME_CLIENT_ID_2', | ||
redirectUri: `${document.location.origin}/callback`, | ||
scope: 'SOME_SCOPE', | ||
responseType: 'code', | ||
exchangeCodeForTokenQueryFn: async (callbackParameters: { code: string }) => { | ||
const jsonObject = { | ||
code: callbackParameters.code, | ||
someOtherData: 'someOtherData', | ||
}; | ||
const formBody = []; | ||
// eslint-disable-next-line no-restricted-syntax, guard-for-in | ||
for (const key in jsonObject) { | ||
formBody.push( | ||
`${encodeURIComponent(key)}=${encodeURIComponent(jsonObject[key as keyof typeof jsonObject])}` | ||
); | ||
} | ||
const response = await fetch(`http://localhost:3001/mock-token-form-data`, { | ||
method: 'POST', | ||
body: formBody.join('&'), | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', | ||
}, | ||
}); | ||
if (!response.ok) throw new Error('exchangeCodeForTokenQueryFn fail at example'); | ||
const tokenData = await response.json(); | ||
return tokenData; | ||
}, | ||
onSuccess: (payload) => console.log('Success', payload), | ||
onError: (error_) => console.log('Error', error_), | ||
}); | ||
|
||
const isLoggedIn = Boolean(data?.access_token); // or whatever... | ||
|
||
let ui = ( | ||
<button type="button" id="authorization-code-queryfn-login" onClick={() => getAuth()}> | ||
Login with Authorization Code with QueryFn | ||
</button> | ||
); | ||
|
||
if (error) { | ||
ui = <div>Error</div>; | ||
} | ||
|
||
if (loading) { | ||
ui = <div id="authorization-code-queryfn-loading">Loading...</div>; | ||
} | ||
|
||
if (isLoggedIn) { | ||
ui = ( | ||
<div> | ||
<pre id="authorization-code-queryfn-data">{JSON.stringify(data)}</pre> | ||
<button | ||
id="authorization-code-queryfn-logout" | ||
type="button" | ||
onClick={() => logout()} | ||
> | ||
Logout | ||
</button> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div style={{ margin: '24px' }}> | ||
<h2> Login with Authorization Code with QueryFn</h2> | ||
{ui} | ||
</div> | ||
); | ||
}; | ||
|
||
export default LoginCode; |
Oops, something went wrong.