From 5ee34ce0826ca7e710deb11321a805e86bfa0f84 Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Wed, 23 Feb 2022 20:45:49 +0000 Subject: [PATCH] Subresource integrity ftw! --- src/static/app.mjs | 2 -- src/static/esbuild.mjs | 46 ++++++++++++++++++++++++++++++++++++++---- src/static/index.html | 4 ++-- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/static/app.mjs b/src/static/app.mjs index d17e279..e09f407 100644 --- a/src/static/app.mjs +++ b/src/static/app.mjs @@ -1,5 +1,3 @@ -import __INDEX__ from "./index.html"; - import make_router from './js/routes_client.mjs'; window.addEventListener("load", (_event) => { diff --git a/src/static/esbuild.mjs b/src/static/esbuild.mjs index 2eacd88..189194e 100755 --- a/src/static/esbuild.mjs +++ b/src/static/esbuild.mjs @@ -1,21 +1,45 @@ #!/usr/bin/env node "use strict"; +import fs from 'fs'; import path from 'path'; +import crypto from 'crypto'; import esbuild from 'esbuild'; const __dirname = import.meta.url.slice(7, import.meta.url.lastIndexOf("/")); +/** + * Hashes the contents of a file. + * @ref https://stackoverflow.com/a/44643479/1460422 + * @param {string} hashName The name of the hash algorithm to use. + * @param {string} path The path to the file to hash. + * @return {string} The resulting hash as a hexadecimal string. + */ +function hash_file(hashName, path) { + return new Promise((resolve, reject) => { + const hash = crypto.createHash(hashName); + const stream = fs.createReadStream(path); + stream.once("error", reject); + stream.on("data", chunk => hash.update(chunk)); + stream.once("end", () => { + stream.off("error", reject); + resolve(hash.digest('base64')); + }); + }); +} + (async () => { - "use strict"; - + "use strict"; + + const outdir = path.resolve(__dirname, "../static-dist"); + const result = await esbuild.build({ entryPoints: [ "./app.mjs", "./app.css" ].map(filepath => path.resolve(__dirname, filepath)), - outdir: path.resolve(__dirname, "../static-dist"), + outdir, bundle: true, minify: true, sourcemap: true, @@ -28,9 +52,23 @@ const __dirname = import.meta.url.slice(7, import.meta.url.lastIndexOf("/")); ".ttf": "file" } }); - console.log(result); + if(result.errors.length > 0 || result.warnings.length > 0) + console.log(result); + // We *would* use SHA3 here, but we need to use SHA2 for subresource integrity + let algorithm = "sha256"; + let css_hash = await hash_file(algorithm, path.join(outdir, "app.css")); + let js_hash = await hash_file(algorithm, path.join(outdir, "app.js")); + + let html = (await fs.promises.readFile(path.join(__dirname, "index.html"), "utf-8")) + .replace(/\{JS_HASH\}/g, js_hash) + .replace(/\{CSS_HASH\}/g, css_hash) + .replace(/\{JS_HASH_SHORT\}/g, js_hash.substring(0, 7).replace(/[+/=]/, "")) + .replace(/\{CSS_HASH_SHORT\}/g, css_hash.substring(0, 7).replace(/[+/=]/, "")) + .replace(/\{ALGO\}/g, algorithm); // TODO: Use fs.promises.copyFile() for index.html here, and also maybe find & replace for css/js filenames that we can then randomise? + await fs.promises.writeFile(path.join(outdir, "index.html"), html); + // console.log(await esbuild.analyzeMetafile(result.metafile)); })(); diff --git a/src/static/index.html b/src/static/index.html index 37a6d9d..cfd0afa 100644 --- a/src/static/index.html +++ b/src/static/index.html @@ -18,7 +18,7 @@ - - + +