diff --git a/.gitignore b/.gitignore index 16378a1..785633f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store node_modules/ dist/ -.firebase \ No newline at end of file +.firebase +firebase.json \ No newline at end of file diff --git a/firebase.json b/firebase.json deleted file mode 100644 index 22761cb..0000000 --- a/firebase.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "hosting": { - "public": "dist", - "cleanUrls": true, - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**" - ], - "rewrites": [ - { - "source": "**", - "destination": "/404.html" - } - ], - "headers": [ - { - "source": "**/*.@(ico|jpg|jpeg|gif|png|webp|js|css)", - "headers" : [{ - "key" : "Cache-Control", - "value" : "max-age=31536000" - }] - }, - { - "source": "**/*.@(eot|otf|ttf|ttc|woff|woff2|font.css)", - "headers" : [{ - "key" : "Cache-Control", - "value" : "max-age=31536000" - }] - } - ] - } -} diff --git a/generate-firebase-json.js b/generate-firebase-json.js new file mode 100644 index 0000000..33e57e5 --- /dev/null +++ b/generate-firebase-json.js @@ -0,0 +1,62 @@ +const cheerio = require('cheerio'); +const get = require('lodash/get'); +const transformerFirebase = require('./transformer-firebase.js'); +const { sources } = require('webpack'); + +/** + * The default function for adding the CSP to the head of a document + * Can be overwritten to allow the developer to process the CSP in their own way + * @param {string} builtPolicy + * @param {object} htmlPluginData + * @param {object} $ + */ +const defaultProcessFn = (builtPolicy, htmlPluginData, $) => { + let metaTag = $('meta[http-equiv="Content-Security-Policy"]'); + + // Add element if it doesn't exist. + if (!metaTag.length) { + metaTag = cheerio.load('')( + 'meta' + ); + metaTag.prependTo($('head')); + } + + // build the policy into the context attr of the csp meta tag + metaTag.attr('content', builtPolicy); + + // eslint-disable-next-line no-param-reassign + htmlPluginData.html = get(htmlPluginData, 'plugin.options.xhtml', false) + ? $.xml() + : $.html(); +}; + +var cspHeadersForSourceGlob = []; + +const generateFirebaseJson = ( + builtPolicy, + htmlPluginData, + $, + compilation, +) => { + const sourceGlob = htmlPluginData.outputName + .replace(/\.html/, '') + .replace(/index/, '/') + .replace(/404/, '**/*'); + cspHeadersForSourceGlob = [ + { + source: sourceGlob, + headers: [ + { + key: 'Content-Security-Policy', + value: builtPolicy, + } + ], + } + ].concat(cspHeadersForSourceGlob); + + transformerFirebase(cspHeadersForSourceGlob); + + defaultProcessFn(builtPolicy, htmlPluginData, $, compilation); +}; + +module.exports = generateFirebaseJson; \ No newline at end of file diff --git a/transformer-firebase.js b/transformer-firebase.js new file mode 100644 index 0000000..4f264cb --- /dev/null +++ b/transformer-firebase.js @@ -0,0 +1,68 @@ +const fs = require('fs'); +const path = require('path'); + +// This transformer will generate a `firebase.json` config file, based on the application +// environment config and custom headers. If you are deploying to a Firebase project with multiple +// sites, set the FIREBASE_TARGET environment variable to match the host created with +// `firebase target:apply hosting target-name resource-name` +module.exports = (headers) => { + const config = { + hosting: { + public: 'dist', + cleanUrls: true, + ignore: [ + 'firebase.json', + '**/.*', + '**/node_modules/**', + ], + rewrites: [ + { + "source": "**", + "destination": "/404.html" + }, + ], + headers: [ + { + source: '**/*.@(ico|jpg|jpeg|gif|png|webp|js.map|js|css|txt|html)', + headers: [ + { + key: 'Cache-Control', + value: 's-maxage=31536000,immutable', + }, + ], + }, + { + source: '**/*.@(eot|otf|ttf|ttc|woff|woff2|font.css)', + headers: [ + { + key: 'Cache-Control', + value: 's-maxage=31536000,immutable', + }, + ], + }, + ...headers, + //{ + // source: '**', + // headers: Object.entries({ + // ...headers, + // }).map(([key, value]) => ({ + // key, + // value, + // })), + //}, + ], + }, + }; + + const target = process.env.FIREBASE_TARGET; + + if (target) { + config.hosting.target = target; + } + + fs.writeFileSync( + path.join(__dirname, './firebase.json'), + JSON.stringify(config, null, 2), + { encoding: 'utf8' } + ); +}; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 00402a8..77861fd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,7 +14,7 @@ const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin"); const SitemapWebpackPlugin = require('sitemap-webpack-plugin').default; const HtmlNewLineRemoverPlugin = require('./html-new-line-remover-plugin.js'); const CspHtmlWebpackPlugin = require('./inline-script-csp-html-webpack-plugin.js'); - +const generateFirebaseJson = require('./generate-firebase-json.js'); const { interpolateName } = require('loader-utils'); const fs = require('fs'); const glob = require('glob'); @@ -213,14 +213,19 @@ module.exports = { 'object-src': "'none'", 'require-trusted-types-for': "'script'", }, - { - hashingMethod: 'sha256', - enabled: true, - hashEnabled: { - 'script-src': true, - 'style-src': true - }, - } + { + hashingMethod: 'sha384', + enabled: true, + hashEnabled: { + 'script-src': true, + 'style-src': true + }, + nonceEnabled: { + 'script-src': true, + 'style-src': true, + }, + processFn: generateFirebaseJson, + } ), ], optimization: {