Skip to content

Commit da56f5d

Browse files
committed
CCM-9916: preload in link headers
1 parent 4da301c commit da56f5d

File tree

3 files changed

+45
-19
lines changed

3 files changed

+45
-19
lines changed

frontend/src/__tests__/middleware.test.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ function getCsp(response: Response) {
1717
return csp?.split(';').map((s) => s.trim());
1818
}
1919

20+
function getLinkHeaders(response: Response) {
21+
return response.headers.get('Link')?.split(', ');
22+
}
23+
2024
const OLD_ENV = { ...process.env };
2125
afterAll(() => {
2226
process.env = OLD_ENV;
@@ -36,6 +40,10 @@ describe('middleware function', () => {
3640
const response = await middleware(request);
3741

3842
expect(response.status).toBe(404);
43+
expect(getLinkHeaders(response)).toEqual([
44+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-55Roman.woff2>; rel=preload; as=font; crossorigin=anonymous',
45+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-65Bold.woff2>; rel=preload; as=font; crossorigin=anonymous',
46+
]);
3947
});
4048

4149
it('if request path is protected, and no access/id token is obtained, redirect to auth page', async () => {
@@ -61,7 +69,7 @@ describe('middleware function', () => {
6169
expect(response.cookies.get('csrf_token')?.value).toEqual('');
6270
});
6371

64-
it('if request path is protected, tokens exist AND token has client-id, respond with CSP', async () => {
72+
it('if request path is protected, tokens exist AND token has client-id, respond with CSP and links to preload fonts', async () => {
6573
getTokenMock.mockResolvedValueOnce({
6674
accessToken: 'access-token',
6775
clientId: 'client1',
@@ -92,6 +100,11 @@ describe('middleware function', () => {
92100
'upgrade-insecure-requests',
93101
'',
94102
]);
103+
104+
expect(getLinkHeaders(response)).toEqual([
105+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-55Roman.woff2>; rel=preload; as=font; crossorigin=anonymous',
106+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-65Bold.woff2>; rel=preload; as=font; crossorigin=anonymous',
107+
]);
95108
});
96109

97110
it('if request path is protected, tokens exist BUT token missing client-id, redirect to request-to-be-added page', async () => {
@@ -113,7 +126,7 @@ describe('middleware function', () => {
113126
);
114127
});
115128

116-
it('if request path is not protected, respond with CSP', async () => {
129+
it('if request path is not protected, respond with CSP and links to preload fonts', async () => {
117130
const url = new URL('https://url.com/create-and-submit-templates');
118131
const request = new NextRequest(url);
119132
const response = await middleware(request);
@@ -136,6 +149,11 @@ describe('middleware function', () => {
136149
'upgrade-insecure-requests',
137150
'',
138151
]);
152+
153+
expect(getLinkHeaders(response)).toEqual([
154+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-55Roman.woff2>; rel=preload; as=font; crossorigin=anonymous',
155+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-65Bold.woff2>; rel=preload; as=font; crossorigin=anonymous',
156+
]);
139157
});
140158

141159
it('public path (/auth/request-to-be-added-to-a-service) responds with CSP', async () => {
@@ -163,6 +181,11 @@ describe('middleware function', () => {
163181
'upgrade-insecure-requests',
164182
'',
165183
]);
184+
185+
expect(getLinkHeaders(response)).toEqual([
186+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-55Roman.woff2>; rel=preload; as=font; crossorigin=anonymous',
187+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-65Bold.woff2>; rel=preload; as=font; crossorigin=anonymous',
188+
]);
166189
});
167190

168191
it('when running in development mode, CSP script-src allows unsafe-eval and does not upgrade insecure requests', async () => {

frontend/src/app/layout.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,6 @@ export default function RootLayout({
5757
return (
5858
<html lang='en'>
5959
<head>
60-
<link
61-
rel='preload'
62-
as='font'
63-
href='https://assets.nhs.uk/fonts/FrutigerLTW01-55Roman.woff2'
64-
type='font/woff2'
65-
crossOrigin='anonymous'
66-
/>
67-
68-
<link
69-
rel='preload'
70-
as='font'
71-
href='https://assets.nhs.uk/fonts/FrutigerLTW01-65Bold.woff2'
72-
type='font/woff2'
73-
crossOrigin='anonymous'
74-
/>
75-
7660
<script
7761
src={`${getBasePath()}/lib/nhsuk-frontend-10.0.0.min.js`}
7862
defer

frontend/src/middleware.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ function getContentSecurityPolicy(nonce: string) {
7878
.concat(';');
7979
}
8080

81+
function preloadFonts(res: NextResponse) {
82+
res.headers.append(
83+
'Link',
84+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-55Roman.woff2>; rel=preload; as=font; crossorigin=anonymous'
85+
);
86+
res.headers.append(
87+
'Link',
88+
'<https://assets.nhs.uk/fonts/FrutigerLTW01-65Bold.woff2>; rel=preload; as=font; crossorigin=anonymous'
89+
);
90+
}
91+
8192
export async function middleware(request: NextRequest) {
8293
const { pathname } = request.nextUrl;
8394

@@ -97,11 +108,17 @@ export async function middleware(request: NextRequest) {
97108

98109
publicPathResponse.headers.set('Content-Security-Policy', csp);
99110

111+
preloadFonts(publicPathResponse);
112+
100113
return publicPathResponse;
101114
}
102115

103116
if (!protectedPaths.some((p) => p.test(pathname))) {
104-
return new NextResponse('Page not found', { status: 404 });
117+
const notFound = new NextResponse('Page not found', { status: 404 });
118+
119+
preloadFonts(notFound);
120+
121+
return notFound;
105122
}
106123

107124
const { accessToken, idToken } = await getSessionServer({
@@ -145,6 +162,8 @@ export async function middleware(request: NextRequest) {
145162

146163
response.headers.set('Content-Security-Policy', csp);
147164

165+
preloadFonts(response);
166+
148167
return response;
149168
}
150169

0 commit comments

Comments
 (0)