Skip to content

Commit a7787e7

Browse files
authored
feat: Automate Firebase JSON generation with dynamic CSP integration (#80)
- **New Features**: - Added `generate-firebase-json.js` to dynamically inject CSP policies into `firebase.json` based on generated headers. - Created `transformer-firebase.js` to streamline Firebase hosting configuration with environment-aware headers. - **Enhancements**: - Updated `CspHtmlWebpackPlugin` configuration: - Enabled `sha384` hashing for improved security. - Added nonce support for `script-src` and `style-src` directives. - Integrated custom processing function (`generateFirebaseJson`) for dynamic CSP handling. - Removed static `firebase.json` and automated its generation during builds. - Updated `.gitignore` to exclude `firebase.json` from tracking. - **Impact**: - Simplifies Firebase deployment workflows by removing the need for manual `firebase.json` updates. - Improves security with dynamically generated CSP headers and nonce-based policies.
1 parent 8ecf04d commit a7787e7

File tree

5 files changed

+146
-43
lines changed

5 files changed

+146
-43
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
22
node_modules/
33
dist/
4-
.firebase
4+
.firebase
5+
firebase.json

firebase.json

Lines changed: 0 additions & 33 deletions
This file was deleted.

generate-firebase-json.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const cheerio = require('cheerio');
2+
const get = require('lodash/get');
3+
const transformerFirebase = require('./transformer-firebase.js');
4+
const { sources } = require('webpack');
5+
6+
/**
7+
* The default function for adding the CSP to the head of a document
8+
* Can be overwritten to allow the developer to process the CSP in their own way
9+
* @param {string} builtPolicy
10+
* @param {object} htmlPluginData
11+
* @param {object} $
12+
*/
13+
const defaultProcessFn = (builtPolicy, htmlPluginData, $) => {
14+
let metaTag = $('meta[http-equiv="Content-Security-Policy"]');
15+
16+
// Add element if it doesn't exist.
17+
if (!metaTag.length) {
18+
metaTag = cheerio.load('<meta http-equiv="Content-Security-Policy">')(
19+
'meta'
20+
);
21+
metaTag.prependTo($('head'));
22+
}
23+
24+
// build the policy into the context attr of the csp meta tag
25+
metaTag.attr('content', builtPolicy);
26+
27+
// eslint-disable-next-line no-param-reassign
28+
htmlPluginData.html = get(htmlPluginData, 'plugin.options.xhtml', false)
29+
? $.xml()
30+
: $.html();
31+
};
32+
33+
var cspHeadersForSourceGlob = [];
34+
35+
const generateFirebaseJson = (
36+
builtPolicy,
37+
htmlPluginData,
38+
$,
39+
compilation,
40+
) => {
41+
const sourceGlob = htmlPluginData.outputName
42+
.replace(/\.html/, '')
43+
.replace(/index/, '/')
44+
.replace(/404/, '**/*');
45+
cspHeadersForSourceGlob = [
46+
{
47+
source: sourceGlob,
48+
headers: [
49+
{
50+
key: 'Content-Security-Policy',
51+
value: builtPolicy,
52+
}
53+
],
54+
}
55+
].concat(cspHeadersForSourceGlob);
56+
57+
transformerFirebase(cspHeadersForSourceGlob);
58+
59+
defaultProcessFn(builtPolicy, htmlPluginData, $, compilation);
60+
};
61+
62+
module.exports = generateFirebaseJson;

transformer-firebase.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
// This transformer will generate a `firebase.json` config file, based on the application
5+
// environment config and custom headers. If you are deploying to a Firebase project with multiple
6+
// sites, set the FIREBASE_TARGET environment variable to match the host created with
7+
// `firebase target:apply hosting target-name resource-name`
8+
module.exports = (headers) => {
9+
const config = {
10+
hosting: {
11+
public: 'dist',
12+
cleanUrls: true,
13+
ignore: [
14+
'firebase.json',
15+
'**/.*',
16+
'**/node_modules/**',
17+
],
18+
rewrites: [
19+
{
20+
"source": "**",
21+
"destination": "/404.html"
22+
},
23+
],
24+
headers: [
25+
{
26+
source: '**/*.@(ico|jpg|jpeg|gif|png|webp|js.map|js|css|txt|html)',
27+
headers: [
28+
{
29+
key: 'Cache-Control',
30+
value: 's-maxage=31536000,immutable',
31+
},
32+
],
33+
},
34+
{
35+
source: '**/*.@(eot|otf|ttf|ttc|woff|woff2|font.css)',
36+
headers: [
37+
{
38+
key: 'Cache-Control',
39+
value: 's-maxage=31536000,immutable',
40+
},
41+
],
42+
},
43+
...headers,
44+
//{
45+
// source: '**',
46+
// headers: Object.entries({
47+
// ...headers,
48+
// }).map(([key, value]) => ({
49+
// key,
50+
// value,
51+
// })),
52+
//},
53+
],
54+
},
55+
};
56+
57+
const target = process.env.FIREBASE_TARGET;
58+
59+
if (target) {
60+
config.hosting.target = target;
61+
}
62+
63+
fs.writeFileSync(
64+
path.join(__dirname, './firebase.json'),
65+
JSON.stringify(config, null, 2),
66+
{ encoding: 'utf8' }
67+
);
68+
};

webpack.config.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
1414
const SitemapWebpackPlugin = require('sitemap-webpack-plugin').default;
1515
const HtmlNewLineRemoverPlugin = require('./html-new-line-remover-plugin.js');
1616
const CspHtmlWebpackPlugin = require('./inline-script-csp-html-webpack-plugin.js');
17-
17+
const generateFirebaseJson = require('./generate-firebase-json.js');
1818
const { interpolateName } = require('loader-utils');
1919
const fs = require('fs');
2020
const glob = require('glob');
@@ -213,14 +213,19 @@ module.exports = {
213213
'object-src': "'none'",
214214
'require-trusted-types-for': "'script'",
215215
},
216-
{
217-
hashingMethod: 'sha256',
218-
enabled: true,
219-
hashEnabled: {
220-
'script-src': true,
221-
'style-src': true
222-
},
223-
}
216+
{
217+
hashingMethod: 'sha384',
218+
enabled: true,
219+
hashEnabled: {
220+
'script-src': true,
221+
'style-src': true
222+
},
223+
nonceEnabled: {
224+
'script-src': true,
225+
'style-src': true,
226+
},
227+
processFn: generateFirebaseJson,
228+
}
224229
),
225230
],
226231
optimization: {

0 commit comments

Comments
 (0)