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

const nonce = (document.querySelector('script[nonce]') as HTMLScriptElement | null)?.nonce;
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.

How does nonce relate to __webpack_nonce__ in index.tsx?

I think some commenting to describe what the code in this file is doing is needed.


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

export default emotionCache;
62 changes: 59 additions & 3 deletions src/client/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,59 @@
* 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/. */

const __webpack_nonce__ = (document.querySelector('script[nonce]') as HTMLScriptElement | null)?.nonce;
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.

I think some commenting to describe what the new code is doing is needed.

(window as any).__webpack_nonce__ = __webpack_nonce__;
(window as any).__plotly_nonce__ = __webpack_nonce__;

console.log('Set __webpack_nonce__ to:', __webpack_nonce__);
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.

There are a number of console.log statements. They should go before this is finalized.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@huss Could you clarify what this comment means? What is the issue with the placement of the console.log statements right now?

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.

@huss Could you clarify what this comment means? What is the issue with the placement of the console.log statements right now?

@BrianRaymond800 console.log statements are fine for debugging but should not be present in final code. OED does not want output like this in merged code. If information needs to be given then the log system should be used. I think these are for debugging so should go when no longer needed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@huss That makes sense. Thank you.


console.log('Set nonces:', __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
) {
console.log('Appending style, has nonce:', __webpack_nonce__);
node.setAttribute('nonce', __webpack_nonce__ || '');
}

try {
return originalAppendChild.call(this, node);
} catch (err) {
console.error('Failed to append style:', err);
throw err;
}
};
// document.head.appendChild = function (node: any) {
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.

It should be determined if this commented out code should be used or removed.

// if (node instanceof HTMLStyleElement) {
// const hasNonce = node.getAttribute('nonce');
// console.log('Appending style, has nonce:', hasNonce);

// if (!hasNonce) {
// const nonceToUse = window.__plotly_nonce__ || window.__webpack_nonce__ || '';
// console.log('Setting nonce on style element:', nonceToUse);
// node.setAttribute('nonce', nonceToUse);
// }
// }
// try {
// return originalAppendChild.call(this, node);
// } catch (err) {
// console.error('Failed to append style:', err, node);
// throw err;
// }
// };
console.log('Before import, __webpack_nonce__ =', __webpack_nonce__);
import 'bootstrap/dist/css/bootstrap.css';
console.log('BootstrapCSS Loaded');
console.log('After import, __webpack_nonce__ =', __webpack_nonce__);
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
Expand All @@ -13,13 +65,17 @@ import './styles/index.css';

store.dispatch(initApp());

import { CacheProvider } from '@emotion/react';
import emotionCache from './emotionCache';
// Renders the entire application, starting with RouteComponent, into the root div
const container = document.getElementById('root') as HTMLElement;
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>
);
44 changes: 42 additions & 2 deletions src/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,60 @@

<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
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.

Some of these lines exceed 150 characters.

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;
-->
<!-- <meta http-equiv="Content-Security-Policy" content="
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.

I'm wondering how these are working given the lines are commented out. How does this relate to the code in app.js? I think some may be examples but it needs to be clarified.

default-src 'self';
img-src 'self';
font-src 'self' https://maxcdn.bootstrapcdn.com;
media-src 'self';
script-src 'self' 'nonce-__NONCE__';
style-src 'self' 'nonce-__NONCE__'; "> -->

<!-- <link rel="stylesheet" href="styles/card-page.css" nonce="__NONCE__">
<link rel="stylesheet" href="styles/DateRangeCustom.css" nonce="__NONCE__">
<link rel="stylesheet" href="styles/index.css" nonce="__NONCE__">
<link rel="stylesheet" href="styles/modal.css" nonce="__NONCE__">
<link rel="stylesheet" href="styles/react-select-css.css" nonce="__NONCE__">
<link rel="stylesheet" href="styles/spinner.css" nonce="__NONCE__">
<link rel="stylesheet" href="styles/tooltip.css" nonce="__NONCE__">
-->
</head>

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

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

<noscript>
<div style="padding:15px 15px;font-family:Helvetica;font-size:24px;background:lightcoral;border:6px solid red">
OED requires JavaScript to run correctly. Please enable JavaScript.
</div>
</noscript>
</body>

</html>
8 changes: 8 additions & 0 deletions src/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ 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.
// TODO Verify that user see the message returned, see https://express-rate-limit.mintlify.app/reference/configuration#message
Expand Down Expand Up @@ -145,6 +147,12 @@ 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);

const nonce = crypto.randomBytes(16).toString('base64url')
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 comment on what this is doing would help.

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
12 changes: 9 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,16 @@ 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'},
// CSS stylesheet loader.
{ test: /\.css$/, use: [
{loader: 'style-loader'},
{loader: 'style-loader',
options: {
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 comment on this would help.

attributes: {
nonce: '__webpack_nonce__'
}
}
},
{loader: 'css-loader'}
] },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
Expand Down