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

(GAP-1984) Adds Navbar for authenticated users #71

Merged
merged 20 commits into from
Nov 9, 2023
Merged
15 changes: 3 additions & 12 deletions __tests__/pages/api/confirm-email.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,7 @@ import {
generateSignedApiKey,
} from '../../../src/service/api-key-service';
import * as nookies from 'nookies';
import {
maxAgeForCookies,
notificationRoutes,
} from '../../../src/utils/constants';

jest.mock('next/config', () => {
return jest.fn().mockImplementation(() => {
return { serverRuntimeConfig: {} };
});
});
import { notificationRoutes } from '../../../src/utils/constants';

jest.mock('../../../src/service/api-key-service');

Expand Down Expand Up @@ -52,7 +43,7 @@ describe('Confirm email and set cookie', () => {
handler(req, res);
expect(res.redirect).toHaveBeenCalledTimes(1);
expect(res.redirect).toHaveBeenCalledWith(
notificationRoutes['manageNotifications']
notificationRoutes['manageNotifications'],
);
});

Expand All @@ -75,7 +66,7 @@ describe('Confirm email and set cookie', () => {
maxAge: 2 * 60 * 60,
path: '/',
httpOnly: true,
}
},
);
});
});
74 changes: 74 additions & 0 deletions __tests__/pages/api/logout.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import '@testing-library/jest-dom';
import { merge } from 'lodash';
import Logout from '../../../pages/api/logout';
import { getSessionIdFromCookies } from '../../../src/utils/session';
import axios from 'axios';

jest.mock('../../../src/utils/session');
jest.mock('axios');

const mockedRedirect = jest.fn();
const mockedSetHeader = jest.fn();
const mockedSend = jest.fn();

const req = (overrides = {}) =>
merge(
{
headers: {
referer: `/referer`,
},
cookies: { sessionCookieName: 'testSessionId' },
},
overrides,
);

const res = (overrides = {}) =>
merge(
{
redirect: mockedRedirect,
setHeader: mockedSetHeader,
send: mockedSend,
},
overrides,
);

describe('Logout page', () => {
beforeEach(() => {
jest.clearAllMocks();
process.env.ONE_LOGIN_ENABLED = 'false';
process.env.LOGOUT = 'http://localhost:8082/logout';
});

it('Should clear back-end authentication session if there is session_id cookie available', async () => {
(getSessionIdFromCookies as jest.Mock).mockReturnValue('testSessionId');
await Logout(req(), res());

expect(axios.delete).toHaveBeenCalledTimes(1);
});

it('Should NOT try to clear back-end authentication session if session_id cookie not available', async () => {
(getSessionIdFromCookies as jest.Mock).mockReturnValue('');
await Logout(req(), res());

expect(axios.delete).toHaveBeenCalledTimes(0);
});

it('Should clear session_id cookie', async () => {
await Logout(req(), res());

expect(mockedSetHeader).toHaveBeenCalledTimes(1);
});

it('Should redirect to login page', async () => {
process.env.V2_LOGOUT_URL = 'http://localhost:8082/logout';
process.env.LOGOUT_URL = 'http://localhost:8082/logout';

await Logout(req(), res());

expect(mockedRedirect).toHaveBeenNthCalledWith(
1,
302,
'http://localhost:8082/logout',
);
});
});
6 changes: 0 additions & 6 deletions __tests__/pages/api/notification-signup.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ import { notificationRoutes } from '../../../src/utils/constants';
import { decrypt } from '../../../src/utils/encryption';
import nookies from 'nookies';

jest.mock('next/config', () => {
return jest.fn().mockImplementation(() => {
return { serverRuntimeConfig: {} };
});
});

jest.mock('../../../src/service/api-key-service');
jest.mock('../../../src/utils/encryption');
jest.mock('nookies');
Expand Down
6 changes: 0 additions & 6 deletions __tests__/pages/api/unsubscribe.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ jest.mock('../../../src/utils/jwt', () => ({
})),
}));

jest.mock('next/config', () => {
return jest.fn().mockImplementation(() => {
return { serverRuntimeConfig: {} };
});
});

