From 6d9715169caa2b3b4db7d23677c472f92977746b Mon Sep 17 00:00:00 2001 From: Ryan Date: Sat, 26 Apr 2025 02:28:02 -0400 Subject: [PATCH] Harden security: enable CSP, add SRI, and externalize inline scripts --- CHANGELOG.md | 26 +++++++++++++++++++ Dockerfile | 1 + public/index.html | 55 ++++++++++++++++------------------------ public/js/auth.js | 26 ++++++++++++++----- public/js/fileActions.js | 38 +++++++++++++++++++-------- 5 files changed, 97 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7abcea8..0728596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,31 @@ # Changelog +## Changes 4/26/2025 + +### 1. Apache / Dockerfile (CSP) + +- Enabled Apache’s `mod_headers` in the Dockerfile (`a2enmod headers ssl deflate expires proxy proxy_fcgi rewrite`) +- Added a strong `Content-Security-Policy` header in the vhost configs to lock down allowed sources for scripts, styles, fonts, images, and connections + +### 2. index.html & CDN Includes + +- Applied Subresource Integrity (`integrity` + `crossorigin="anonymous"`) to all static CDN assets (Bootstrap CSS, CodeMirror CSS/JS, Resumable.js, DOMPurify, Fuse.js) +- Omitted SRI on Google Fonts & Material Icons links (dynamic per-browser CSS) +- Removed all inline ` @@ -20,9 +13,12 @@ - - - + + + @@ -396,27 +392,23 @@ - - diff --git a/public/js/auth.js b/public/js/auth.js index b3c8025..d64d96d 100644 --- a/public/js/auth.js +++ b/public/js/auth.js @@ -437,12 +437,26 @@ function initAuth() { submitLogin(formData); }); } - document.getElementById("logoutBtn").addEventListener("click", function () { - fetch("/api/auth/logout.php", { - method: "POST", - credentials: "include", - headers: { "X-CSRF-Token": window.csrfToken } - }).then(() => window.location.reload(true)).catch(() => { }); + // handle ?logout=1 query + const params = new URLSearchParams(window.location.search); + if (params.get('logout') === '1') { + localStorage.removeItem("username"); + localStorage.removeItem("userTOTPEnabled"); + } + + // attach logout button listener + document.addEventListener('DOMContentLoaded', () => { + const btn = document.getElementById('logoutBtn'); + if (!btn) return; + btn.addEventListener('click', () => { + fetch('/api/auth/logout.php', { + method: 'POST', + credentials: 'include', + headers: { 'X-CSRF-Token': window.csrfToken } + }) + .then(() => window.location.reload(true)) + .catch(() => { }); + }); }); document.getElementById("addUserBtn").addEventListener("click", function () { resetUserForm(); diff --git a/public/js/fileActions.js b/public/js/fileActions.js index 2557e6c..dd807e5 100644 --- a/public/js/fileActions.js +++ b/public/js/fileActions.js @@ -193,10 +193,10 @@ export function handleExtractZipSelected(e) { } document.addEventListener("DOMContentLoaded", () => { - const zipNameModal = document.getElementById("downloadZipModal"); - const progressModal = document.getElementById("downloadProgressModal"); - const cancelZipBtn = document.getElementById("cancelDownloadZip"); - const confirmZipBtn = document.getElementById("confirmDownloadZip"); + const zipNameModal = document.getElementById("downloadZipModal"); + const progressModal = document.getElementById("downloadProgressModal"); + const cancelZipBtn = document.getElementById("cancelDownloadZip"); + const confirmZipBtn = document.getElementById("confirmDownloadZip"); // 1) Cancel button hides the name modal if (cancelZipBtn) { @@ -219,8 +219,8 @@ document.addEventListener("DOMContentLoaded", () => { } // b) Hide the name‐input modal, show the spinner modal - zipNameModal.style.display = "none"; - progressModal.style.display = "block"; + zipNameModal.style.display = "none"; + progressModal.style.display = "block"; // c) (Optional) update the “Preparing…” text if you gave it an ID const titleEl = document.getElementById("downloadProgressTitle"); @@ -233,11 +233,11 @@ document.addEventListener("DOMContentLoaded", () => { credentials: "include", headers: { "Content-Type": "application/json", - "X-CSRF-Token": window.csrfToken + "X-CSRF-Token": window.csrfToken }, body: JSON.stringify({ folder: window.currentFolder || "root", - files: window.filesToDownload + files: window.filesToDownload }) }); if (!res.ok) { @@ -252,8 +252,8 @@ document.addEventListener("DOMContentLoaded", () => { // e) Hand off to the browser’s download manager const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; + const a = document.createElement("a"); + a.href = url; a.download = zipName; document.body.appendChild(a); a.click(); @@ -555,4 +555,22 @@ export function initFileActions() { } } +// Hook up the single‐file download modal buttons +document.addEventListener("DOMContentLoaded", () => { + const cancelDownloadFileBtn = document.getElementById("cancelDownloadFile"); + if (cancelDownloadFileBtn) { + cancelDownloadFileBtn.addEventListener("click", () => { + document.getElementById("downloadFileModal").style.display = "none"; + }); + } + + const confirmSingleDownloadBtn = document.getElementById("confirmSingleDownloadButton"); + if (confirmSingleDownloadBtn) { + confirmSingleDownloadBtn.addEventListener("click", confirmSingleDownload); + } + + // Make Enter also confirm the download + attachEnterKeyListener("downloadFileModal", "confirmSingleDownloadButton"); +}); + window.renameFile = renameFile; \ No newline at end of file