Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9cd1cfd
Added Documentation and pushing the initial CSP tag as well.
CamClendenon Apr 30, 2025
2cb4ac9
Merge branch 'development' of https://github.com/CamClendenon/OED int…
CamClendenon Apr 30, 2025
8cdc2da
Updates to documentation based on Steve's reccomendation
CamClendenon May 1, 2025
956e878
Grammar correction
CamClendenon May 1, 2025
2e17a9c
Merge branch 'OpenEnergyDashboard:development' into development
CamClendenon May 2, 2025
fc1eb21
Current Attempt to Mend CSP rule (STILL IN PROGRESS)
CamClendenon May 3, 2025
5824620
Semi Working Page with CSP rule, Still csp blocking some things
CamClendenon May 3, 2025
d45135d
Added new file to add Emotion/Cache which helps let the nonce be save…
CamClendenon May 5, 2025
ceab8a8
Merge branch 'OpenEnergyDashboard:development' into development
CamClendenon May 31, 2025
dfd102c
Merge remote-tracking branch 'origin/development' into pr/CamClendeno…
huss Dec 1, 2025
4b4233a
formatting
huss Dec 1, 2025
f4c3206
Add MPL & formatting
huss Dec 1, 2025
33aa0a5
spelling fix
huss Dec 1, 2025
88d9f40
Updated changes in PR 1484 with improved
BrianRaymond800 Jan 15, 2026
6ac1a5b
Updated the CSP fix with improved comments
BrianRaymond800 Jan 19, 2026
28c2cd9
Improved the formatting to align with OED's
BrianRaymond800 Feb 12, 2026
79c378c
Merge remote-tracking branch 'origin/pr-update' into pr-changes
BrianRaymond800 Apr 20, 2026
519ffbf
Merge branch 'development' into pr-update
BrianRaymond800 Apr 27, 2026
282260c
formatting per comments
huss Jun 12, 2026
984d91b
Merge branch 'development' into pr/huss/1656
huss Jun 22, 2026
779dc7e
minor formatting
huss Jun 22, 2026
378a56b
fix lost code in development merge
huss Jun 22, 2026
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
16 changes: 16 additions & 0 deletions src/client/app/emotionCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* 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';

//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>
);
54 changes: 40 additions & 14 deletions src/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,47 @@
<html>

<head>
<meta charset="utf-8">
<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" integrity="sha512-SfTiTlX6kk+qitfevl/7LibUOeJWlt9rbyDn92a1DqWOw9vWG2MFoays0sgObmWazO5BQPiFucnnEAjpAB+/Sw==" crossorigin="anonymous">
<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"
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 Author

Choose a reason for hiding this comment

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

This was comments from PR #1567 that may still need to be considers:

@huss said on 2 Feb. 26:

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.

and @BrianRaymond800 said on 10 Feb. 26:

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.

I don't know if these lines are important or not, esp. given the console warnings.

</head>

<body>
<div id="root"></div>
<script src="app/bundle.js"></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>
<div id="root"></div>

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

</html>
20 changes: 8 additions & 12 deletions src/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,7 @@ const conversionArray = require('./routes/conversionArray');
const units = require('./routes/units');
const conversions = require('./routes/conversions');
const ciks = require('./routes/ciks');
const { HTTP_CODES } = require('./util/httpCodes');

// Detect test environment and use higher rate limits during tests.
// Rate limiting is critical for security in production but interferes with automated testing.
// Using a separate test rate limiter (100x production limits) ensures the middleware is still
// exercised during tests while preventing test failures from rate limiting.
// 100x is certainly big enough to avoid issues and the exact value should not be important as
// the goal is to avoid hitting rate limiting in testing.
// Note that NODE_ENV of test should only be set for the testing environment and is done in
// package.json in the script section for the test ones.
const isTestEnvironment = process.env.NODE_ENV === 'test';
const testMultiplier = isTestEnvironment ? 100 : 1;
const crypto = require('node:crypto');

// Limit the rate of overall requests to OED
// TODO Verify that user see the message returned, see https://express-rate-limit.mintlify.app/reference/configuration#message
Expand Down Expand Up @@ -162,6 +151,13 @@ router.get('*', (req, res) => {

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
22 changes: 16 additions & 6 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 @@ -40,12 +40,22 @@ const config = {
// All TypeScript ('.ts' or '.tsx') will be handled by 'awesome-typescript-loader'.
{ test: /\.[jt]sx?$/, exclude: /node_modules/, use: 'ts-loader' },
// CSS stylesheet loader.
{ test: /\.css$/, use: [
{loader: 'style-loader'},
{loader: 'css-loader'}
] },
{
test: /\.css$/, use: [
{
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'.
{ enforce: 'pre', test: /\.js$/, use:[{loader: 'source-map-loader'}] }
{ enforce: 'pre', test: /\.js$/, use: [{ loader: 'source-map-loader' }] }
]
},
output: {
Expand Down
Loading