diff --git a/inline-script-csp-html-webpack-plugin.js b/inline-script-csp-html-webpack-plugin.js new file mode 100644 index 0000000..505bdac --- /dev/null +++ b/inline-script-csp-html-webpack-plugin.js @@ -0,0 +1,68 @@ +const cheerio = require('cheerio'); +const CspHtmlWebpackPlugin = require('csp-html-webpack-plugin'); +const flatten = require('lodash/flatten'); +const get = require('lodash/get'); + +/** + * InlineScriptCspHtmlWebpackPlugin + * An extension of CspHtmlWebpackPlugin to handle Content Security Policies (CSPs). + * This class only modifies a single property (`_useHtmlParser2`) in the Cheerio configuration + * to customize how HTML is parsed. + */ +class InlineScriptCspHtmlWebpackPlugin extends CspHtmlWebpackPlugin { + /** + * Constructor + * Calls the base class constructor to set up the plugin with user-defined or default policies and options. + * @param {object} policy - CSP policy object, typically defining 'script-src' and 'style-src'. + * @param {object} additionalOpts - Additional options for nonce/hash generation and processing. + */ + constructor(policy = {}, additionalOpts = {}) { + super(policy, additionalOpts); + } + + /** + * Processes HtmlWebpackPlugin's HTML output to inject the Content Security Policy. + * The key difference from the base class is setting `_useHtmlParser2: false` in the Cheerio configuration. + * @param {object} compilation - Webpack's compilation object. + * @param {object} htmlPluginData - Data object from HtmlWebpackPlugin containing the generated HTML. + * @param {function} compileCb - Callback to continue Webpack's compilation process. + */ + processCsp(compilation, htmlPluginData, compileCb) { + const $ = cheerio.load(htmlPluginData.html, { + decodeEntities: false, + _useHtmlParser2: false, // *** Changed from 'true' in the base class to 'false' *** + xmlMode: get(htmlPluginData, 'plugin.options.xhtml', false), + }); + + // if not enabled, remove the empty tag + if (!this.isEnabled(htmlPluginData)) { + return compileCb(null, htmlPluginData); + } + + // get all nonces for script and style tags + const scriptNonce = this.setNonce($, 'script-src', 'script[src]'); + const styleNonce = this.setNonce($, 'style-src', 'link[rel="stylesheet"]'); + + // get all shas for script and style tags + const scriptShas = this.getShas($, 'script-src', 'script:not([src])'); + const styleShas = this.getShas($, 'style-src', 'style:not([href])'); + + const builtPolicy = this.buildPolicy({ + ...this.policy, + 'script-src': flatten([this.policy['script-src']]).concat( + scriptShas, + scriptNonce + ), + 'style-src': flatten([this.policy['style-src']]).concat( + styleShas, + styleNonce + ), + }); + + this.processFn(builtPolicy, htmlPluginData, $, compilation); + + return compileCb(null, htmlPluginData); + } +} + +module.exports = InlineScriptCspHtmlWebpackPlugin; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 63c002c..c16af47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "html-inline-css-webpack-plugin": "^1.11.1", "html-inline-script-webpack-plugin": "^3.2.1", "html-loader": "^5.1.0", - "html-minifier": "^4.0.0", "html-minimizer-webpack-plugin": "^5.0.0", "html-webpack-plugin": "^5.6.3", "image-minimizer-webpack-plugin": "^4.1.1", @@ -2827,7 +2826,8 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-7.0.2.tgz", "integrity": "sha512-mm2HqV22l8lFQh4r2oSsOEVea+m0qqxEmwpc9kC1p/XzmjLWrReR9D/GRs8Pex2NX/imyEH9c5IU/7tMBQCHOA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.4", @@ -4740,16 +4740,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", - "dev": true, - "dependencies": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -5159,18 +5149,6 @@ "node": ">= 0.3.0" } }, - "node_modules/clean-css": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", - "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", - "dev": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 4.0" - } - }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -9416,27 +9394,6 @@ "webpack": "^5.0.0" } }, - "node_modules/html-minifier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", - "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", - "dev": true, - "dependencies": { - "camel-case": "^3.0.0", - "clean-css": "^4.2.1", - "commander": "^2.19.0", - "he": "^1.2.0", - "param-case": "^2.1.1", - "relateurl": "^0.2.7", - "uglify-js": "^3.5.1" - }, - "bin": { - "html-minifier": "cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/html-minifier-terser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", @@ -9499,17 +9456,12 @@ "tslib": "^2.0.3" } }, - "node_modules/html-minifier/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, "node_modules/html-minimizer-webpack-plugin": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/html-minimizer-webpack-plugin/-/html-minimizer-webpack-plugin-5.0.0.tgz", "integrity": "sha512-m4XV1pX+X3uEWOqK5MHhZOHRC1NnEjVc+6KYzSVs9LdMfgpGka0FFogBt7E6srWy894/mQrysUDrUk+EublATw==", "dev": true, + "license": "MIT", "dependencies": { "@types/html-minifier-terser": "^7.0.2", "html-minifier-terser": "^7.2.0", @@ -11521,12 +11473,6 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, - "node_modules/lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", - "dev": true - }, "node_modules/lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -12181,15 +12127,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "node_modules/no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "dependencies": { - "lower-case": "^1.1.1" - } - }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -12973,15 +12910,6 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, - "node_modules/param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", - "dev": true, - "dependencies": { - "no-case": "^2.2.0" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -17857,18 +17785,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/uint8array-extras": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", @@ -18085,12 +18001,6 @@ "node": ">=4" } }, - "node_modules/upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", - "dev": true - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -22059,16 +21969,6 @@ "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", "dev": true }, - "camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", - "dev": true, - "requires": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, "camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -22342,15 +22242,6 @@ "json-parse-helpfulerror": "^1.0.3" } }, - "clean-css": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", - "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", - "dev": true, - "requires": { - "source-map": "~0.6.0" - } - }, "clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -25541,29 +25432,6 @@ "parse5": "^7.1.2" } }, - "html-minifier": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", - "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", - "dev": true, - "requires": { - "camel-case": "^3.0.0", - "clean-css": "^4.2.1", - "commander": "^2.19.0", - "he": "^1.2.0", - "param-case": "^2.1.1", - "relateurl": "^0.2.7", - "uglify-js": "^3.5.1" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - } - } - }, "html-minifier-terser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", @@ -27100,12 +26968,6 @@ "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, - "lower-case": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", - "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", - "dev": true - }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -27595,15 +27457,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, - "no-case": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", - "dev": true, - "requires": { - "lower-case": "^1.1.1" - } - }, "node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -28167,15 +28020,6 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, - "param-case": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", - "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", - "dev": true, - "requires": { - "no-case": "^2.2.0" - } - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -31552,12 +31396,6 @@ "is-typedarray": "^1.0.0" } }, - "uglify-js": { - "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", - "dev": true - }, "uint8array-extras": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", @@ -31707,12 +31545,6 @@ } } }, - "upper-case": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", - "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", - "dev": true - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index 63b2fa1..5cc0cbf 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Personal website and portfolio", "main": "src/index.html", "scripts": { - "build": "webpack --mode production --optimization-minimize --config webpack.config.js", + "build": "webpack --mode production --config webpack.config.js", "start": "npx webpack serve --mode production --optimization-minimize --config webpack.config.js --stats verbose" }, "repository": { @@ -28,7 +28,6 @@ "html-inline-css-webpack-plugin": "^1.11.1", "html-inline-script-webpack-plugin": "^3.2.1", "html-loader": "^5.1.0", - "html-minifier": "^4.0.0", "html-minimizer-webpack-plugin": "^5.0.0", "html-webpack-plugin": "^5.6.3", "image-minimizer-webpack-plugin": "^4.1.1", diff --git a/src/scss/index.scss b/src/scss/index.scss index a5bab27..e5015e2 100644 --- a/src/scss/index.scss +++ b/src/scss/index.scss @@ -25,19 +25,12 @@ html, body { align-items: center; padding: $gap; position: relative; - - > * { - max-width: 640px; - } } .clazz-content { flex: 1 0 auto; width: 100%; - - > * { - margin-bottom: $gap; - } + max-width: 640px; } // Header @@ -65,19 +58,23 @@ h1 { } // Button Styles -.clazz-button-container a { - background-color: $primary-color; - border-radius: $border-radius; - color: $link-color; - padding: $button-padding; - width: 100%; - display: flex; - align-items: center; - justify-content: center; - text-decoration: none; - font-size: $font-size-small; - line-height: 24px; - transition: background-color 0.3s ease; +.clazz-button-container +{ + margin-bottom: $gap; + a { + background-color: $primary-color; + border-radius: $border-radius; + color: $link-color; + padding: $button-padding; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + text-decoration: none; + font-size: $font-size-small; + line-height: 24px; + transition: background-color 0.3s ease; + } } .clazz-button-container a { @@ -93,22 +90,29 @@ h1 { justify-content: center; flex-wrap: wrap; width: 100%; + margin-bottom: $gap; gap: calc($gap / 2); } -.clazz-social-link-container a { - display: inline-block; - color: $primary-color; - padding: calc($gap /2 ) $gap; - transition: color 0.3s ease; - &:visited { +.clazz-social-link-container { + a { + display: inline-block; color: $primary-color; - } - &:hover { - color: $hover-color; + padding: calc($gap /2 ) $gap; + transition: color 0.3s ease; + &:visited { + color: $primary-color; + } + &:hover { + color: $hover-color; + } } } +.fa-2xl { + line-height: 1; +} + // Footer footer { display: flex; @@ -122,6 +126,9 @@ footer { } // Utility Classes +.clazz-hidden { + display: none; +} .clazz-text-center { text-align: center; } \ No newline at end of file diff --git a/src/views/index.ejs b/src/views/index.ejs index 511610f..9029b11 100644 --- a/src/views/index.ejs +++ b/src/views/index.ejs @@ -21,7 +21,21 @@ - <%- include('./linkedData.ejs', { baseUrl: baseUrl, profilePictureUrlPath: profilePictureUrlPath }); %> +