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

[SDK-1395] Refactor loginWithPopup to optionally accept an existing popup window #368

Merged
merged 4 commits into from
Mar 12, 2020
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
56 changes: 43 additions & 13 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,39 +42,48 @@ const setup = async (options = {}) => {
client_id: TEST_CLIENT_ID,
...options
});

const getInstance = m => require(m).default.mock.instances[0];

const storage = {
get: require('../src/storage').get,
save: require('../src/storage').save,
remove: require('../src/storage').remove
};

const lock = require('browser-tabs-lock');
const cache = getInstance('../src/cache');
const tokenVerifier = require('../src/jwt').verify;
const transactionManager = getInstance('../src/transaction-manager');
const utils = require('../src/utils');

utils.createQueryParams.mockReturnValue(TEST_QUERY_PARAMS);
utils.getUniqueScopes.mockReturnValue(TEST_SCOPES);
utils.encodeState.mockReturnValue(TEST_ENCODED_STATE);
utils.createRandomString.mockReturnValue(TEST_RANDOM_STRING);
utils.sha256.mockReturnValue(Promise.resolve(TEST_ARRAY_BUFFER));
utils.bufferToBase64UrlEncoded.mockReturnValue(TEST_BASE64_ENCODED_STRING);

utils.parseQueryResult.mockReturnValue({
state: TEST_ENCODED_STATE,
code: TEST_CODE
});

utils.runPopup.mockReturnValue(
Promise.resolve({ state: TEST_ENCODED_STATE, code: TEST_CODE })
);

utils.runIframe.mockReturnValue(
Promise.resolve({ state: TEST_ENCODED_STATE, code: TEST_CODE })
);

utils.oauthToken.mockReturnValue(
Promise.resolve({
id_token: TEST_ID_TOKEN,
access_token: TEST_ACCESS_TOKEN
})
);

tokenVerifier.mockReturnValue({
user: {
sub: TEST_USER_ID
Expand All @@ -84,14 +93,21 @@ const setup = async (options = {}) => {
aud: TEST_CLIENT_ID
}
});

const popup = {
location: { href: '' },
close: jest.fn()
};

return {
auth0,
storage,
cache,
tokenVerifier,
transactionManager,
utils,
lock
lock,
popup
};
};

