Skip to content
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

Fix query parsing when using hash routing #231

Merged
merged 2 commits into from
Oct 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 159 additions & 2 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,10 +521,10 @@ describe('Auth0', () => {
it('throws when there is no query string', async () => {
const { auth0 } = await setup();
await expect(auth0.handleRedirectCallback()).rejects.toThrow(
'There are no query params available at `window.location.search`.'
'There are no query params available for parsing.'
);
});
describe('when there is a valid query string', async () => {
describe('when there is a valid query string in the url', async () => {
const localSetup = async () => {
window.history.pushState(
{},
Expand Down Expand Up @@ -675,6 +675,163 @@ describe('Auth0', () => {

const response = await auth0.handleRedirectCallback();

expect(response).toEqual({
appState: TEST_APP_STATE
});
});
});
describe('when there is a valid query string in a hash', async () => {
const localSetup = async () => {
window.history.pushState({}, 'Test', `/`);
window.history.pushState(
{},
'Test',
`#/callback/?code=${TEST_CODE}&state=${TEST_ENCODED_STATE}`
);
const result = await setup();
result.transactionManager.get.mockReturnValue({
code_verifier: TEST_RANDOM_STRING,
nonce: TEST_RANDOM_STRING,
audience: 'default',
scope: TEST_SCOPES,
appState: TEST_APP_STATE
});
result.cache.get.mockReturnValue({ access_token: TEST_ACCESS_TOKEN });
return result;
};
it('calls parseQueryResult correctly', async () => {
const { auth0, utils } = await localSetup();
await auth0.handleRedirectCallback();
expect(utils.parseQueryResult).toHaveBeenCalledWith(
`code=${TEST_CODE}&state=${TEST_ENCODED_STATE}`
);
});
it('uses `state` from parsed query to get a transaction', async () => {
const { auth0, utils, transactionManager } = await localSetup();
const queryState = 'the-state';
utils.parseQueryResult.mockReturnValue({ state: queryState });

await auth0.handleRedirectCallback();

expect(transactionManager.get).toHaveBeenCalledWith(queryState);
});
it('throws error with AuthenticationError', async () => {
const { auth0, utils } = await localSetup();
const queryResult = { error: 'unauthorized' };
utils.parseQueryResult.mockReturnValue(queryResult);

await expect(auth0.handleRedirectCallback()).rejects.toBeInstanceOf(
AuthenticationError
);
});
it('throws AuthenticationError with message from error_description', async () => {
const { auth0, utils } = await localSetup();
const queryResult = {
error: 'unauthorized',
error_description: 'Unauthorized user'
};
utils.parseQueryResult.mockReturnValue(queryResult);

await expect(auth0.handleRedirectCallback()).rejects.toThrow(
queryResult.error_description
);
});
it('throws AuthenticationError with state, error, error_description', async () => {
const { auth0, utils } = await localSetup();
const queryResult = {
error: 'unauthorized',
error_description: 'Unauthorized user',
state: 'abcxyz'
};
utils.parseQueryResult.mockReturnValue(queryResult);

let errorThrown: AuthenticationError;
try {
await auth0.handleRedirectCallback();
} catch (error) {
errorThrown = error;
}

expect(errorThrown.state).toEqual(queryResult.state);
expect(errorThrown.error).toEqual(queryResult.error);
expect(errorThrown.error_description).toEqual(
queryResult.error_description
);
});
it('throws error when there is no transaction', async () => {
const { auth0, transactionManager } = await localSetup();
transactionManager.get.mockReturnValue(undefined);

await expect(auth0.handleRedirectCallback()).rejects.toThrow(
'Invalid state'
);
});
it('uses `state` from parsed query to remove the transaction', async () => {
const { auth0, utils, transactionManager } = await localSetup();
const queryState = 'the-state';
utils.parseQueryResult.mockReturnValue({ state: queryState });

await auth0.handleRedirectCallback();

expect(transactionManager.remove).toHaveBeenCalledWith(queryState);
});
it('calls oauth/token with correct params', async () => {
const { auth0, utils } = await localSetup();

await auth0.handleRedirectCallback();

expect(utils.oauthToken).toHaveBeenCalledWith({
audience: undefined,
baseUrl: 'https://test.auth0.com',
client_id: TEST_CLIENT_ID,
code: TEST_CODE,
code_verifier: TEST_RANDOM_STRING
});
});
it('calls `tokenVerifier.verify` with the `id_token` from in the oauth/token response', async () => {
const { auth0, tokenVerifier } = await localSetup();

await auth0.handleRedirectCallback();

expect(tokenVerifier).toHaveBeenCalledWith({
id_token: TEST_ID_TOKEN,
nonce: TEST_RANDOM_STRING,
aud: 'test-client-id',
iss: 'https://test.auth0.com/'
});
});
it('saves cache', async () => {
const { auth0, cache } = await localSetup();

await auth0.handleRedirectCallback();

expect(cache.save).toHaveBeenCalledWith({
access_token: TEST_ACCESS_TOKEN,
audience: 'default',
id_token: TEST_ID_TOKEN,
scope: TEST_SCOPES,
decodedToken: {
claims: { sub: TEST_USER_ID, aud: TEST_CLIENT_ID },
user: { sub: TEST_USER_ID }
}
});
});
it('saves `auth0.is.authenticated` key in storage', async () => {
const { auth0, storage } = await localSetup();

await auth0.handleRedirectCallback();

expect(storage.save).toHaveBeenCalledWith(
'auth0.is.authenticated',
true,
{ daysUntilExpire: 1 }
);
});
it('returns the transactions appState', async () => {
const { auth0 } = await localSetup();

const response = await auth0.handleRedirectCallback();

expect(response).toEqual({
appState: TEST_APP_STATE
});
Expand Down
9 changes: 4 additions & 5 deletions src/Auth0Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,12 @@ export default class Auth0Client {
* will be valid according to their expiration times.
*/
public async handleRedirectCallback(): Promise<RedirectLoginResult> {
if (!window.location.search) {
throw new Error(
'There are no query params available at `window.location.search`.'
);
const queryStringFragments = window.location.href.split('?').slice(1);
if (queryStringFragments.length === 0) {
throw new Error('There are no query params available for parsing.');
}
const { state, code, error, error_description } = parseQueryResult(
window.location.search.substr(1)
queryStringFragments.join('')
);

if (error) {
Expand Down