diff --git a/CHANGELOG.md b/CHANGELOG.md index 68f5901..d79e86b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Updated TOTP integration (namespace, enum, QR provider) accordingly - Updated docker image from 22.04 to 24.04 - Ensure consistent session behavior +- Fix totp_setup.php to use header-based CSRF token verification --- diff --git a/js/authModals.js b/js/authModals.js index f0277f3..b85c50b 100644 --- a/js/authModals.js +++ b/js/authModals.js @@ -325,19 +325,21 @@ export function openTOTPModal() { z-index: 3100; `; totpModal.innerHTML = ` - - `; + + `; document.body.appendChild(totpModal); + loadTOTPQRCode(); document.getElementById("closeTOTPModal").addEventListener("click", () => { closeTOTPModal(true); @@ -406,6 +408,13 @@ export function openTOTPModal() { modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff"; modalContent.style.color = isDarkMode ? "#e0e0e0" : "#000"; + // Clear any previous QR code src if needed and then load it: + const qrImg = document.getElementById("totpQRCodeImage"); + if (qrImg) { + qrImg.src = ""; + } + loadTOTPQRCode(); + // Focus the input and attach enter key listener const totpConfirmInput = document.getElementById("totpConfirmInput"); if (totpConfirmInput) { @@ -419,6 +428,33 @@ export function openTOTPModal() { } } +function loadTOTPQRCode() { + fetch("totp_setup.php", { + method: "GET", + credentials: "include", + headers: { + "X-CSRF-Token": window.csrfToken // Send your CSRF token here + } + }) + .then(response => { + if (!response.ok) { + throw new Error("Failed to fetch QR code. Status: " + response.status); + } + return response.blob(); + }) + .then(blob => { + const imageURL = URL.createObjectURL(blob); + const qrImg = document.getElementById("totpQRCodeImage"); + if (qrImg) { + qrImg.src = imageURL; + } + }) + .catch(error => { + console.error("Error loading TOTP QR code:", error); + showToast("Error loading QR code."); + }); +} + // Updated closeTOTPModal function with a disable parameter export function closeTOTPModal(disable = true) { const totpModal = document.getElementById("totpModal"); @@ -800,18 +836,18 @@ function loadUserPermissionsList() { if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return; // Use stored permissions if available; otherwise fall back to localStorage defaults. -const defaultPerm = { - folderOnly: false, - readOnly: false, - disableUpload: false, -}; + const defaultPerm = { + folderOnly: false, + readOnly: false, + disableUpload: false, + }; -// Normalize the username key to match server storage (e.g., lowercase) -const usernameKey = user.username.toLowerCase(); + // Normalize the username key to match server storage (e.g., lowercase) + const usernameKey = user.username.toLowerCase(); -const userPerm = (permissionsData && typeof permissionsData === "object" && (usernameKey in permissionsData)) - ? permissionsData[usernameKey] - : defaultPerm; + const userPerm = (permissionsData && typeof permissionsData === "object" && (usernameKey in permissionsData)) + ? permissionsData[usernameKey] + : defaultPerm; // Create a row for the user. const row = document.createElement("div"); diff --git a/totp_setup.php b/totp_setup.php index 126524a..e4cf80d 100644 --- a/totp_setup.php +++ b/totp_setup.php @@ -9,16 +9,34 @@ use Endroid\QrCode\Writer\PngWriter; use RobThree\Auth\Algorithm; use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider; -if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) { +// Define the respond() helper if not already defined. +if (!function_exists('respond')) { + function respond($status, $code, $message, $data = []) { + http_response_code($code); + echo json_encode([ + 'status' => $status, + 'code' => $code, + 'message' => $message, + 'data' => $data + ]); + exit; + } +} + +// Allow access if the user is authenticated or pending TOTP. +if (!((isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true) || isset($_SESSION['pending_login_user']))) { http_response_code(403); exit; } +// Retrieve CSRF token from GET parameter or request headers. $headers = array_change_key_case(getallheaders(), CASE_LOWER); -$csrfHeader = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : ''; +$receivedToken = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : ''; -if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) { - respond('error', 403, 'Invalid CSRF token'); +if ($receivedToken !== $_SESSION['csrf_token']) { + echo json_encode(["error" => "Invalid CSRF token"]); + http_response_code(403); + exit; } $username = $_SESSION['username'] ?? ''; @@ -111,7 +129,7 @@ $tfa = new \RobThree\Auth\TwoFactorAuth( 'FileRise', // issuer 6, // number of digits 30, // period in seconds - Algorithm::Sha1 // Correct enum case name from your enum + Algorithm::Sha1 // enum case from your Algorithm enum ); // Retrieve the current TOTP secret for the user. @@ -124,8 +142,6 @@ if (!$totpSecret) { } // Determine the otpauth URL to use. -// If a global OTPAuth URL template is defined, replace placeholders {label} and {secret}. -// Otherwise, use the default method. $globalOtpauthUrl = getGlobalOtpauthUrl(); if (!empty($globalOtpauthUrl)) { $label = "FileRise:" . $username;