Skip to content

Commit

Permalink
Merge pull request #1168 from CruGlobal/8386-login-redirect
Browse files Browse the repository at this point in the history
[MPDX-8386] Redirect after login
  • Loading branch information
canac authored Nov 4, 2024
2 parents 1c203d1 + 6626050 commit 975d3ae
Show file tree
Hide file tree
Showing 15 changed files with 97 additions and 103 deletions.
8 changes: 5 additions & 3 deletions pages/accountLists.page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ interface GetServerSidePropsReturn {
redirect: unknown;
}

const accountListId = 'accountID1';
const accountListId = 'account-list-1';

describe('Account Lists page', () => {
const context = {} as GetServerSidePropsContext;
const context = {
resolvedUrl: '/accountLists/account-list-1',
} as GetServerSidePropsContext;

describe('NextAuth unauthorized', () => {
it('should redirect to login', async () => {
Expand All @@ -35,7 +37,7 @@ describe('Account Lists page', () => {

expect(props).toBeUndefined();
expect(redirect).toEqual({
destination: '/login',
destination: '/login?redirect=%2FaccountLists%2Faccount-list-1',
permanent: false,
});
});
Expand Down
10 changes: 3 additions & 7 deletions pages/accountLists/[accountListId].page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { render } from '@testing-library/react';
import { GraphQLError } from 'graphql';
import { getSession } from 'next-auth/react';
import { I18nextProvider } from 'react-i18next';
import { session } from '__tests__/fixtures/session';
import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
import makeSsrClient from 'src/lib/apollo/ssrClient';
Expand All @@ -29,29 +28,26 @@ describe('AccountListsId page', () => {
query: {
accountListId: 'account-list-1',
},
resolvedUrl: '/accountLists/account-list-1',
} as unknown as GetServerSidePropsContext;

describe('NextAuth unauthorized', () => {
it('should redirect to login', async () => {
(getSession as jest.Mock).mockResolvedValue(null);
(getSession as jest.Mock).mockResolvedValueOnce(null);

const { props, redirect } = (await getServerSideProps(
context,
)) as GetServerSidePropsReturn;

expect(props).toBeUndefined();
expect(redirect).toEqual({
destination: '/login',
destination: '/login?redirect=%2FaccountLists%2Faccount-list-1',
permanent: false,
});
});
});

describe('NextAuth authorized', () => {
beforeEach(() => {
(getSession as jest.Mock).mockResolvedValue(session);
});

it('redirects to the home page on GraphQL query error', async () => {
(makeSsrClient as jest.Mock).mockReturnValue({
query: jest
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { GetServerSidePropsContext } from 'next';
import { session } from '__tests__/fixtures/session';
import { getServerSideProps } from './[inviteId].page';

describe('Account Invite Link Redirect', () => {
Expand All @@ -24,9 +23,6 @@ describe('Account Invite Link Redirect', () => {
'/acceptInvite?accountListId=test-account-list-id&accountInviteId=test-invite-id&inviteCode=test-code',
permanent: true,
},
props: {
session: session,
},
});
});

Expand All @@ -45,9 +41,6 @@ describe('Account Invite Link Redirect', () => {
destination: '/accountLists/_/',
permanent: true,
},
props: {
session: session,
},
});
});
});
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
import { GetServerSideProps } from 'next';
import { ReactNode } from 'react';
import { getSession } from 'next-auth/react';
import { loginRedirect } from 'pages/api/utils/pagePropsHelpers';

// This page redirect old email invite links to the new page that handles invites
const InvitePage = (): ReactNode => null;

export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context);
const { accountListId, inviteId, code } = context.query;
if (!session) {
return loginRedirect(context);
}

const { accountListId, inviteId, code } = context.query;
const redirectURL =
accountListId && inviteId && code
? `/acceptInvite?accountListId=${accountListId}&accountInviteId=${inviteId}&inviteCode=${code}`
: // Intentionally redirect to invalid accountListId since we don't have the current user's accountListId
'/accountLists/_/';

return {
redirect: session
? {
destination: redirectURL,
permanent: true,
}
: {
destination: '/login',
permanent: false,
},
props: {
session,
redirect: {
destination: redirectURL,
permanent: true,
},
};
};
Expand Down
17 changes: 15 additions & 2 deletions pages/api/utils/pagePropsHelpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,28 @@ import { session } from '__tests__/fixtures/session';
import {
enforceAdmin,
loadSession,
loginRedirect,
makeGetServerSideProps,
} from './pagePropsHelpers';

jest.mock('next-auth/react');

const context = {
query: { accountListId: 'account-list-1' },
resolvedUrl: '/page?param=value',
} as unknown as GetServerSidePropsContext;

describe('loginRedirect', () => {
it('returns redirect with current URL', () => {
expect(loginRedirect(context)).toEqual({
redirect: {
destination: '/login?redirect=%2Fpage%3Fparam%3Dvalue',
permanent: false,
},
});
});
});

describe('enforceAdmin', () => {
it('does not return a redirect if the user is an admin', async () => {
(getSession as jest.Mock).mockResolvedValue({ user: { admin: true } });
Expand Down Expand Up @@ -50,7 +63,7 @@ describe('loadSession', () => {

await expect(loadSession(context)).resolves.toMatchObject({
redirect: {
destination: '/login',
destination: '/login?redirect=%2Fpage%3Fparam%3Dvalue',
},
});
});
Expand All @@ -67,7 +80,7 @@ describe('makeGetServerSideProps', () => {

await expect(getServerSideProps(context)).resolves.toEqual({
redirect: {
destination: '/login',
destination: '/login?redirect=%2Fpage%3Fparam%3Dvalue',
permanent: false,
},
});
Expand Down
25 changes: 13 additions & 12 deletions pages/api/utils/pagePropsHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ interface PagePropsWithSession {
session: Session;
}

// Return a redirect to the login page
export const loginRedirect = (
context: GetServerSidePropsContext,
): GetServerSidePropsResult<never> => ({
redirect: {
destination: `/login?redirect=${encodeURIComponent(context.resolvedUrl)}`,
permanent: false,
},
});

// Redirect back to the dashboard if the user isn't an admin
export const enforceAdmin: GetServerSideProps<PagePropsWithSession> = async (
context,
Expand All @@ -36,13 +46,9 @@ export const loadSession: GetServerSideProps<PagePropsWithSession> = async (
) => {
const session = await getSession(context);
if (!session?.user.apiToken) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
return loginRedirect(context);
}

return {
props: {
session,
Expand Down Expand Up @@ -102,12 +108,7 @@ export const makeGetServerSideProps = <PageProps = Record<string, unknown>>(
// Start by loading the session and redirecting to the login page if it is missing
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
return loginRedirect(context);
}

// Pass the session to the page's custom logic to generate the page props
Expand Down
9 changes: 5 additions & 4 deletions pages/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@ import { GetServerSideProps } from 'next';
import { ReactNode } from 'react';
import { getSession } from 'next-auth/react';
import BaseLayout from 'src/components/Layouts/Basic';
import { loginRedirect } from './api/utils/pagePropsHelpers';

const IndexPage = (): ReactNode => null;

IndexPage.layout = BaseLayout;

export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context);
if (!session) {
return loginRedirect(context);
}

return {
redirect: {
destination: session ? '/accountLists' : '/login',
destination: '/accountLists',
permanent: false,
},
props: {
session,
},
};
};

Expand Down
6 changes: 4 additions & 2 deletions pages/login.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Welcome from 'src/components/Welcome';
import useGetAppSettings from 'src/hooks/useGetAppSettings';
import { extractCookie } from 'src/lib/extractCookie';
import i18n from 'src/lib/i18n';
import { getQueryParam } from 'src/utils/queryParam';

const SignUpBox = styled('div')(({ theme }) => ({
marginBlock: theme.spacing(2),
Expand Down Expand Up @@ -124,10 +125,11 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
`mpdx-handoff.redirect-url=; HttpOnly; path=/; Max-Age=0`,
);
}
if (context.res && session && !impersonateCookie) {
if (session && !impersonateCookie) {
const queryRedirectUrl = getQueryParam(context.query, 'redirect');
return {
redirect: {
destination: redirectCookie ?? '/accountLists',
destination: redirectCookie ?? queryRedirectUrl ?? '/accountLists',
permanent: false,
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { GetServerSidePropsContext } from 'next';
import { session } from '__tests__/fixtures/session';
import { getServerSideProps } from './[inviteId].page';

describe('Org Invite Link Redirect', () => {
Expand All @@ -24,9 +23,6 @@ describe('Org Invite Link Redirect', () => {
'/acceptInvite?orgId=test-org-id&orgInviteId=test-invite-id&inviteCode=test-code',
permanent: true,
},
props: {
session: session,
},
});
});

Expand All @@ -45,9 +41,6 @@ describe('Org Invite Link Redirect', () => {
destination: '/accountLists/_/',
permanent: true,
},
props: {
session: session,
},
});
});
});
21 changes: 8 additions & 13 deletions pages/organizations/[orgId]/accept_invite/[inviteId].page.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
import { GetServerSideProps } from 'next';
import { ReactNode } from 'react';
import { getSession } from 'next-auth/react';
import { loginRedirect } from 'pages/api/utils/pagePropsHelpers';

// This page redirect old email invite links to the new page that handles invites
const OrgInvitePage = (): ReactNode => null;

export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context);
const { orgId, inviteId, code } = context.query;
if (!session) {
return loginRedirect(context);
}

const { orgId, inviteId, code } = context.query;
const redirectURL =
orgId && inviteId && code
? `/acceptInvite?orgId=${orgId}&orgInviteId=${inviteId}&inviteCode=${code}`
: // Intentionally redirect to invalid accountListId since we don't have the current user's accountListId
'/accountLists/_/';

return {
redirect: session
? {
destination: redirectURL,
permanent: true,
}
: {
destination: '/login',
permanent: false,
},
props: {
session,
redirect: {
destination: redirectURL,
permanent: true,
},
};
};
Expand Down
6 changes: 4 additions & 2 deletions pages/setup/account.page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const router = {
push,
};

const context = {} as unknown as GetServerSidePropsContext;
const context = {
resolvedUrl: '/accountLists/account-list-1',
} as unknown as GetServerSidePropsContext;

const mutationSpy = jest.fn();

Expand Down Expand Up @@ -97,7 +99,7 @@ describe('getServerSideProps', () => {

await expect(getServerSideProps(context)).resolves.toEqual({
redirect: {
destination: '/login',
destination: '/login?redirect=%2FaccountLists%2Faccount-list-1',
permanent: false,
},
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/RouterGuard/RouterGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const RouterGuard: React.FC<Props> = ({ children = null }) => {
const session = useSession({
required: true,
onUnauthenticated: () => {
push('/login');
push({ pathname: '/login', query: { redirect: window.location.href } });
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useRouter } from 'next/router';
import { ReactElement } from 'react';
import {
Box,
Expand Down Expand Up @@ -43,6 +44,7 @@ export const ImpersonateUserAccordion: React.FC<AccordionProps> = ({
const accordionName = t('Impersonate User');
const { enqueueSnackbar } = useSnackbar();
const { appName } = useGetAppSettings();
const { push } = useRouter();

const onSubmit = async (attributes: ImpersonateUserFormType) => {
try {
Expand All @@ -66,7 +68,7 @@ export const ImpersonateUserAccordion: React.FC<AccordionProps> = ({
variant: 'success',
},
);
window.location.href = `/login`;
push('/login');
} else {
setupImpersonateJson.errors.forEach((error) => {
enqueueSnackbar(error.detail, {
Expand Down
Loading

0 comments on commit 975d3ae

Please sign in to comment.