const req = {
body: {
email: '[email protected]',
Expand Down
1 change: 0 additions & 1 deletion __tests__/pages/info/privacy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ jest.mock('next/router', () => ({
return jest.fn();
},
}));

let props, component;
beforeAll(async () => {
props = {
Expand Down
1 change: 0 additions & 1 deletion __tests__/pages/info/terms-and-conditions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ jest.mock('next/router', () => ({
return jest.fn();
},
}));

let props, component;
beforeAll(async () => {
props = {
Expand Down
9 changes: 3 additions & 6 deletions __tests__/pages/newsletter/confirmation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ import { parseBody } from 'next/dist/server/api-utils/node';

jest.mock('next/dist/server/api-utils/node');

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

jest.mock('next/router', () => ({
useRouter: jest.fn(),
}));
jest.mock('../../../src/service/gov_notify_service', () => ({
sendEmail: jest.fn(),
}));
Expand Down
22 changes: 8 additions & 14 deletions __tests__/pages/notifications/check-email.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,14 @@ import CheckEmail, {
import { notificationRoutes } from '../../../src/utils/constants';
import cookieExistsAndContainsValidJwt from '../../../src/utils/cookieAndJwtChecker';

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

jest.mock('nookies', () => {
return {
get: jest.fn(),
set: jest.fn(),
destroy: jest.fn(),
};
});

jest.mock('next/router', () => ({
useRouter: jest.fn(),
}));
jest.mock('nookies', () => ({
get: jest.fn(),
set: jest.fn(),
destroy: jest.fn(),
}));
jest.mock('../../../src/utils/cookieAndJwtChecker');

const mockQuery = {
Expand Down
6 changes: 0 additions & 6 deletions __tests__/pages/notifications/deleteSearch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ jest.mock('../../../src/service/saved_search_service');
const encryptedEmail = 'test-encrypted-email-string';
const decryptedEmail = 'test-decrypted-email-string';

jest.mock('next/config', () => {
return jest.fn().mockImplementation(() => {
return { serverRuntimeConfig: {} };
});
});

jest.mock('next/router', () => {
return {
useRouter: jest.fn(),
Expand Down
6 changes: 0 additions & 6 deletions __tests__/pages/notifications/email-confirmation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ jest.mock('../../../src/utils/encryption');

const encryptedEmail = 'test-encrypted-email-string';

jest.mock('next/config', () => {
return jest.fn().mockImplementation(() => {
return { serverRuntimeConfig: {} };
});
});

jest.mock('next/router', () => {
return {
useRouter: jest.fn(),
Expand Down
6 changes: 0 additions & 6 deletions __tests__/pages/notifications/unsubscribe.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ jest.mock('../../../src/service/api-key-service');
const encryptedEmail = 'test-encrypted-email-string';
const decryptedEmail = 'test-decrypted-email-string';

jest.mock('next/config', () => {
return jest.fn().mockImplementation(() => {
return { serverRuntimeConfig: {} };
});
});

jest.mock('next/router', () => {
return {
useRouter: jest.fn(),
Expand Down
1 change: 0 additions & 1 deletion __tests__/pages/save-search/email.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ describe('getServerSideProps', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('should return expected props for a GET request', async () => {
const context = {
req: {
Expand Down
1 change: 0 additions & 1 deletion __tests__/pages/save-search/notifications.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import SignupSavedSearch, {
import { RouterContext } from 'next/dist/shared/lib/router-context.js';
import { parseBody } from 'next/dist/server/api-utils/node';
jest.mock('next/dist/server/api-utils/node');

describe('Rendering serverside props', () => {
const queryWithNoErrors = {
req: { method: 'GET' },
Expand Down
1 change: 1 addition & 0 deletions __tests__/pages/unsubscribe/[id].test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jest.mock('../../../pages/service-error/index.page', () => ({
jest.mock('../../../src/utils/encryption', () => ({
decrypt: jest.fn(),
}));

jest.mock(
'../../../src/service/newsletter/newsletter-subscription-service',
() => ({
Expand Down
5 changes: 0 additions & 5 deletions __tests__/service/subscription_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ jest.mock('../../src/utils/axios', () => {
};
});

jest.mock('next/config', () => {
return jest.fn().mockImplementation(() => {
return { serverRuntimeConfig: {} };
});
});
const subscriptionService = SubscriptionService.getInstance();
const instance = axios.create();

Expand Down
1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ module.exports = {
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
},

output: 'standalone',
};
53 changes: 48 additions & 5 deletions pages/_app.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import Script from 'next/script';
import nookies from 'nookies';
import React, { useEffect } from 'react';
import React, { createContext, useContext, useEffect } from 'react';
import TagManager from 'react-gtm-module';
import '../src/lib/ie11_nodelist_polyfill';
import '../styles/globals.scss';
import { checkUserLoggedIn } from '../src/service';
import { getJwtFromCookies } from '../src/utils/jwt';
import App from 'next/app';

const MyApp = ({ Component, pageProps }) => {
let cookies = nookies.get({});
export const AuthContext = createContext({
isUserLoggedIn: false,
});
export const AppContext = createContext({
applicantUrl: null,
oneLoginEnabled: null,
});

export const useAuth = () => useContext(AuthContext);
export const useAppContext = () => useContext(AppContext);

const MyApp = ({
Component,
pageProps,
props: { isUserLoggedIn, applicantUrl, oneLoginEnabled },
}) => {
const cookies = nookies.get({});

useEffect(() => {
if (cookies.design_system_cookies_policy === 'true') {
Expand All @@ -28,10 +46,35 @@ const MyApp = ({ Component, pageProps }) => {
return (
<>
<Script src="/javascript/govuk.js" strategy="beforeInteractive" />

<Component {...pageProps} />
<AppContext.Provider value={{ applicantUrl, oneLoginEnabled }}>
<AuthContext.Provider value={{ isUserLoggedIn }}>
<Component {...pageProps} />
</AuthContext.Provider>
</AppContext.Provider>
</>
);
};

MyApp.getInitialProps = async (context) => {
const ctx = await App.getInitialProps(context);
let oneLoginEnabled = null;
let applicantUrl = null;

if (process?.env) {
oneLoginEnabled = process.env.ONE_LOGIN_ENABLED;
applicantUrl = process.env.APPLY_FOR_A_GRANT_APPLICANT_URL;
}
try {
const { jwt } = getJwtFromCookies(context.ctx.req);
const isUserLoggedIn = await checkUserLoggedIn(jwt);

return { ...ctx, props: { isUserLoggedIn, applicantUrl, oneLoginEnabled } };
} catch (err) {
return {
...ctx,
props: { isUserLoggedIn: false, applicantUrl, oneLoginEnabled },
};
}
};

export default MyApp;
29 changes: 29 additions & 0 deletions pages/api/logout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { getSessionIdFromCookies } from '../../../src/utils/session';
import axios from 'axios';

const Logout = async (req: NextApiRequest, res: NextApiResponse) => {
const sessionCookie = getSessionIdFromCookies(req);
if (sessionCookie) await logoutAdmin(sessionCookie);

res.setHeader(
'Set-Cookie',
`session_id=deleted; Path=/; secure; HttpOnly; SameSite=Strict; expires=Thu, 01 Jan 2003 00:00:00 GMT`,
);
res.redirect(302, process.env.V2_LOGOUT_URL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to add this to .env.example as well

};

const axiosSessionConfig = (sessionId: string) => ({
withCredentials: true,
headers: {
Cookie: `SESSION=${sessionId};`,
},
});

const logoutAdmin = async (sessionCookie: string) =>
axios.delete(
`${process.env.ADMIN_BACKEND_HOST}/logout`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also needs to go in .env.example

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all fair

axiosSessionConfig(sessionCookie),
);

export default Logout;
1 change: 0 additions & 1 deletion pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ const Home = ({ searchTerm, applicantUrl, oneLoginEnabled }) => {
/>
<title>Home - Find a grant</title>
</Head>

<Layout description="Find a grant">
<div className="govuk-width-container ">
<div className="govuk-grid-row">
Expand Down
Loading