Fix totp_setup.php to use header-based CSRF token verification
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
- Updated TOTP integration (namespace, enum, QR provider) accordingly
|
- Updated TOTP integration (namespace, enum, QR provider) accordingly
|
||||||
- Updated docker image from 22.04 to 24.04 <https://github.com/error311/filerise-docker>
|
- Updated docker image from 22.04 to 24.04 <https://github.com/error311/filerise-docker>
|
||||||
- Ensure consistent session behavior
|
- Ensure consistent session behavior
|
||||||
|
- Fix totp_setup.php to use header-based CSRF token verification
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -325,19 +325,21 @@ export function openTOTPModal() {
|
|||||||
z-index: 3100;
|
z-index: 3100;
|
||||||
`;
|
`;
|
||||||
totpModal.innerHTML = `
|
totpModal.innerHTML = `
|
||||||
<div class="modal-content" style="${modalContentStyles}">
|
<div class="modal-content" style="${modalContentStyles}">
|
||||||
<span id="closeTOTPModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
<span id="closeTOTPModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">×</span>
|
||||||
<h3>TOTP Setup</h3>
|
<h3>TOTP Setup</h3>
|
||||||
<p>Scan this QR code with your authenticator app:</p>
|
<p>Scan this QR code with your authenticator app:</p>
|
||||||
<img src="totp_setup.php?csrf=${encodeURIComponent(window.csrfToken)}" alt="TOTP QR Code" style="max-width: 100%; height: auto; display: block; margin: 0 auto;">
|
<!-- Create an image placeholder without the CSRF token in the src -->
|
||||||
<br/>
|
<img id="totpQRCodeImage" src="" alt="TOTP QR Code" style="max-width: 100%; height: auto; display: block; margin: 0 auto;">
|
||||||
<p>Enter the 6-digit code from your app to confirm setup:</p>
|
<br/>
|
||||||
<input type="text" id="totpConfirmInput" maxlength="6" style="font-size:24px; text-align:center; width:100%; padding:10px;" placeholder="6-digit code" />
|
<p>Enter the 6-digit code from your app to confirm setup:</p>
|
||||||
<br/><br/>
|
<input type="text" id="totpConfirmInput" maxlength="6" style="font-size:24px; text-align:center; width:100%; padding:10px;" placeholder="6-digit code" />
|
||||||
<button type="button" id="confirmTOTPBtn" class="btn btn-primary">Confirm</button>
|
<br/><br/>
|
||||||
</div>
|
<button type="button" id="confirmTOTPBtn" class="btn btn-primary">Confirm</button>
|
||||||
`;
|
</div>
|
||||||
|
`;
|
||||||
document.body.appendChild(totpModal);
|
document.body.appendChild(totpModal);
|
||||||
|
loadTOTPQRCode();
|
||||||
|
|
||||||
document.getElementById("closeTOTPModal").addEventListener("click", () => {
|
document.getElementById("closeTOTPModal").addEventListener("click", () => {
|
||||||
closeTOTPModal(true);
|
closeTOTPModal(true);
|
||||||
@@ -406,6 +408,13 @@ export function openTOTPModal() {
|
|||||||
modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff";
|
modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff";
|
||||||
modalContent.style.color = isDarkMode ? "#e0e0e0" : "#000";
|
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
|
// Focus the input and attach enter key listener
|
||||||
const totpConfirmInput = document.getElementById("totpConfirmInput");
|
const totpConfirmInput = document.getElementById("totpConfirmInput");
|
||||||
if (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
|
// Updated closeTOTPModal function with a disable parameter
|
||||||
export function closeTOTPModal(disable = true) {
|
export function closeTOTPModal(disable = true) {
|
||||||
const totpModal = document.getElementById("totpModal");
|
const totpModal = document.getElementById("totpModal");
|
||||||
@@ -800,18 +836,18 @@ function loadUserPermissionsList() {
|
|||||||
if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return;
|
if ((user.role && user.role === "1") || user.username.toLowerCase() === "admin") return;
|
||||||
|
|
||||||
// Use stored permissions if available; otherwise fall back to localStorage defaults.
|
// Use stored permissions if available; otherwise fall back to localStorage defaults.
|
||||||
const defaultPerm = {
|
const defaultPerm = {
|
||||||
folderOnly: false,
|
folderOnly: false,
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
disableUpload: false,
|
disableUpload: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Normalize the username key to match server storage (e.g., lowercase)
|
// Normalize the username key to match server storage (e.g., lowercase)
|
||||||
const usernameKey = user.username.toLowerCase();
|
const usernameKey = user.username.toLowerCase();
|
||||||
|
|
||||||
const userPerm = (permissionsData && typeof permissionsData === "object" && (usernameKey in permissionsData))
|
const userPerm = (permissionsData && typeof permissionsData === "object" && (usernameKey in permissionsData))
|
||||||
? permissionsData[usernameKey]
|
? permissionsData[usernameKey]
|
||||||
: defaultPerm;
|
: defaultPerm;
|
||||||
|
|
||||||
// Create a row for the user.
|
// Create a row for the user.
|
||||||
const row = document.createElement("div");
|
const row = document.createElement("div");
|
||||||
|
|||||||
@@ -9,16 +9,34 @@ use Endroid\QrCode\Writer\PngWriter;
|
|||||||
use RobThree\Auth\Algorithm;
|
use RobThree\Auth\Algorithm;
|
||||||
use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider;
|
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);
|
http_response_code(403);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retrieve CSRF token from GET parameter or request headers.
|
||||||
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
$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']) {
|
if ($receivedToken !== $_SESSION['csrf_token']) {
|
||||||
respond('error', 403, 'Invalid CSRF token');
|
echo json_encode(["error" => "Invalid CSRF token"]);
|
||||||
|
http_response_code(403);
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$username = $_SESSION['username'] ?? '';
|
$username = $_SESSION['username'] ?? '';
|
||||||
@@ -111,7 +129,7 @@ $tfa = new \RobThree\Auth\TwoFactorAuth(
|
|||||||
'FileRise', // issuer
|
'FileRise', // issuer
|
||||||
6, // number of digits
|
6, // number of digits
|
||||||
30, // period in seconds
|
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.
|
// Retrieve the current TOTP secret for the user.
|
||||||
@@ -124,8 +142,6 @@ if (!$totpSecret) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine the otpauth URL to use.
|
// 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();
|
$globalOtpauthUrl = getGlobalOtpauthUrl();
|
||||||
if (!empty($globalOtpauthUrl)) {
|
if (!empty($globalOtpauthUrl)) {
|
||||||
$label = "FileRise:" . $username;
|
$label = "FileRise:" . $username;
|
||||||
|
|||||||
Reference in New Issue
Block a user