mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
Add Content Security Policy (CSP) middleware
This commit is contained in:
parent
37cd1fdb4c
commit
4552a9cf29
2 changed files with 122 additions and 0 deletions
91
server/csp.js
Normal file
91
server/csp.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
const helmet = require('helmet');
|
||||
const { config } = require('./importer');
|
||||
|
||||
const dev = process.env.REACT_APP_ENV === 'development';
|
||||
const googleAnalyticsEnabled = !!process.env.REACT_APP_GOOGLE_ANALYTICS_ID;
|
||||
|
||||
/**
|
||||
* Middleware for creating a Content Security Policy
|
||||
*
|
||||
* @param {String} reportUri URL where the browser will POST the
|
||||
* policy violation reports
|
||||
*
|
||||
* @param {Boolean} enforceSsl When SSL is enforced, all mixed content
|
||||
* is blocked/reported by the policy
|
||||
*
|
||||
* @param {Boolean} reportOnly In the report mode, requests are only
|
||||
* reported to the report URL instead of blocked
|
||||
*/
|
||||
module.exports = (reportUri, enforceSsl, reportOnly) => {
|
||||
const self = "'self'";
|
||||
const inline = "'unsafe-inline'";
|
||||
const sharetribeApi = config.sdk.baseUrl;
|
||||
const sharetribeAssets = 'https://assets-sharetribecom.sharetribe.com/';
|
||||
const imgix = '*.imgix.net/';
|
||||
const loremPixel = 'https://lorempixel.com/';
|
||||
const placeholder = 'https://via.placeholder.com/';
|
||||
const googleAnalytics = 'https://www.google-analytics.com/';
|
||||
const googleMaps = 'https://maps.googleapis.com/';
|
||||
const googleStaticCsi = 'https://csi.gstatic.com/';
|
||||
const googleStaticFonts = 'https://fonts.gstatic.com/';
|
||||
const googleStaticMaps = 'https://maps.gstatic.com/';
|
||||
const googleFonts = 'https://fonts.googleapis.com/';
|
||||
const stripeJs = 'https://js.stripe.com/';
|
||||
const stripeQ = 'https://q.stripe.com/';
|
||||
|
||||
const scriptSrc = [self, inline, googleMaps, stripeJs];
|
||||
|
||||
const styleSrc = [self, inline, googleFonts];
|
||||
|
||||
const imgSrc = [
|
||||
self,
|
||||
'data:',
|
||||
imgix,
|
||||
loremPixel,
|
||||
placeholder,
|
||||
stripeQ,
|
||||
googleMaps,
|
||||
googleStaticCsi,
|
||||
googleStaticMaps,
|
||||
];
|
||||
|
||||
const connectSrc = [self, sharetribeApi, googleMaps];
|
||||
|
||||
const fontSrc = [self, sharetribeAssets, googleStaticFonts];
|
||||
|
||||
const frameSrc = [self, stripeJs];
|
||||
|
||||
if (googleAnalyticsEnabled) {
|
||||
// Only whitelist Google Analytics URLs when the analytics is
|
||||
// enabled.
|
||||
scriptSrc.push(googleAnalytics);
|
||||
imgSrc.push(googleAnalytics);
|
||||
}
|
||||
if (dev) {
|
||||
// The default Core docker-compose setup server images from this
|
||||
// URL locally.
|
||||
imgSrc.push('*.localhost:8000');
|
||||
}
|
||||
|
||||
const directives = {
|
||||
scriptSrc,
|
||||
styleSrc,
|
||||
imgSrc,
|
||||
connectSrc,
|
||||
fontSrc,
|
||||
frameSrc,
|
||||
formAction: [self],
|
||||
defaultSrc: [self],
|
||||
baseUri: [self],
|
||||
reportUri,
|
||||
};
|
||||
|
||||
if (enforceSsl) {
|
||||
directives.blockAllMixedContent = true;
|
||||
}
|
||||
|
||||
return helmet.contentSecurityPolicy({
|
||||
directives,
|
||||
reportOnly,
|
||||
});
|
||||
};
|
||||
|
|
@ -20,6 +20,7 @@ const express = require('express');
|
|||
const helmet = require('helmet');
|
||||
const compression = require('compression');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const bodyParser = require('body-parser');
|
||||
const enforceSsl = require('express-enforces-ssl');
|
||||
const path = require('path');
|
||||
const sharetribeSdk = require('sharetribe-sdk');
|
||||
|
|
@ -31,6 +32,7 @@ const dataLoader = require('./dataLoader');
|
|||
const fs = require('fs');
|
||||
const log = require('./log');
|
||||
const { sitemapStructure } = require('./sitemap');
|
||||
const csp = require('./csp');
|
||||
|
||||
const buildPath = path.resolve(__dirname, '..', 'build');
|
||||
const env = process.env.REACT_APP_ENV || 'production';
|
||||
|
|
@ -41,6 +43,9 @@ const CLIENT_ID =
|
|||
const BASE_URL = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL || 'http://localhost:8088';
|
||||
const USING_SSL = process.env.REACT_APP_SHARETRIBE_USING_SSL === 'true';
|
||||
const TRUST_PROXY = process.env.SERVER_SHARETRIBE_TRUST_PROXY || null;
|
||||
const CSP = process.env.REACT_APP_CSP;
|
||||
const cspReportUrl = '/csp-report';
|
||||
const cspEnabled = CSP === 'block' || CSP === 'report';
|
||||
const app = express();
|
||||
|
||||
const errorPage = fs.readFileSync(path.join(buildPath, '500.html'), 'utf-8');
|
||||
|
|
@ -60,6 +65,23 @@ app.use(log.requestHandler());
|
|||
// See: https://www.npmjs.com/package/helmet
|
||||
app.use(helmet());
|
||||
|
||||
if (cspEnabled) {
|
||||
// When a CSP directive is violated, the browser posts a JSON body
|
||||
// to the defined report URL and we need to parse this body.
|
||||
app.use(
|
||||
bodyParser.json({
|
||||
type: ['json', 'application/csp-report'],
|
||||
})
|
||||
);
|
||||
|
||||
// CSP can be turned on in report or block mode. In report mode, the
|
||||
// browser checks the policy and calls the report URL when the
|
||||
// policy is violated, but doesn't block any requests. In block
|
||||
// mode, the browser also blocks the requests.
|
||||
const reportOnly = CSP === 'report';
|
||||
app.use(csp(cspReportUrl, USING_SSL, reportOnly));
|
||||
}
|
||||
|
||||
// Redirect HTTP to HTTPS if USING_SSL is `true`.
|
||||
// This also works behind reverse proxies (load balancers) as they are for example used by Heroku.
|
||||
// In such cases, however, the TRUST_PROXY parameter has to be set (see below)
|
||||
|
|
@ -193,6 +215,15 @@ app.get('*', (req, res) => {
|
|||
// will be logged there.
|
||||
app.use(log.errorHandler());
|
||||
|
||||
if (cspEnabled) {
|
||||
// Handler for CSP violation reports.
|
||||
app.post(cspReportUrl, (req, res) => {
|
||||
const report = req.body ? req.body['csp-report'] : null;
|
||||
log.error(new Error('CSP violation'), 'csp-violation', report);
|
||||
res.status(204).end();
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
const mode = dev ? 'development' : 'production';
|
||||
console.log(`Listening to port ${PORT} in ${mode} mode`);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue