Skip to content
Open
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
12 changes: 12 additions & 0 deletions src/client/app/emotionCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import createCache from '@emotion/cache';

//creates the nonce for the script being run
//the nonce works alongside the webpack_nonce and plotly_nonce to protect against unwanted scripts
const nonce = (document.querySelector('script[nonce]') as HTMLScriptElement | null)?.nonce;

const emotionCache = createCache({
key: 'css',
nonce: nonce,
});

export default emotionCache;
42 changes: 38 additions & 4 deletions src/client/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import 'bootstrap/dist/css/bootstrap.css';
import * as React from 'react';
import { createRoot } from 'react-dom/client';
Expand All @@ -10,6 +10,38 @@ import { store } from './store';
import RouteComponent from './components/RouteComponent';
import { initApp } from './redux/slices/appStateSlice';
import './styles/index.css';
import { CacheProvider } from '@emotion/react';
import emotionCache from './emotionCache';

//these lines take the nonce, and create the webpack and plotly nonces from it
//these are additional nonces that contribute to styling with webpack and plotly
const __webpack_nonce__ = (document.querySelector('script[nonce]') as HTMLScriptElement | null)?.nonce;
(window as any).__webpack_nonce__ = __webpack_nonce__;
(window as any).__plotly_nonce__ = __webpack_nonce__;

declare global {
interface Window {
__webpack_nonce__?: string;
__plotly_nonce__?: string;
}
}


const originalAppendChild = document.head.appendChild;
document.head.appendChild = function (node: any) {
if (
node instanceof HTMLStyleElement
) {
node.setAttribute('nonce',__webpack_nonce__|| '');
}

try {
return originalAppendChild.call(this, node);
} catch (err) {
console.error('Failed to append style:', err);
throw err;
}
};

store.dispatch(initApp());

Expand All @@ -19,7 +51,9 @@ const root = createRoot(container);

root.render(
// Provides the Redux store to all child components
< Provider store={store} stabilityCheck='always' >
< RouteComponent />
</Provider >
<CacheProvider value={emotionCache}>
<Provider store={store} stabilityCheck="always">
<RouteComponent />
</Provider>
</CacheProvider>
);
31 changes: 29 additions & 2 deletions src/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,43 @@

<head>
<meta charset="utf-8">
<script nonce="{{nonce}}">
window.__webpack_nonce__ = "{{nonce}}";
window.__plotly_nonce__ = "{{nonce}}";
// console.log("This script runs because it has the correct nonce.");
</script>
<base id="base" href="SUBDIR" target="_blank">
<link rel="icon" type="image/png" href="favicon.ico">
<title>Open Energy Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">


<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" nonce="{{nonce}}">


<!--
The meta tag with "Content-Security-Policy" below is the Content Security Policy, by having default-src as self all CSP rules that are
unspecified will only allow the OED site and resources to be used/displayed. Tags like img-src, media-src, and script-src are also set to self
to ensure that only resources like images, audio/videos, and scripts like JavaScript and TypeScript can only be from OED and will block any
types of injections. The tag font-src is the exception to this as OED also uses a font from a bootstrapcdn.com sub-domain and has this site
listed next to 'self'. To test CSP rules change http-equiv=”Content-Security-Policy” to http-equiv=”Content-Security-Policy-Report-Only” this
allows us to send reports of what would have been blocked without actually blocking it.

For sites using OED and are blocked by these CSP rules may add their site to the exception they may list their website link next to the tag that
is blocking the user site. The site link must be added after 'self' but before the semi colon marking the end of that tag. The font-src tag is a
great example on how to implement a site to the exception list. Another example for adding a site (https://newException.com) to a tag with
multiple sites as an exceptions would be : img-src 'self' http://example.com https://site_example.net; becomes img-src 'self'
http://example.com https://site_example.net https://newException.com;
-->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

A number of commented out lines were removed here from the original PR. I wanted to check if they had any value or were examples.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

From what I can tell, those lines were an alternate implementation of the CSP that was rejected and replaced with the current one, but never fully removed. That is why I chose to remove it.

</head>

<body>
<div id="root"></div>
<script src="app/bundle.js"></script>

<script src="app/bundle.js" nonce="{{nonce}}">

</script>
<noscript>
<h1>OED requires JavaScript to run correctly. Please enable JavaScript.</h1>
</noscript>
Expand Down
8 changes: 8 additions & 0 deletions src/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const conversionArray = require('./routes/conversionArray');
const units = require('./routes/units');
const conversions = require('./routes/conversions');
const ciks = require('./routes/ciks');
const crypto = require('node:crypto');

// Limit the rate of overall requests to OED
// Note that the rate limit may make the automatic test return the value of 429. In that case, the limiters below need to be increased.
Expand Down Expand Up @@ -145,6 +146,13 @@ router.get('*', (req, res) => {
fs.readFile(path.resolve(__dirname, '..', 'client', 'index.html'), (err, html) => {
const subdir = config.subdir || '/';
let htmlPlusData = html.toString().replace('SUBDIR', subdir);

//assigns a value to the nonce in order to check for authenticity
const nonce = crypto.randomBytes(16).toString('base64url');
htmlPlusData = htmlPlusData.replace(/{{nonce}}/g, nonce);

res.setHeader('Content-Security-Policy', `default-src 'self'; img-src 'self' data: ; font-src 'self' https://maxcdn.bootstrapcdn.com ; media-src 'self'; script-src 'self' 'nonce-${nonce}' ; style-src 'self' 'nonce-${nonce}' 'unsafe-inline';`)

res.send(htmlPlusData);
});
});
Expand Down
13 changes: 10 additions & 3 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
const webpack = require('webpack');
const path = require('path');

Expand Down Expand Up @@ -38,10 +38,17 @@ const config = {
module: {
rules: [
// All TypeScript ('.ts' or '.tsx') will be handled by 'awesome-typescript-loader'.
{ test: /\.[jt]sx?$/, exclude: /node_modules/, use: 'ts-loader' },
{ test: /\.[jt]sx?$/, exclude: /node_modules/, use: 'ts-loader'},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The space before the final } was removed but should be there. There are some other formatting issues so doing format document to fix (even though there before this wor) would be nice.

// CSS stylesheet loader.
{ test: /\.css$/, use: [
{loader: 'style-loader'},
{loader: 'style-loader',
options: {
attributes: {
//this line allows the webpack nonce to be applied to styles
nonce: '__webpack_nonce__'
}
}
},
{loader: 'css-loader'}
] },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
Expand Down