Skip to content

Commit

Permalink
Use window.location.toString() for returnTo (#370)
Browse files Browse the repository at this point in the history
  • Loading branch information
Widcket committed Apr 15, 2021
1 parent 85018c7 commit bc11293
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 42 deletions.
19 changes: 11 additions & 8 deletions src/frontend/with-page-auth-required.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { ComponentType, useEffect } from 'react';
import { useRouter } from 'next/router';

import { useConfig } from './use-config';
import { useUser } from './use-user';
Expand Down Expand Up @@ -73,18 +72,22 @@ export type WithPageAuthRequired = <P extends object>(
*/
const withPageAuthRequired: WithPageAuthRequired = (Component, options = {}) => {
return function withPageAuthRequired(props): JSX.Element {
const router = useRouter();
const {
returnTo = `${router.basePath ?? ''}${router.asPath}`,
onRedirecting = defaultOnRedirecting,
onError = defaultOnError
} = options;
const { returnTo, onRedirecting = defaultOnRedirecting, onError = defaultOnError } = options;
const { loginUrl } = useConfig();
const { user, error, isLoading } = useUser();

useEffect(() => {
if ((user && !error) || isLoading) return;
window.location.assign(`${loginUrl}?returnTo=${encodeURIComponent(returnTo)}`);
let returnToPath: string;

if (!returnTo) {
const currentLocation = window.location.toString();
returnToPath = currentLocation.replace(new URL(currentLocation).origin, '') || '/';
} else {
returnToPath = returnTo;
}

window.location.assign(`${loginUrl}?returnTo=${encodeURIComponent(returnToPath)}`);
}, [user, error, isLoading]);

if (error) return onError(error);
Expand Down
4 changes: 0 additions & 4 deletions tests/frontend/use-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ import { renderHook } from '@testing-library/react-hooks';
import { withConfigProvider } from '../fixtures/frontend';
import { useConfig } from '../../src/frontend/use-config';

jest.mock('next/router', () => ({
useRouter: (): any => ({ asPath: '/' })
}));

describe('context wrapper', () => {
test('should provide the default login url', async () => {
const { result } = renderHook(() => useConfig(), {
Expand Down
4 changes: 0 additions & 4 deletions tests/frontend/use-user.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ import { useConfig } from '../../src/frontend';
import { useUser, UserContext } from '../../src';
import React from 'react';

jest.mock('next/router', () => ({
useRouter: (): any => ({ asPath: '/' })
}));

describe('context wrapper', () => {
afterEach(() => delete (global as any).fetch);

Expand Down
56 changes: 30 additions & 26 deletions tests/frontend/with-page-auth-required.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,17 @@ import { fetchUserUnsuccessfulMock, fetchUserErrorMock, withUserProvider, user }
import { withPageAuthRequired } from '../../src/frontend';

const windowLocation = window.location;
const routerMock: {
basePath?: string;
asPath: string;
} = {
basePath: undefined,
asPath: '/'
};

jest.mock('next/router', () => ({ useRouter: (): any => routerMock }));

describe('with-page-auth-required csr', () => {
beforeAll(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore window.location is non-optional
delete window.location;
window.location = { ...windowLocation, assign: jest.fn() };
window.location = {
...windowLocation,
assign: jest.fn(),
toString: jest.fn(() => 'https://example.com')
};
});
afterEach(() => delete (global as any).fetch);
afterAll(() => (window.location = windowLocation));
Expand Down Expand Up @@ -86,47 +81,56 @@ describe('with-page-auth-required csr', () => {
await waitFor(() => expect(screen.getByText('Error')).toBeInTheDocument());
});

it('should accept a returnTo url', async () => {
it('should use a custom login URL', async () => {
process.env.NEXT_PUBLIC_AUTH0_LOGIN = '/api/foo';
(global as any).fetch = fetchUserUnsuccessfulMock;
const MyPage = (): JSX.Element => <>Private</>;
const ProtectedPage = withPageAuthRequired(MyPage, { returnTo: '/foo' });
const ProtectedPage = withPageAuthRequired(MyPage);

render(<ProtectedPage />, { wrapper: withUserProvider() });
await waitFor(() => expect(window.location.assign).toHaveBeenCalledWith(expect.stringContaining('/api/foo')));
delete process.env.NEXT_PUBLIC_AUTH0_LOGIN;
});

it('should return to the root path', async () => {
window.location.toString = jest.fn(() => 'https://example.net');
(global as any).fetch = fetchUserUnsuccessfulMock;
const MyPage = (): JSX.Element => <>Private</>;
const ProtectedPage = withPageAuthRequired(MyPage);

render(<ProtectedPage />, { wrapper: withUserProvider() });
await waitFor(() =>
expect(window.location.assign).toHaveBeenCalledWith(
expect.stringContaining(`?returnTo=${encodeURIComponent('/foo')}`)
expect.stringContaining(`?returnTo=${encodeURIComponent('/')}`)
)
);
});

it('should use a custom login url', async () => {
process.env.NEXT_PUBLIC_AUTH0_LOGIN = '/api/foo';
it('should return to the current path', async () => {
window.location.toString = jest.fn(() => 'https://example.net/foo');
(global as any).fetch = fetchUserUnsuccessfulMock;
const MyPage = (): JSX.Element => <>Private</>;
const ProtectedPage = withPageAuthRequired(MyPage);

render(<ProtectedPage />, { wrapper: withUserProvider() });
await waitFor(() => expect(window.location.assign).toHaveBeenCalledWith(expect.stringContaining('/api/foo')));
delete process.env.NEXT_PUBLIC_AUTH0_LOGIN;
await waitFor(() =>
expect(window.location.assign).toHaveBeenCalledWith(
expect.stringContaining(`?returnTo=${encodeURIComponent('/foo')}`)
)
);
});

it('should prepend the basePath to the returnTo URL', async () => {
const asPath = routerMock.asPath;
const basePath = routerMock.basePath;
routerMock.basePath = '/foo';
routerMock.asPath = '/bar';
it('should accept a custom returnTo URL', async () => {
(global as any).fetch = fetchUserUnsuccessfulMock;
const MyPage = (): JSX.Element => <>Private</>;
const ProtectedPage = withPageAuthRequired(MyPage);
const ProtectedPage = withPageAuthRequired(MyPage, { returnTo: '/foo' });

render(<ProtectedPage />, { wrapper: withUserProvider() });
await waitFor(() =>
expect(window.location.assign).toHaveBeenCalledWith(
expect.stringContaining(`?returnTo=${encodeURIComponent('/foo/bar')}`)
expect.stringContaining(`?returnTo=${encodeURIComponent('/foo')}`)
)
);
routerMock.basePath = basePath;
routerMock.asPath = asPath;
});

it('should preserve multiple query params in the returnTo URL', async () => {
Expand Down

0 comments on commit bc11293

Please sign in to comment.