Expand Down Expand Up @@ -171,17 +187,31 @@ describe('Auth0', () => {

describe('loginWithPopup()', () => {
it('opens popup', async () => {
const { auth0, utils } = await setup();
const { auth0 } = await setup();

await auth0.loginWithPopup({});
expect(utils.openPopup).toHaveBeenCalled();
});

it('uses a custom popup specified in the configuration', async () => {
const { auth0, popup, utils } = await setup();

await auth0.loginWithPopup({}, { popup });

expect(utils.runPopup).toHaveBeenCalledWith(
`https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
{
popup
}
);
});

it('encodes state with random string', async () => {
const { auth0, utils } = await setup();

await auth0.loginWithPopup({});
expect(utils.encodeState).toHaveBeenCalledWith(TEST_RANDOM_STRING);
});

it('creates `code_challenge` by using `utils.sha256` with the result of `utils.createRandomString`', async () => {
const { auth0, utils } = await setup();

Expand All @@ -191,6 +221,7 @@ describe('Auth0', () => {
TEST_ARRAY_BUFFER
);
});

it('creates correct query params', async () => {
const { auth0, utils } = await setup();

Expand All @@ -210,12 +241,14 @@ describe('Auth0', () => {
connection: 'test-connection'
});
});

it('creates correct query params without leeway', async () => {
const { auth0, utils } = await setup({ leeway: 10 });

await auth0.loginWithPopup({
connection: 'test-connection'
});

expect(utils.createQueryParams).toHaveBeenCalledWith({
client_id: TEST_CLIENT_ID,
scope: TEST_SCOPES,
Expand All @@ -229,13 +262,15 @@ describe('Auth0', () => {
connection: 'test-connection'
});
});

it('creates correct query params when providing a default redirect_uri', async () => {
const redirect_uri = 'https://custom-redirect-uri/callback';
const { auth0, utils } = await setup({
redirect_uri
});

await auth0.loginWithPopup({});

expect(utils.createQueryParams).toHaveBeenCalledWith({
client_id: TEST_CLIENT_ID,
scope: TEST_SCOPES,
Expand All @@ -248,10 +283,12 @@ describe('Auth0', () => {
code_challenge_method: 'S256'
});
});

it('creates correct query params with custom params', async () => {
const { auth0, utils } = await setup();

await auth0.loginWithPopup({ audience: 'test' });

expect(utils.createQueryParams).toHaveBeenCalledWith({
audience: 'test',
client_id: TEST_CLIENT_ID,
Expand All @@ -265,36 +302,29 @@ describe('Auth0', () => {
code_challenge_method: 'S256'
});
});

it('opens popup with correct popup, url and default config', async () => {
const { auth0, utils } = await setup();
const popup = {};
utils.openPopup.mockReturnValue(popup);
await auth0.loginWithPopup();

expect(utils.runPopup).toHaveBeenCalledWith(
popup,
`https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
DEFAULT_POPUP_CONFIG_OPTIONS
);
});
it('opens popup with correct popup, url and custom config', async () => {
const { auth0, utils } = await setup();
const popup = {};
utils.openPopup.mockReturnValue(popup);
await auth0.loginWithPopup({}, { timeoutInSeconds: 1 });
expect(utils.runPopup).toHaveBeenCalledWith(
popup,
`https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
{ timeoutInSeconds: 1 }
);
});

it('opens popup with correct popup, url and timeout from client options', async () => {
const { auth0, utils } = await setup({ authorizeTimeoutInSeconds: 1 });
const popup = {};
utils.openPopup.mockReturnValue(popup);
await auth0.loginWithPopup({}, DEFAULT_POPUP_CONFIG_OPTIONS);
expect(utils.runPopup).toHaveBeenCalledWith(
popup,
`https://test.auth0.com/authorize?${TEST_QUERY_PARAMS}${TEST_TELEMETRY_QUERY_STRING}`,
{ timeoutInSeconds: 1 }
);
Expand All @@ -316,6 +346,7 @@ describe('Auth0', () => {
const { auth0, utils } = await setup();

await auth0.loginWithPopup({});

expect(utils.oauthToken).toHaveBeenCalledWith({
audience: undefined,
baseUrl: 'https://test.auth0.com',
Expand Down Expand Up @@ -479,7 +510,6 @@ describe('Auth0', () => {
const { auth0, utils } = await setup();

await auth0.loginWithPopup();
expect(utils.openPopup).toHaveBeenCalled();
});
});
describe('buildAuthorizeUrl()', () => {
Expand Down
78 changes: 54 additions & 24 deletions __tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
encodeState,
decodeState,
sha256,
openPopup,
runPopup,
runIframe,
urlDecodeB64,
Expand Down Expand Up @@ -239,21 +238,6 @@ describe('utils', () => {
expect(result).toBe('dGVzdA');
});
});
describe('openPopup', () => {
it('opens the popup', () => {
window.open = <any>jest.fn(() => true);
expect(openPopup()).toBe(true);
expect(window.open).toHaveBeenCalledWith(
'',
'auth0:authorize:popup',
'left=312,top=84,width=400,height=600,resizable,scrollbars=yes,status=1'
);
});
it('throws error when the popup is blocked', () => {
window.open = jest.fn(() => undefined);
expect(openPopup).toThrowError('Could not open popup');
});
});
describe('oauthToken', () => {
let oauthToken;
let mockUnfetch;
Expand Down Expand Up @@ -337,18 +321,23 @@ describe('utils', () => {
});
describe('runPopup', () => {
const TIMEOUT_ERROR = { error: 'timeout', error_description: 'Timeout' };

const url = 'https://authorize.com';

const setup = customMessage => {
const popup = {
location: { href: '' },
location: { href: url },
close: jest.fn()
};
const url = 'https://authorize.com';

window.addEventListener = <any>jest.fn((message, callback) => {
expect(message).toBe('message');
callback(customMessage);
});

return { popup, url };
};

describe('with invalid messages', () => {
['', {}, { data: 'test' }, { data: { type: 'other-type' } }].forEach(
m => {
Expand All @@ -364,45 +353,55 @@ describe('utils', () => {
jest.runAllTimers();
}, 10);
jest.useFakeTimers();
await expect(runPopup(popup, url, {})).rejects.toMatchObject(
await expect(runPopup(url, { popup })).rejects.toMatchObject(
TIMEOUT_ERROR
);
jest.useRealTimers();
});
}
);
});

it('returns authorization response message', async () => {
const message = {
data: {
type: 'authorization_response',
response: { id_token: 'id_token' }
}
};

const { popup, url } = setup(message);
await expect(runPopup(popup, url, {})).resolves.toMatchObject(

await expect(runPopup(url, { popup })).resolves.toMatchObject(
message.data.response
);

expect(popup.location.href).toBe(url);
expect(popup.close).toHaveBeenCalled();
});

it('returns authorization error message', async () => {
const message = {
data: {
type: 'authorization_response',
response: { error: 'error' }
}
};

const { popup, url } = setup(message);
await expect(runPopup(popup, url, {})).rejects.toMatchObject(

await expect(runPopup(url, { popup })).rejects.toMatchObject(
message.data.response
);

expect(popup.location.href).toBe(url);
expect(popup.close).toHaveBeenCalled();
});

it('times out after config.timeoutInSeconds', async () => {
const { popup, url } = setup('');
const seconds = 10;

/**
* We need to run the timers after we start `runPopup`, but we also
* need to use `jest.useFakeTimers` to trigger the timeout.
Expand All @@ -412,16 +411,21 @@ describe('utils', () => {
setTimeout(() => {
jest.runTimersToTime(seconds * 1000);
}, 10);

jest.useFakeTimers();

await expect(
runPopup(popup, url, {
timeoutInSeconds: seconds
runPopup(url, {
timeoutInSeconds: seconds,
popup
})
).rejects.toMatchObject({ ...TIMEOUT_ERROR, popup });

jest.useRealTimers();
});
it('times out after DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS if config is not defined', async () => {
const { popup, url } = setup('');

/**
* We need to run the timers after we start `runPopup`, but we also
* need to use `jest.useFakeTimers` to trigger the timeout.
Expand All @@ -431,12 +435,38 @@ describe('utils', () => {
setTimeout(() => {
jest.runTimersToTime(DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS * 1000);
}, 10);

jest.useFakeTimers();
await expect(runPopup(popup, url, {})).rejects.toMatchObject(

await expect(runPopup(url, { popup })).rejects.toMatchObject(
TIMEOUT_ERROR
);

jest.useRealTimers();
});

it('creates and uses a popup window if none was given', async () => {
const message = {
data: {
type: 'authorization_response',
response: { id_token: 'id_token' }
}
};

const { popup, url } = setup(message);
const oldOpenFn = window.open;

window.open = <any>jest.fn(() => popup);

await expect(runPopup(url, {})).resolves.toMatchObject(
message.data.response
);

expect(popup.location.href).toBe(url);
expect(popup.close).toHaveBeenCalled();

window.open = oldOpenFn;
});
});
describe('runIframe', () => {
const TIMEOUT_ERROR = { error: 'timeout', error_description: 'Timeout' };
Expand Down
Loading