Skip to content
Draft
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
42 changes: 0 additions & 42 deletions ws-nextjs-app/middleware.page.ts

This file was deleted.

14 changes: 12 additions & 2 deletions ws-nextjs-app/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ const assetPrefix =
process.env.SIMORGH_PUBLIC_STATIC_ASSETS_ORIGIN +
process.env.SIMORGH_PUBLIC_STATIC_ASSETS_PATH;

const isLocal =
process.env.SIMORGH_PUBLIC_STATIC_ASSETS_ORIGIN?.includes('localhost');
const isLocal = process.env.SIMORGH_APP_ENV === 'local';

/** @type {import('next').NextConfig} */
module.exports = {
Expand All @@ -29,6 +28,17 @@ module.exports = {
},
async rewrites() {
return [
// Service worker is registered at the root (e.g. /pidgin) so will work as is on Test/Live
// but will not work on localhost. This rewrites requests from paths outside of root
// to the sw.js file found in the 'public' folder, which is served from the root.
...(isLocal
? [
{
source: '/:path/sw.js',
destination: '/sw.js',
},
]
: []),
{
source: '/:service/og/:id',
destination: '/api/:service/og/:id',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('handleArticleRoute', () => {
it('returns correct cache-control header if article is older than six hours', async () => {
jest.spyOn(Date, 'now').mockImplementation(() => 2673964957894);

const result = await handleArticleRoute(mockGetServerSidePropsContext);
await handleArticleRoute(mockGetServerSidePropsContext);

expect(mockSetHeader).toHaveBeenCalledWith(
'Cache-Control',
Expand All @@ -54,7 +54,7 @@ describe('handleArticleRoute', () => {
it('returns correct cache-control header if article is not older than six hours', async () => {
jest.spyOn(Date, 'now').mockImplementation(() => 1673964987894);

const result = await handleArticleRoute(mockGetServerSidePropsContext);
await handleArticleRoute(mockGetServerSidePropsContext);

expect(mockSetHeader).toHaveBeenCalledWith(
'Cache-Control',
Expand Down
25 changes: 25 additions & 0 deletions ws-nextjs-app/pages/_document.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import ReverbTemplate from '#src/server/Document/Renderers/ReverbTemplate';
import { PageTypes } from '#app/models/types/global';
import ComponentTracking from '#src/server/Document/Renderers/ComponentTracking';
import addOperaMiniClassScript from '#app/lib/utilities/addOperaMiniClassScript';
import addPlatformToRequestChainHeader from '#src/server/utilities/addPlatformToRequestChainHeader';
import { cspHeaderResponseForNextDocumentContext } from '#nextjs/utilities/cspHeaderResponse';
import removeSensitiveHeaders from '../utilities/removeSensitiveHeaders';
import derivePageType from '../utilities/derivePageType';

Expand Down Expand Up @@ -83,6 +85,27 @@ const handleServerLogging = ({
}
};

const LOCALHOST_DOMAINS = ['localhost', '127.0.0.1'];

const addServiceChainAndCspHeaders = async (ctx: DocumentContext) => {
ctx.res?.setHeader(
'req-svc-chain',
addPlatformToRequestChainHeader({
headers: ctx.req?.headers as unknown as Headers,
}),
);

const hostname = ctx.req?.headers.host || '';

const isLocalhost = LOCALHOST_DOMAINS.includes(hostname.split(':')?.[0]);

const PRODUCTION_ONLY = !isLocalhost && process.env.NODE_ENV === 'production';

if (PRODUCTION_ONLY) {
await cspHeaderResponseForNextDocumentContext({ ctx });
}
};

type DocProps = {
clientSideEnvVariables: EnvConfig;
css: string;
Expand All @@ -103,6 +126,8 @@ export default class AppDocument extends Document<DocProps> {

const { isApp, isAmp, isLite } = getPathExtension(url);

await addServiceChainAndCspHeaders(ctx);

const cache = createCache({ key: 'css' });
const { extractCritical } = createEmotionServer(cache);

Expand Down
71 changes: 71 additions & 0 deletions ws-nextjs-app/utilities/cspHeaderResponse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import isLiveEnv from '#lib/utilities/isLive';
import getToggles from '#app/lib/utilities/getToggles/withCache';
import { Services } from '#app/models/types/global';
import SERVICES from '#app/lib/config/services';
import { DocumentContext } from 'next/document';

const setReportTo = (header: Headers) => {
header.set(
Expand Down Expand Up @@ -59,6 +60,76 @@ const isValidService = (str: string) => {
return service && SERVICES.includes(service);
};

export const cspHeaderResponseForNextDocumentContext = async ({
ctx,
}: {
ctx: DocumentContext;
}) => {
const reqUrl = ctx.req?.url || '';
const { isAmp } = getPathExtension(reqUrl);
const isLive = isLiveEnv();

let hasAdsScripts = false;
let countryList = '';

if (isValidService(reqUrl)) {
const service = fallbackServiceParam(reqUrl);
const toggles = await getToggles(service);

({ enabled: hasAdsScripts, value: countryList = '' } =
toggles?.adsNonce || { enabled: false, value: '' });
}

const country =
ctx?.req?.headers?.['x-country'] ||
ctx?.req?.headers?.['x-bbc-edge-country'];

const shouldServeRelaxedCsp =
hasAdsScripts &&
isRelaxedCspEnabled(countryList, (country as string) || '');

const { directives } = cspDirectives({
isAmp,
isLive,
shouldServeRelaxedCsp,
});

const BUMP4SpecificConditions = {
'media-src': ['https:', 'blob:'],
'connect-src': ['https:'],
};

const contentSecurityPolicyHeaderValue = directiveToString({
...directives,
...BUMP4SpecificConditions,
});

ctx.res?.setHeader(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue,
);

ctx.res?.setHeader(
'report-to',
JSON.stringify({
group: 'worldsvc',
max_age: 2592000,
endpoints: [
{
url: process.env.SIMORGH_CSP_REPORTING_ENDPOINT,
priority: 1,
},
],
include_subdomains: true,
}),
);

ctx.res?.setHeader(
'Content-Security-Policy',
contentSecurityPolicyHeaderValue,
);
};

const cspHeaderResponse = async ({ request }: { request: NextRequest }) => {
const { isAmp } = getPathExtension(request.url);
const isLive = isLiveEnv();
Expand Down
Loading