ensure consistent session behavior
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
- Updated robthree/twofactorauth to v3 and endroid/qr-code to v5
|
- Updated robthree/twofactorauth to v3 and endroid/qr-code to v5
|
||||||
- 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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
3
auth.php
3
auth.php
@@ -5,7 +5,6 @@ require_once 'config.php';
|
|||||||
use RobThree\Auth\Algorithm;
|
use RobThree\Auth\Algorithm;
|
||||||
use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider;
|
use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider;
|
||||||
|
|
||||||
// Only send the Content-Type header; CORS and related headers are handled via .htaccess.
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// Global exception handler: logs errors and returns a generic error message.
|
// Global exception handler: logs errors and returns a generic error message.
|
||||||
@@ -180,7 +179,7 @@ if (!$username || !$password) {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $username)) {
|
if (!preg_match(REGEX_USER, $username)) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
echo json_encode(["error" => "Invalid username format. Only letters, numbers, underscores, dashes, and spaces are allowed."]);
|
echo json_encode(["error" => "Invalid username format. Only letters, numbers, underscores, dashes, and spaces are allowed."]);
|
||||||
exit();
|
exit();
|
||||||
|
|||||||
15
config.php
15
config.php
@@ -1,5 +1,20 @@
|
|||||||
<?php
|
<?php
|
||||||
// config.php
|
// config.php
|
||||||
|
header("Cache-Control: no-cache, must-revalidate");
|
||||||
|
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");
|
||||||
|
header("Pragma: no-cache");
|
||||||
|
header("Expires: 0");
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
// Security headers
|
||||||
|
header("X-Content-Type-Options: nosniff");
|
||||||
|
header("X-Frame-Options: SAMEORIGIN");
|
||||||
|
header("Referrer-Policy: no-referrer-when-downgrade");
|
||||||
|
// Only include Strict-Transport-Security if you are using HTTPS
|
||||||
|
if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
|
||||||
|
header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload");
|
||||||
|
}
|
||||||
|
header("Permissions-Policy: geolocation=(), microphone=(), camera=()");
|
||||||
|
header("X-XSS-Protection: 1; mode=block");
|
||||||
|
|
||||||
// Define constants.
|
// Define constants.
|
||||||
define('UPLOAD_DIR', '/var/www/uploads/');
|
define('UPLOAD_DIR', '/var/www/uploads/');
|
||||||
|
|||||||
@@ -80,10 +80,6 @@ if (in_array($ext, ['jpg','jpeg','png','gif','bmp','webp','svg','ico'])) {
|
|||||||
}
|
}
|
||||||
header('Content-Length: ' . filesize($realFilePath));
|
header('Content-Length: ' . filesize($realFilePath));
|
||||||
|
|
||||||
// Disable caching.
|
|
||||||
header('Cache-Control: no-store, no-cache, must-revalidate');
|
|
||||||
header('Pragma: no-cache');
|
|
||||||
|
|
||||||
readfile($realFilePath);
|
readfile($realFilePath);
|
||||||
exit;
|
exit;
|
||||||
?>
|
?>
|
||||||
@@ -79,10 +79,6 @@ if (in_array($ext, ['jpg','jpeg','png','gif','bmp','webp','svg','ico'])) {
|
|||||||
header('Content-Disposition: attachment; filename="' . basename($realFilePath) . '"');
|
header('Content-Disposition: attachment; filename="' . basename($realFilePath) . '"');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable caching.
|
|
||||||
header("Cache-Control: no-store, no-cache, must-revalidate");
|
|
||||||
header("Pragma: no-cache");
|
|
||||||
|
|
||||||
// Read and output the file.
|
// Read and output the file.
|
||||||
readfile($realFilePath);
|
readfile($realFilePath);
|
||||||
exit;
|
exit;
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once 'config.php';
|
require_once 'config.php';
|
||||||
header("Cache-Control: no-cache, no-store, must-revalidate");
|
|
||||||
header("Pragma: no-cache");
|
|
||||||
header("Expires: 0");
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
// Ensure user is authenticated
|
// Ensure user is authenticated
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ require_once 'config.php';
|
|||||||
|
|
||||||
// Set security and content headers
|
// Set security and content headers
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
header('X-Content-Type-Options: nosniff');
|
|
||||||
header('Cache-Control: no-cache, must-revalidate');
|
|
||||||
|
|
||||||
$metadataPath = META_DIR . 'createdTags.json';
|
$metadataPath = META_DIR . 'createdTags.json';
|
||||||
|
|
||||||
|
|||||||
17
js/main.js
17
js/main.js
@@ -12,7 +12,8 @@ import { initFileActions, renameFile, openDownloadModal, confirmSingleDownload }
|
|||||||
import { editFile, saveFile } from './fileEditor.js';
|
import { editFile, saveFile } from './fileEditor.js';
|
||||||
import { t, applyTranslations, setLocale } from './i18n.js';
|
import { t, applyTranslations, setLocale } from './i18n.js';
|
||||||
|
|
||||||
function loadCsrfTokenWithRetry(retries = 3, delay = 1000) {
|
// Remove the retry logic version and just use loadCsrfToken directly:
|
||||||
|
function loadCsrfToken() {
|
||||||
return fetch('token.php', { credentials: 'include' })
|
return fetch('token.php', { credentials: 'include' })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -21,11 +22,9 @@ function loadCsrfTokenWithRetry(retries = 3, delay = 1000) {
|
|||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
// Set global variables.
|
|
||||||
window.csrfToken = data.csrf_token;
|
window.csrfToken = data.csrf_token;
|
||||||
window.SHARE_URL = data.share_url;
|
window.SHARE_URL = data.share_url;
|
||||||
|
|
||||||
// Update (or create) the CSRF meta tag.
|
|
||||||
let metaCSRF = document.querySelector('meta[name="csrf-token"]');
|
let metaCSRF = document.querySelector('meta[name="csrf-token"]');
|
||||||
if (!metaCSRF) {
|
if (!metaCSRF) {
|
||||||
metaCSRF = document.createElement('meta');
|
metaCSRF = document.createElement('meta');
|
||||||
@@ -34,7 +33,6 @@ function loadCsrfTokenWithRetry(retries = 3, delay = 1000) {
|
|||||||
}
|
}
|
||||||
metaCSRF.setAttribute('content', data.csrf_token);
|
metaCSRF.setAttribute('content', data.csrf_token);
|
||||||
|
|
||||||
// Update (or create) the share URL meta tag.
|
|
||||||
let metaShare = document.querySelector('meta[name="share-url"]');
|
let metaShare = document.querySelector('meta[name="share-url"]');
|
||||||
if (!metaShare) {
|
if (!metaShare) {
|
||||||
metaShare = document.createElement('meta');
|
metaShare = document.createElement('meta');
|
||||||
@@ -44,15 +42,6 @@ function loadCsrfTokenWithRetry(retries = 3, delay = 1000) {
|
|||||||
metaShare.setAttribute('content', data.share_url);
|
metaShare.setAttribute('content', data.share_url);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
if (retries > 0) {
|
|
||||||
console.warn(`CSRF token load failed. Retrying in ${delay}ms... (${retries} retries left)`, error);
|
|
||||||
return new Promise(resolve => setTimeout(resolve, delay))
|
|
||||||
.then(() => loadCsrfTokenWithRetry(retries - 1, delay * 2));
|
|
||||||
}
|
|
||||||
console.error("Failed to load CSRF token after retries.", error);
|
|
||||||
throw error;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +67,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
// Apply the translations to update the UI
|
// Apply the translations to update the UI
|
||||||
applyTranslations();
|
applyTranslations();
|
||||||
// First, load the CSRF token (with retry).
|
// First, load the CSRF token (with retry).
|
||||||
loadCsrfTokenWithRetry().then(() => {
|
loadCsrfToken().then(() => {
|
||||||
// Once CSRF token is loaded, initialize authentication.
|
// Once CSRF token is loaded, initialize authentication.
|
||||||
initAuth();
|
initAuth();
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once 'config.php';
|
require_once 'config.php';
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
header("Cache-Control: no-cache, no-store, must-revalidate");
|
|
||||||
header("Pragma: no-cache");
|
|
||||||
header("Expires: 0");
|
|
||||||
|
|
||||||
// --- CSRF Protection ---
|
// --- CSRF Protection ---
|
||||||
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
require_once 'config.php';
|
require_once 'config.php';
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
header("Cache-Control: no-cache, no-store, must-revalidate");
|
|
||||||
header("Pragma: no-cache");
|
|
||||||
header("Expires: 0");
|
|
||||||
|
|
||||||
// Ensure user is authenticated
|
// Ensure user is authenticated
|
||||||
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CSRF Protection: validate token from header.
|
// CSRF Protection: validate token from header.
|
||||||
$headers = getallheaders();
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
if (!isset($headers['X-CSRF-Token']) || $headers['X-CSRF-Token'] !== $_SESSION['csrf_token']) {
|
$csrfHeader = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
|
||||||
echo json_encode(["error" => "Invalid CSRF token."]);
|
|
||||||
http_response_code(403);
|
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
||||||
exit;
|
respond('error', 403, 'Invalid CSRF token');
|
||||||
}
|
}
|
||||||
|
|
||||||
$username = $_SESSION['username'] ?? '';
|
$username = $_SESSION['username'] ?? '';
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify CSRF token from request headers.
|
// Verify CSRF token from request headers.
|
||||||
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
|
$csrfHeader = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
|
||||||
|
|
||||||
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
||||||
http_response_code(403);
|
respond('error', 403, 'Invalid CSRF token');
|
||||||
echo json_encode(["error" => "Invalid CSRF token"]);
|
|
||||||
exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ——— 2) CSRF check ———
|
// ——— 2) CSRF check ———
|
||||||
if (empty($_SERVER['HTTP_X_CSRF_TOKEN'])
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
|| $_SERVER['HTTP_X_CSRF_TOKEN'] !== ($_SESSION['csrf_token'] ?? '')) {
|
$csrfHeader = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
|
||||||
http_response_code(403);
|
|
||||||
error_log("Invalid CSRF token on recovery for IP {$_SERVER['REMOTE_ADDR']}");
|
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
||||||
exit(json_encode(['status'=>'error','message'=>'Invalid CSRF token']));
|
respond('error', 403, 'Invalid CSRF token');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ——— 3) Identify user to recover ———
|
// ——— 3) Identify user to recover ———
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2) CSRF check
|
// 2) CSRF check
|
||||||
if (empty($_SERVER['HTTP_X_CSRF_TOKEN'])
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
|| $_SERVER['HTTP_X_CSRF_TOKEN'] !== ($_SESSION['csrf_token'] ?? '')) {
|
$csrfHeader = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
|
||||||
http_response_code(403);
|
|
||||||
error_log("totp_saveCode: invalid CSRF token from IP {$_SERVER['REMOTE_ADDR']}");
|
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
||||||
exit(json_encode(['status'=>'error','message'=>'Invalid CSRF token']));
|
respond('error', 403, 'Invalid CSRF token');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3) Must be logged in
|
// 3) Must be logged in
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify CSRF token provided as a GET parameter.
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
if (!isset($_GET['csrf']) || $_GET['csrf'] !== $_SESSION['csrf_token']) {
|
$csrfHeader = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
|
||||||
http_response_code(403);
|
|
||||||
exit;
|
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
||||||
|
respond('error', 403, 'Invalid CSRF token');
|
||||||
}
|
}
|
||||||
|
|
||||||
$username = $_SESSION['username'] ?? '';
|
$username = $_SESSION['username'] ?? '';
|
||||||
|
|||||||
@@ -57,8 +57,9 @@ try {
|
|||||||
respond('error', 403, 'Not authenticated');
|
respond('error', 403, 'Not authenticated');
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSRF check
|
$headers = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
|
$csrfHeader = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
|
||||||
|
|
||||||
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
||||||
respond('error', 403, 'Invalid CSRF token');
|
respond('error', 403, 'Invalid CSRF token');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user