";
+ });
+}
\ No newline at end of file
diff --git a/changePassword.php b/changePassword.php
index 0834d46..945f426 100644
--- a/changePassword.php
+++ b/changePassword.php
@@ -1,10 +1,8 @@
"Unauthorized"]);
exit;
diff --git a/checkAuth.php b/checkAuth.php
index 683d427..a324048 100644
--- a/checkAuth.php
+++ b/checkAuth.php
@@ -1,20 +1,41 @@
true]);
exit();
}
+// Check session authentication.
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
echo json_encode(["authenticated" => false]);
- exit;
+ exit();
}
+/**
+ * Helper function to get a user's role from users.txt.
+ * Returns the role as a string (e.g. "1") or null if not found.
+ */
+function getUserRole($username) {
+ global $usersFile;
+ if (file_exists($usersFile)) {
+ $lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ foreach ($lines as $line) {
+ $parts = explode(":", trim($line));
+ if (count($parts) >= 3 && $parts[0] === $username) {
+ return trim($parts[2]);
+ }
+ }
+ }
+ return null;
+}
+
+// Determine if TOTP is enabled by checking users.txt.
$totp_enabled = false;
$username = $_SESSION['username'] ?? '';
if ($username) {
@@ -31,9 +52,19 @@ if ($username) {
}
}
-echo json_encode([
+// Use getUserRole() to determine admin status.
+// We cast the role to an integer so that "1" (string) is treated as true.
+$userRole = getUserRole($username);
+$isAdmin = ((int)$userRole === 1);
+
+// Build and return the JSON response.
+$response = [
"authenticated" => true,
- "isAdmin" => isset($_SESSION["isAdmin"]) ? $_SESSION["isAdmin"] : false,
- "totp_enabled" => $totp_enabled
-]);
+ "isAdmin" => $isAdmin,
+ "totp_enabled" => $totp_enabled,
+ "username" => $username,
+ "folderOnly" => isset($_SESSION["folderOnly"]) ? $_SESSION["folderOnly"] : false
+];
+
+echo json_encode($response);
?>
\ No newline at end of file
diff --git a/config.php b/config.php
index caa59b7..721c004 100644
--- a/config.php
+++ b/config.php
@@ -21,7 +21,8 @@ date_default_timezone_set(TIMEZONE);
* @param string $encryptionKey The encryption key.
* @return string Base64-encoded string containing IV and ciphertext.
*/
-function encryptData($data, $encryptionKey) {
+function encryptData($data, $encryptionKey)
+{
$cipher = 'AES-256-CBC';
$ivlen = openssl_cipher_iv_length($cipher);
$iv = openssl_random_pseudo_bytes($ivlen);
@@ -36,7 +37,8 @@ function encryptData($data, $encryptionKey) {
* @param string $encryptionKey The encryption key.
* @return string|false The decrypted plaintext or false on failure.
*/
-function decryptData($encryptedData, $encryptionKey) {
+function decryptData($encryptedData, $encryptionKey)
+{
$cipher = 'AES-256-CBC';
$data = base64_decode($encryptedData);
$ivlen = openssl_cipher_iv_length($cipher);
@@ -51,6 +53,40 @@ if (!$encryptionKey) {
die('Encryption key for persistent tokens is not set.');
}
+function loadUserPermissions($username)
+{
+ global $encryptionKey; // Ensure $encryptionKey is available
+ $permissionsFile = USERS_DIR . 'userPermissions.json';
+
+ if (file_exists($permissionsFile)) {
+ $content = file_get_contents($permissionsFile);
+
+ // Try to decrypt the content.
+ $decryptedContent = decryptData($content, $encryptionKey);
+ if ($decryptedContent !== false) {
+ $permissions = json_decode($decryptedContent, true);
+ } else {
+ $permissions = json_decode($content, true);
+ }
+
+ if (!is_array($permissions)) {
+ } else {
+ }
+
+ if (is_array($permissions) && array_key_exists($username, $permissions)) {
+ $result = $permissions[$username];
+ if (empty($result)) {
+ return false;
+ }
+ return $result;
+ } else {
+ }
+ } else {
+ error_log("loadUserPermissions: Permissions file not found: $permissionsFile");
+ }
+ return false; // Return false if no permissions found.
+}
+
// Determine whether HTTPS is used.
$envSecure = getenv('SECURE');
if ($envSecure !== false) {
@@ -67,9 +103,12 @@ $cookieParams = [
'httponly' => true,
'samesite' => 'Lax'
];
-session_set_cookie_params($cookieParams);
-ini_set('session.gc_maxlifetime', 7200);
-session_start();
+
+if (session_status() === PHP_SESSION_NONE) {
+ session_set_cookie_params($cookieParams);
+ ini_set('session.gc_maxlifetime', 7200);
+ session_start();
+}
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
@@ -92,6 +131,8 @@ if (!isset($_SESSION["authenticated"]) && isset($_COOKIE['remember_me_token']))
if ($tokenData['expiry'] >= time()) {
$_SESSION["authenticated"] = true;
$_SESSION["username"] = $tokenData["username"];
+ // IMPORTANT: Set the folderOnly flag here for auto-login.
+ $_SESSION["folderOnly"] = loadFolderPermission($tokenData["username"]);
} else {
unset($persistentTokens[$_COOKIE['remember_me_token']]);
$newEncryptedContent = encryptData(json_encode($persistentTokens, JSON_PRETTY_PRINT), $encryptionKey);
@@ -111,4 +152,3 @@ if (strpos(BASE_URL, 'yourwebsite') !== false) {
$defaultShareUrl = rtrim(BASE_URL, '/') . "/share.php";
}
define('SHARE_URL', getenv('SHARE_URL') ? getenv('SHARE_URL') : $defaultShareUrl);
-?>
\ No newline at end of file
diff --git a/copyFiles.php b/copyFiles.php
index 3a3f455..8264803 100644
--- a/copyFiles.php
+++ b/copyFiles.php
@@ -18,6 +18,17 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
exit;
}
+$userPermissions = loadUserPermissions($username);
+// Check if the user is read-only. (Assuming that if readOnly is true, deletion is disallowed.)
+$username = $_SESSION['username'] ?? '';
+if ($username) {
+ $userPermissions = loadUserPermissions($username);
+ if (isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
+ echo json_encode(["error" => "Read-only users are not allowed to copy files."]);
+ exit();
+ }
+}
+
$data = json_decode(file_get_contents("php://input"), true);
if (
!$data ||
diff --git a/createFolder.php b/createFolder.php
index 0325793..449a21e 100644
--- a/createFolder.php
+++ b/createFolder.php
@@ -1,5 +1,5 @@
"Read-only users are not allowed to create folders."]);
+ exit();
+ }
+}
// Get the JSON input and decode it
$input = json_decode(file_get_contents('php://input'), true);
diff --git a/deleteFiles.php b/deleteFiles.php
index a6f338f..70dfbd9 100644
--- a/deleteFiles.php
+++ b/deleteFiles.php
@@ -19,6 +19,20 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
exit;
}
+// Define $username first.
+$username = $_SESSION['username'] ?? '';
+
+// Now load the user's permissions.
+$userPermissions = loadUserPermissions($username);
+
+// Check if the user is read-only.
+if ($username) {
+ if (isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
+ echo json_encode(["error" => "Read-only users are not allowed to delete files."]);
+ exit();
+ }
+}
+
// --- Setup Trash Folder & Metadata ---
$trashDir = rtrim(TRASH_DIR, '/\\') . DIRECTORY_SEPARATOR;
if (!file_exists($trashDir)) {
diff --git a/deleteFolder.php b/deleteFolder.php
index b0ed663..818c4a7 100644
--- a/deleteFolder.php
+++ b/deleteFolder.php
@@ -1,5 +1,5 @@
"Read-only users are not allowed to delete folders."]);
+ exit();
+ }
+}
// Get the JSON input and decode it
$input = json_decode(file_get_contents('php://input'), true);
diff --git a/deleteTrashFiles.php b/deleteTrashFiles.php
index 800c0b6..1727bfc 100644
--- a/deleteTrashFiles.php
+++ b/deleteTrashFiles.php
@@ -1,5 +1,4 @@
"Unauthorized"]);
exit;
}
+$userPermissions = loadUserPermissions($username);
+// Check if the user is read-only. (Assuming that if readOnly is true, deletion is disallowed.)
+$username = $_SESSION['username'] ?? '';
+if ($username) {
+ $userPermissions = loadUserPermissions($username);
+ if (isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
+ echo json_encode(["error" => "Read-only users are not allowed to extract zip files"]);
+ exit();
+ }
+}
// Read and decode the JSON input.
$rawData = file_get_contents("php://input");
diff --git a/fileTags.js b/fileTags.js
index f8544fa..2fbadd5 100644
--- a/fileTags.js
+++ b/fileTags.js
@@ -305,7 +305,13 @@ if (localStorage.getItem('globalTags')) {
// New function to load global tags from the server's persistent JSON.
export function loadGlobalTags() {
fetch("metadata/createdTags.json", { credentials: "include" })
- .then(response => response.json())
+ .then(response => {
+ if (!response.ok) {
+ // If the file doesn't exist, assume there are no global tags.
+ return [];
+ }
+ return response.json();
+ })
.then(data => {
window.globalTags = data;
localStorage.setItem('globalTags', JSON.stringify(window.globalTags));
diff --git a/folderManager.js b/folderManager.js
index f357c56..1fadeed 100644
--- a/folderManager.js
+++ b/folderManager.js
@@ -3,9 +3,9 @@
import { loadFileList } from './fileManager.js';
import { showToast, escapeHTML, attachEnterKeyListener } from './domUtils.js';
-// ----------------------
-// Helper Functions (Data/State)
-// ----------------------
+/* ----------------------
+ Helper Functions (Data/State)
+----------------------*/
// Formats a folder name for display (e.g. adding indentations).
export function formatFolderName(folder) {
@@ -26,7 +26,6 @@ export function formatFolderName(folder) {
function buildFolderTree(folders) {
const tree = {};
folders.forEach(folderPath => {
- // Ensure folderPath is a string
if (typeof folderPath !== "string") return;
const parts = folderPath.split('/');
let current = tree;
@@ -40,9 +39,9 @@ function buildFolderTree(folders) {
return tree;
}
-// ----------------------
-// Folder Tree State (Save/Load)
-// ----------------------
+/* ----------------------
+ Folder Tree State (Save/Load)
+----------------------*/
function loadFolderTreeState() {
const state = localStorage.getItem("folderTreeState");
return state ? JSON.parse(state) : {};
@@ -59,58 +58,41 @@ function getParentFolder(folder) {
return lastSlash === -1 ? "root" : folder.substring(0, lastSlash);
}
-// ----------------------
-// Breadcrumb Functions
-// ----------------------
-// Render breadcrumb for a normalized folder path.
+/* ----------------------
+ Breadcrumb Functions
+----------------------*/
function renderBreadcrumb(normalizedFolder) {
- if (normalizedFolder === "root") {
- return `Root`;
- }
+ if (!normalizedFolder || normalizedFolder === "") return "";
const parts = normalizedFolder.split("/");
let breadcrumbItems = [];
- // Always start with "Root".
- breadcrumbItems.push(`Root`);
- let cumulative = "";
- parts.forEach((part, index) => {
- cumulative = index === 0 ? part : cumulative + "/" + part;
+ // Use the first segment as the root.
+ breadcrumbItems.push(`${escapeHTML(parts[0])}`);
+ let cumulative = parts[0];
+ parts.slice(1).forEach(part => {
+ cumulative += "/" + part;
breadcrumbItems.push(` / `);
breadcrumbItems.push(`${escapeHTML(part)}`);
});
return breadcrumbItems.join('');
}
-// Bind click and drag-and-drop events to breadcrumb links.
function bindBreadcrumbEvents() {
const breadcrumbLinks = document.querySelectorAll(".breadcrumb-link");
breadcrumbLinks.forEach(link => {
- // Click event for navigation.
link.addEventListener("click", function (e) {
e.stopPropagation();
let folder = this.getAttribute("data-folder");
window.currentFolder = folder;
localStorage.setItem("lastOpenedFolder", folder);
const titleEl = document.getElementById("fileListTitle");
- if (folder === "root") {
- titleEl.innerHTML = "Files in (" + renderBreadcrumb("root") + ")";
- } else {
- titleEl.innerHTML = "Files in (" + renderBreadcrumb(folder) + ")";
- }
- // Expand the folder tree to ensure the target is visible.
+ titleEl.innerHTML = "Files in (" + renderBreadcrumb(folder) + ")";
expandTreePath(folder);
- // Update folder tree selection.
document.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
const targetOption = document.querySelector(`.folder-option[data-folder="${folder}"]`);
- if (targetOption) {
- targetOption.classList.add("selected");
- }
- // Load the file list.
+ if (targetOption) targetOption.classList.add("selected");
loadFileList(folder);
- // Re-bind breadcrumb events to ensure all links remain active.
bindBreadcrumbEvents();
});
-
- // Drag-and-drop events.
link.addEventListener("dragover", function (e) {
e.preventDefault();
this.classList.add("drop-hover");
@@ -144,36 +126,73 @@ function bindBreadcrumbEvents() {
destination: dropFolder
})
})
- .then(response => response.json())
- .then(data => {
- if (data.success) {
- showToast(`File(s) moved successfully to ${dropFolder}!`);
- loadFileList(dragData.sourceFolder);
- } else {
- showToast("Error moving files: " + (data.error || "Unknown error"));
- }
- })
- .catch(error => {
- console.error("Error moving files via drop on breadcrumb:", error);
- showToast("Error moving files.");
- });
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showToast(`File(s) moved successfully to ${dropFolder}!`);
+ loadFileList(dragData.sourceFolder);
+ } else {
+ showToast("Error moving files: " + (data.error || "Unknown error"));
+ }
+ })
+ .catch(error => {
+ console.error("Error moving files via drop on breadcrumb:", error);
+ showToast("Error moving files.");
+ });
});
});
}
-// ----------------------
-// DOM Building Functions for Folder Tree
-// ----------------------
+/* ----------------------
+ Check Current User's Folder-Only Permission
+----------------------*/
+// This function uses localStorage values (set during login) to determine if the current user is restricted.
+// If folderOnly is "true", then the personal folder (i.e. username) is forced as the effective root.
+function checkUserFolderPermission() {
+ const username = localStorage.getItem("username");
+ console.log("checkUserFolderPermission: username =", username);
+ if (!username) {
+ console.warn("No username in localStorage; skipping getUserPermissions fetch.");
+ return Promise.resolve(false);
+ }
+ if (localStorage.getItem("folderOnly") === "true") {
+ window.userFolderOnly = true;
+ console.log("checkUserFolderPermission: using localStorage.folderOnly = true");
+ localStorage.setItem("lastOpenedFolder", username);
+ window.currentFolder = username;
+ return Promise.resolve(true);
+ }
+ return fetch("getUserPermissions.php", { credentials: "include" })
+ .then(response => response.json())
+ .then(permissionsData => {
+ console.log("checkUserFolderPermission: permissionsData =", permissionsData);
+ if (permissionsData && permissionsData[username] && permissionsData[username].folderOnly) {
+ window.userFolderOnly = true;
+ localStorage.setItem("folderOnly", "true");
+ localStorage.setItem("lastOpenedFolder", username);
+ window.currentFolder = username;
+ return true;
+ } else {
+ window.userFolderOnly = false;
+ localStorage.setItem("folderOnly", "false");
+ return false;
+ }
+ })
+ .catch(err => {
+ console.error("Error fetching user permissions:", err);
+ window.userFolderOnly = false;
+ return false;
+ });
+}
-// Recursively builds HTML for the folder tree as nested
elements.
+/* ----------------------
+ DOM Building Functions for Folder Tree
+----------------------*/
function renderFolderTree(tree, parentPath = "", defaultDisplay = "block") {
const state = loadFolderTreeState();
let html = `
`;
for (const folder in tree) {
- // Skip the trash folder (case-insensitive)
- if (folder.toLowerCase() === "trash") {
- continue;
- }
+ if (folder.toLowerCase() === "trash") continue;
const fullPath = parentPath ? parentPath + "/" + folder : folder;
const hasChildren = Object.keys(tree[folder]).length > 0;
const displayState = state[fullPath] !== undefined ? state[fullPath] : defaultDisplay;
@@ -194,7 +213,6 @@ function renderFolderTree(tree, parentPath = "", defaultDisplay = "block") {
return html;
}
-// Expands the folder tree along a given normalized path.
function expandTreePath(path) {
const parts = path.split("/");
let cumulative = "";
@@ -219,9 +237,9 @@ function expandTreePath(path) {
});
}
-// ----------------------
-// Drag & Drop Support for Folder Tree Nodes
-// ----------------------
+/* ----------------------
+ Drag & Drop Support for Folder Tree Nodes
+----------------------*/
function folderDragOverHandler(event) {
event.preventDefault();
event.currentTarget.classList.add("drop-hover");
@@ -272,90 +290,110 @@ function folderDropHandler(event) {
});
}
-// ----------------------
-// Main Folder Tree Rendering and Event Binding
-// ----------------------
+/* ----------------------
+ Main Folder Tree Rendering and Event Binding
+----------------------*/
export async function loadFolderTree(selectedFolder) {
try {
- const response = await fetch('getFolderList.php');
+ // Check if the user has folder-only permission.
+ await checkUserFolderPermission();
+
+ // Determine effective root folder.
+ const username = localStorage.getItem("username") || "root";
+ let effectiveRoot = "root";
+ let effectiveLabel = "(Root)";
+ if (window.userFolderOnly) {
+ effectiveRoot = username; // Use the username as the personal root.
+ effectiveLabel = `(Root)`;
+ // Force override of any saved folder.
+ localStorage.setItem("lastOpenedFolder", username);
+ window.currentFolder = username;
+ } else {
+ window.currentFolder = localStorage.getItem("lastOpenedFolder") || "root";
+ }
+
+ // Build fetch URL.
+ let fetchUrl = 'getFolderList.php';
+ if (window.userFolderOnly) {
+ fetchUrl += '?restricted=1';
+ }
+ console.log("Fetching folder list from:", fetchUrl);
+
+ // Fetch folder list from the server.
+ const response = await fetch(fetchUrl);
if (response.status === 401) {
console.error("Unauthorized: Please log in to view folders.");
showToast("Session expired. Please log in again.");
window.location.href = "logout.php";
return;
}
- let folders = await response.json();
-
- // If returned items are objects (with a "folder" property), extract folder paths.
- if (Array.isArray(folders) && folders.length && typeof folders[0] === "object" && folders[0].folder) {
- folders = folders.map(item => item.folder);
+ let folderData = await response.json();
+ console.log("Folder data received:", folderData);
+ let folders = [];
+ if (Array.isArray(folderData) && folderData.length && typeof folderData[0] === "object" && folderData[0].folder) {
+ folders = folderData.map(item => item.folder);
+ } else if (Array.isArray(folderData)) {
+ folders = folderData;
}
- // Filter out duplicate "root" entries if present.
- folders = folders.filter(folder => folder !== "root");
-
- if (!Array.isArray(folders)) {
- console.error("Folder list response is not an array:", folders);
- return;
+
+ // Remove any global "root" entry.
+ folders = folders.filter(folder => folder.toLowerCase() !== "root");
+
+ // If restricted, filter folders: keep only those that start with effectiveRoot + "/" (do not include effectiveRoot itself).
+ if (window.userFolderOnly && effectiveRoot !== "root") {
+ folders = folders.filter(folder => folder.startsWith(effectiveRoot + "/"));
+ // Force current folder to be the effective root.
+ localStorage.setItem("lastOpenedFolder", effectiveRoot);
+ window.currentFolder = effectiveRoot;
}
-
+
+ localStorage.setItem("lastOpenedFolder", window.currentFolder);
+
+ // Render the folder tree.
const container = document.getElementById("folderTreeContainer");
if (!container) {
console.error("Folder tree container not found.");
return;
}
-
+
let html = `
- [-]
- (Root)
-
`;
+ [-]
+ ${effectiveLabel}
+ `;
if (folders.length > 0) {
const tree = buildFolderTree(folders);
html += renderFolderTree(tree, "", "block");
}
container.innerHTML = html;
-
- // Attach drag-and-drop event listeners to folder nodes.
+
+ // Attach drag/drop event listeners.
container.querySelectorAll(".folder-option").forEach(el => {
el.addEventListener("dragover", folderDragOverHandler);
el.addEventListener("dragleave", folderDragLeaveHandler);
el.addEventListener("drop", folderDropHandler);
});
-
- // Determine current folder (normalized).
+
if (selectedFolder) {
window.currentFolder = selectedFolder;
- } else {
- window.currentFolder = localStorage.getItem("lastOpenedFolder") || "root";
}
localStorage.setItem("lastOpenedFolder", window.currentFolder);
-
- // Update file list title using breadcrumb.
+
const titleEl = document.getElementById("fileListTitle");
- if (window.currentFolder === "root") {
- titleEl.innerHTML = "Files in (" + renderBreadcrumb("root") + ")";
- } else {
- titleEl.innerHTML = "Files in (" + renderBreadcrumb(window.currentFolder) + ")";
- }
- // Bind breadcrumb events (click and drag/drop).
+ titleEl.innerHTML = "Files in (" + renderBreadcrumb(window.currentFolder) + ")";
bindBreadcrumbEvents();
-
- // Load file list.
loadFileList(window.currentFolder);
-
- // Expand tree to current folder.
+
const folderState = loadFolderTreeState();
- if (window.currentFolder !== "root" && folderState[window.currentFolder] !== "none") {
+ if (window.currentFolder !== effectiveRoot && folderState[window.currentFolder] !== "none") {
expandTreePath(window.currentFolder);
}
-
- // Highlight current folder in folder tree.
+
const selectedEl = container.querySelector(`.folder-option[data-folder="${window.currentFolder}"]`);
if (selectedEl) {
container.querySelectorAll(".folder-option").forEach(item => item.classList.remove("selected"));
selectedEl.classList.add("selected");
}
-
- // Event binding for folder selection in folder tree.
+
container.querySelectorAll(".folder-option").forEach(el => {
el.addEventListener("click", function (e) {
e.stopPropagation();
@@ -365,18 +403,12 @@ export async function loadFolderTree(selectedFolder) {
window.currentFolder = selected;
localStorage.setItem("lastOpenedFolder", selected);
const titleEl = document.getElementById("fileListTitle");
- if (selected === "root") {
- titleEl.innerHTML = "Files in (" + renderBreadcrumb("root") + ")";
- } else {
- titleEl.innerHTML = "Files in (" + renderBreadcrumb(selected) + ")";
- }
- // Re-bind breadcrumb events so the new breadcrumb is clickable.
+ titleEl.innerHTML = "Files in (" + renderBreadcrumb(selected) + ")";
bindBreadcrumbEvents();
loadFileList(selected);
});
});
-
- // Event binding for toggling folders.
+
const rootToggle = container.querySelector("#rootRow .folder-toggle");
if (rootToggle) {
rootToggle.addEventListener("click", function (e) {
@@ -388,18 +420,18 @@ export async function loadFolderTree(selectedFolder) {
nestedUl.classList.remove("collapsed");
nestedUl.classList.add("expanded");
this.innerHTML = "[" + '-' + "]";
- state["root"] = "block";
+ state[effectiveRoot] = "block";
} else {
nestedUl.classList.remove("expanded");
nestedUl.classList.add("collapsed");
this.textContent = "[+]";
- state["root"] = "none";
+ state[effectiveRoot] = "none";
}
saveFolderTreeState(state);
}
});
}
-
+
container.querySelectorAll(".folder-toggle").forEach(toggle => {
toggle.addEventListener("click", function (e) {
e.stopPropagation();
@@ -422,7 +454,7 @@ export async function loadFolderTree(selectedFolder) {
}
});
});
-
+
} catch (error) {
console.error("Error loading folder tree:", error);
}
@@ -433,12 +465,10 @@ export function loadFolderList(selectedFolder) {
loadFolderTree(selectedFolder);
}
-// ----------------------
-// Folder Management (Rename, Delete, Create)
-// ----------------------
-
+/* ----------------------
+ Folder Management (Rename, Delete, Create)
+----------------------*/
document.getElementById("renameFolderBtn").addEventListener("click", openRenameFolderModal);
-
document.getElementById("deleteFolderBtn").addEventListener("click", openDeleteFolderModal);
function openRenameFolderModal() {
@@ -450,7 +480,6 @@ function openRenameFolderModal() {
const parts = selectedFolder.split("/");
document.getElementById("newRenameFolderName").value = parts[parts.length - 1];
document.getElementById("renameFolderModal").style.display = "block";
- // Focus the input field after a short delay to ensure modal is visible.
setTimeout(() => {
const input = document.getElementById("newRenameFolderName");
input.focus();
@@ -601,8 +630,6 @@ document.getElementById("submitCreateFolder").addEventListener("click", function
});
// ---------- CONTEXT MENU SUPPORT FOR FOLDER MANAGER ----------
-
-// Function to display the custom context menu at (x, y) with given menu items.
function showFolderManagerContextMenu(x, y, menuItems) {
let menu = document.getElementById("folderManagerContextMenu");
if (!menu) {
@@ -614,8 +641,6 @@ function showFolderManagerContextMenu(x, y, menuItems) {
menu.style.zIndex = "9999";
document.body.appendChild(menu);
}
-
- // Set styles based on dark mode.
if (document.body.classList.contains("dark-mode")) {
menu.style.backgroundColor = "#2c2c2c";
menu.style.border = "1px solid #555";
@@ -625,8 +650,6 @@ function showFolderManagerContextMenu(x, y, menuItems) {
menu.style.border = "1px solid #ccc";
menu.style.color = "#000";
}
-
- // Clear previous items.
menu.innerHTML = "";
menuItems.forEach(item => {
const menuItem = document.createElement("div");
@@ -661,24 +684,16 @@ function hideFolderManagerContextMenu() {
}
}
-// Context menu handler for folder tree and breadcrumb items.
function folderManagerContextMenuHandler(e) {
e.preventDefault();
e.stopPropagation();
-
- // Get the closest folder element (either from the tree or breadcrumb).
const target = e.target.closest(".folder-option, .breadcrumb-link");
if (!target) return;
-
const folder = target.getAttribute("data-folder");
if (!folder) return;
-
- // Update current folder and highlight the selected element.
window.currentFolder = folder;
document.querySelectorAll(".folder-option, .breadcrumb-link").forEach(el => el.classList.remove("selected"));
target.classList.add("selected");
-
- // Build context menu items.
const menuItems = [
{
label: "Create Folder",
@@ -696,20 +711,15 @@ function folderManagerContextMenuHandler(e) {
action: () => { openDeleteFolderModal(); }
}
];
-
showFolderManagerContextMenu(e.pageX, e.pageY, menuItems);
}
-// Bind contextmenu events to folder tree and breadcrumb elements.
function bindFolderManagerContextMenu() {
- // Bind context menu to folder tree container.
const container = document.getElementById("folderTreeContainer");
if (container) {
container.removeEventListener("contextmenu", folderManagerContextMenuHandler);
container.addEventListener("contextmenu", folderManagerContextMenuHandler, false);
}
-
- // Bind context menu to breadcrumb links.
const breadcrumbNodes = document.querySelectorAll(".breadcrumb-link");
breadcrumbNodes.forEach(node => {
node.removeEventListener("contextmenu", folderManagerContextMenuHandler);
@@ -717,31 +727,23 @@ function bindFolderManagerContextMenu() {
});
}
-// Hide context menu when clicking elsewhere.
document.addEventListener("click", function () {
hideFolderManagerContextMenu();
});
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener("keydown", function (e) {
- // Skip if the user is typing in an input, textarea, or contentEditable element.
const tag = e.target.tagName.toLowerCase();
if (tag === "input" || tag === "textarea" || e.target.isContentEditable) {
return;
}
-
- // On macOS, "Delete" is typically reported as "Backspace" (keyCode 8)
if (e.key === "Delete" || e.key === "Backspace" || e.keyCode === 46 || e.keyCode === 8) {
- // Ensure a folder is selected and it isn't the root folder.
if (window.currentFolder && window.currentFolder !== "root") {
- // Prevent default (avoid navigating back on macOS).
e.preventDefault();
- // Call your existing folder delete function.
openDeleteFolderModal();
}
}
});
});
-// Call this binding function after rendering the folder tree and breadcrumbs.
bindFolderManagerContextMenu();
\ No newline at end of file
diff --git a/getConfig.php b/getConfig.php
index 11818ca..b8f5e3f 100644
--- a/getConfig.php
+++ b/getConfig.php
@@ -1,5 +1,5 @@
"Unauthorized"]);
+ exit;
+}
+
+$permissionsFile = USERS_DIR . "userPermissions.json";
+$permissionsArray = [];
+
+// Load permissions file if it exists.
+if (file_exists($permissionsFile)) {
+ $content = file_get_contents($permissionsFile);
+ // Attempt to decrypt the content.
+ $decryptedContent = decryptData($content, $encryptionKey);
+ if ($decryptedContent === false) {
+ // If decryption fails, assume the file is plain JSON.
+ $permissionsArray = json_decode($content, true);
+ } else {
+ $permissionsArray = json_decode($decryptedContent, true);
+ }
+ if (!is_array($permissionsArray)) {
+ $permissionsArray = [];
+ }
+}
+
+// If the user is an admin, return all permissions.
+if (isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true) {
+ echo json_encode($permissionsArray);
+ exit;
+}
+
+// Otherwise, return only the current user's permissions.
+$username = $_SESSION['username'] ?? '';
+foreach ($permissionsArray as $storedUsername => $data) {
+ if (strcasecmp($storedUsername, $username) === 0) {
+ echo json_encode($data);
+ exit;
+ }
+}
+
+// If no permissions are found for the current user, return an empty object.
+echo json_encode(new stdClass());
+?>
\ No newline at end of file
diff --git a/getUsers.php b/getUsers.php
index 0a2bc78..bc11f31 100644
--- a/getUsers.php
+++ b/getUsers.php
@@ -1,24 +1,31 @@
"Unauthorized"]);
exit;
}
+
$usersFile = USERS_DIR . USERS_FILE;
$users = [];
+
if (file_exists($usersFile)) {
$lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$parts = explode(':', trim($line));
if (count($parts) >= 3) {
- // Optionally, validate username format:
+ // Validate username format:
if (preg_match('/^[A-Za-z0-9_\- ]+$/', $parts[0])) {
- $users[] = ["username" => $parts[0]];
+ $users[] = [
+ "username" => $parts[0],
+ "role" => trim($parts[2])
+ ];
}
}
}
}
+
echo json_encode($users);
-?>
+?>
\ No newline at end of file
diff --git a/login_basic.php b/login_basic.php
index a91fa7c..389e146 100644
--- a/login_basic.php
+++ b/login_basic.php
@@ -1,14 +1,14 @@
= 3 && $parts[0] === $username) {
+ return trim($parts[2]);
+ }
+ }
+ }
+ return null;
+}
+
+// Add the loadFolderPermission function here:
+function loadFolderPermission($username) {
+ global $encryptionKey;
+ $permissionsFile = USERS_DIR . 'userPermissions.json';
+ if (file_exists($permissionsFile)) {
+ $content = file_get_contents($permissionsFile);
+ // Try to decrypt the content.
+ $decryptedContent = decryptData($content, $encryptionKey);
+ if ($decryptedContent !== false) {
+ $permissions = json_decode($decryptedContent, true);
+ } else {
+ $permissions = json_decode($content, true);
+ }
+ if (is_array($permissions)) {
+ // Use case-insensitive comparison.
+ foreach ($permissions as $storedUsername => $data) {
+ if (strcasecmp($storedUsername, $username) === 0 && isset($data['folderOnly'])) {
+ return (bool)$data['folderOnly'];
+ }
+ }
+ }
+ }
+ return false; // Default if not set.
+}
+
// Check if the user has sent HTTP Basic auth credentials.
if (!isset($_SERVER['PHP_AUTH_USER'])) {
header('WWW-Authenticate: Basic realm="FileRise Login"');
@@ -40,15 +81,18 @@ if (!isset($_SERVER['PHP_AUTH_USER'])) {
}
// Attempt authentication
- $userRole = authenticate($username, $password);
- if ($userRole !== false) {
- // Successful login
+ $roleFromAuth = authenticate($username, $password);
+ if ($roleFromAuth !== false) {
+ // Use getUserRole() to determine the user's role from the file
+ $actualRole = getUserRole($username);
session_regenerate_id(true);
$_SESSION["authenticated"] = true;
$_SESSION["username"] = $username;
- $_SESSION["isAdmin"] = ($userRole === "1"); // Assuming "1" means admin
+ $_SESSION["isAdmin"] = ($actualRole === "1");
+ // Set the folderOnly flag based on userPermissions.json.
+ $_SESSION["folderOnly"] = loadFolderPermission($username);
- // Redirect to the main page
+ // Redirect to the main page (or output JSON for testing)
header("Location: index.html");
exit;
} else {
diff --git a/logout.php b/logout.php
index 8ee4158..1572888 100644
--- a/logout.php
+++ b/logout.php
@@ -1,5 +1,5 @@
"Read-only users are not allowed to move files."]);
+ exit();
+ }
+}
$data = json_decode(file_get_contents("php://input"), true);
if (
diff --git a/removeUser.php b/removeUser.php
index 30f7893..49e0b5e 100644
--- a/removeUser.php
+++ b/removeUser.php
@@ -1,5 +1,5 @@
"User removed successfully"]);
?>
\ No newline at end of file
diff --git a/renameFile.php b/renameFile.php
index eac70ce..f9235aa 100644
--- a/renameFile.php
+++ b/renameFile.php
@@ -21,6 +21,16 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
http_response_code(401);
exit;
}
+$userPermissions = loadUserPermissions($username);
+// Check if the user is read-only. (Assuming that if readOnly is true, deletion is disallowed.)
+$username = $_SESSION['username'] ?? '';
+if ($username) {
+ $userPermissions = loadUserPermissions($username);
+ if (isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
+ echo json_encode(["error" => "Read-only users are not allowed to rename files."]);
+ exit();
+ }
+}
$data = json_decode(file_get_contents("php://input"), true);
if (!$data || !isset($data['folder']) || !isset($data['oldName']) || !isset($data['newName'])) {
diff --git a/renameFolder.php b/renameFolder.php
index cb47c12..edb417d 100644
--- a/renameFolder.php
+++ b/renameFolder.php
@@ -1,5 +1,5 @@
"Read-only users are not allowed to rename folders."]);
+ exit();
+ }
+}
// Get the JSON input and decode it
$input = json_decode(file_get_contents('php://input'), true);
diff --git a/saveFile.php b/saveFile.php
index 87e2525..7a0750e 100644
--- a/saveFile.php
+++ b/saveFile.php
@@ -18,6 +18,16 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
http_response_code(401);
exit;
}
+$userPermissions = loadUserPermissions($username);
+// Check if the user is read-only. (Assuming that if readOnly is true, deletion is disallowed.)
+$username = $_SESSION['username'] ?? '';
+if ($username) {
+ $userPermissions = loadUserPermissions($username);
+ if (isset($userPermissions['readOnly']) && $userPermissions['readOnly'] === true) {
+ echo json_encode(["error" => "Read-only users are not allowed to save files."]);
+ exit();
+ }
+}
$data = json_decode(file_get_contents("php://input"), true);
diff --git a/styles.css b/styles.css
index 049fb13..6bab5c0 100644
--- a/styles.css
+++ b/styles.css
@@ -1053,7 +1053,7 @@ body.dark-mode .custom-prev-next-btn:hover:not(:disabled) {
#customToast {
position: fixed;
- top: 20px;
+ bottom: 20px;
right: 20px;
background: #333;
color: #fff;
@@ -1068,7 +1068,7 @@ body.dark-mode .custom-prev-next-btn:hover:not(:disabled) {
}
#customToast.show {
- opacity: 1;
+ opacity: 0.7;
}
.button-wrap {
diff --git a/token.php b/token.php
index c66cb2f..7ab4d4d 100644
--- a/token.php
+++ b/token.php
@@ -1,5 +1,5 @@
$_SESSION['csrf_token'],
diff --git a/totp_disable.php b/totp_disable.php
new file mode 100644
index 0000000..7197db1
--- /dev/null
+++ b/totp_disable.php
@@ -0,0 +1,74 @@
+ "Not authenticated"]);
+ exit;
+}
+
+// Verify CSRF token from request headers.
+$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
+if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
+ http_response_code(403);
+ echo json_encode(["error" => "Invalid CSRF token"]);
+ exit;
+}
+
+header('Content-Type: application/json');
+
+$username = $_SESSION['username'] ?? '';
+if (empty($username)) {
+ http_response_code(400);
+ echo json_encode(["error" => "Username not found in session"]);
+ exit;
+}
+
+/**
+ * Removes the TOTP secret for the given user in users.txt.
+ *
+ * @param string $username
+ * @return bool True on success, false otherwise.
+ */
+function removeUserTOTPSecret($username) {
+ global $encryptionKey;
+ $usersFile = USERS_DIR . USERS_FILE;
+ if (!file_exists($usersFile)) {
+ return false;
+ }
+ $lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ $modified = false;
+ $newLines = [];
+ foreach ($lines as $line) {
+ $parts = explode(':', trim($line));
+ if (count($parts) < 3) {
+ $newLines[] = $line;
+ continue;
+ }
+ if ($parts[0] === $username) {
+ // Remove the TOTP secret by setting it to an empty string.
+ if (count($parts) >= 4) {
+ $parts[3] = "";
+ }
+ $modified = true;
+ $newLines[] = implode(":", $parts);
+ } else {
+ $newLines[] = $line;
+ }
+ }
+ if ($modified) {
+ file_put_contents($usersFile, implode(PHP_EOL, $newLines) . PHP_EOL, LOCK_EX);
+ }
+ return $modified;
+}
+
+if (removeUserTOTPSecret($username)) {
+ echo json_encode(["success" => true, "message" => "TOTP disabled successfully."]);
+} else {
+ http_response_code(500);
+ echo json_encode(["error" => "Failed to disable TOTP."]);
+}
+?>
\ No newline at end of file
diff --git a/totp_setup.php b/totp_setup.php
index 346ccdb..b5ad7fc 100644
--- a/totp_setup.php
+++ b/totp_setup.php
@@ -2,7 +2,7 @@
// totp_setup.php
require_once 'vendor/autoload.php';
-require 'config.php';
+require_once 'config.php';
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Writer\PngWriter;
@@ -12,10 +12,6 @@ use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
// ini_set('display_errors', 1);
// error_reporting(E_ALL);
-// Start the session and ensure the user is authenticated.
-if (session_status() !== PHP_SESSION_ACTIVE) {
- session_start();
-}
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
http_response_code(403);
exit;
diff --git a/totp_verify.php b/totp_verify.php
new file mode 100644
index 0000000..532aae6
--- /dev/null
+++ b/totp_verify.php
@@ -0,0 +1,84 @@
+ "Not authenticated"]);
+ exit;
+}
+
+// Verify CSRF token from request headers.
+$csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
+if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
+ http_response_code(403);
+ echo json_encode(["error" => "Invalid CSRF token"]);
+ exit;
+}
+
+// Ensure Content-Type is JSON.
+header('Content-Type: application/json');
+
+// Read and decode the JSON request body.
+$input = json_decode(file_get_contents("php://input"), true);
+if (!isset($input['totp_code']) || strlen(trim($input['totp_code'])) !== 6 || !ctype_digit(trim($input['totp_code']))) {
+ http_response_code(400);
+ echo json_encode(["error" => "A valid 6-digit TOTP code is required"]);
+ exit;
+}
+
+$totpCode = trim($input['totp_code']);
+$username = $_SESSION['username'] ?? '';
+if (empty($username)) {
+ http_response_code(400);
+ echo json_encode(["error" => "Username not found in session"]);
+ exit;
+}
+
+/**
+ * Retrieves the current user's TOTP secret from users.txt.
+ *
+ * @param string $username
+ * @return string|null The decrypted TOTP secret or null if not found.
+ */
+function getUserTOTPSecret($username) {
+ global $encryptionKey;
+ // Define the path to your users file.
+ $usersFile = USERS_DIR . USERS_FILE;
+ if (!file_exists($usersFile)) {
+ return null;
+ }
+ $lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+ foreach ($lines as $line) {
+ $parts = explode(':', trim($line));
+ // Assuming format: username:hashedPassword:role:encryptedTOTPSecret
+ if (count($parts) >= 4 && $parts[0] === $username && !empty($parts[3])) {
+ return decryptData($parts[3], $encryptionKey);
+ }
+ }
+ return null;
+}
+
+// Retrieve the user's TOTP secret.
+$totpSecret = getUserTOTPSecret($username);
+if (!$totpSecret) {
+ http_response_code(500);
+ echo json_encode(["error" => "TOTP secret not found. Please try setting up TOTP again."]);
+ exit;
+}
+
+// Verify the provided TOTP code.
+$tfa = new \RobThree\Auth\TwoFactorAuth('FileRise');
+if (!$tfa->verifyCode($totpSecret, $totpCode)) {
+ http_response_code(400);
+ echo json_encode(["error" => "Invalid TOTP code."]);
+ exit;
+}
+
+// If needed, you could update a flag or store the confirmation in the user record here.
+
+// Return a successful response.
+echo json_encode(["success" => true, "message" => "TOTP successfully verified."]);
+?>
\ No newline at end of file
diff --git a/updateConfig.php b/updateConfig.php
index 564ff38..54e9808 100644
--- a/updateConfig.php
+++ b/updateConfig.php
@@ -1,5 +1,5 @@
'Failed to update configuration.']);
- exit;
+ // Log the error.
+ error_log("updateConfig.php: Initial write failed, attempting to delete the old configuration file.");
+
+ // Delete the old file.
+ if (file_exists($configFile)) {
+ unlink($configFile);
+ }
+
+ // Try writing again.
+ if (file_put_contents($configFile, $encryptedContent, LOCK_EX) === false) {
+ error_log("updateConfig.php: Failed to write configuration even after deletion.");
+ http_response_code(500);
+ echo json_encode(['error' => 'Failed to update configuration even after cleanup.']);
+ exit;
+ }
}
echo json_encode(['success' => 'Configuration updated successfully.']);
diff --git a/updateUserPanel.php b/updateUserPanel.php
index baed453..fc8f3ad 100644
--- a/updateUserPanel.php
+++ b/updateUserPanel.php
@@ -1,10 +1,8 @@
"Unauthorized"]);
+ exit;
+}
+
+// Verify the CSRF token from headers.
+$headers = array_change_key_case(getallheaders(), CASE_LOWER);
+$csrfToken = isset($headers['x-csrf-token']) ? trim($headers['x-csrf-token']) : '';
+if (!isset($_SESSION['csrf_token']) || $csrfToken !== $_SESSION['csrf_token']) {
+ http_response_code(403);
+ echo json_encode(["error" => "Invalid CSRF token"]);
+ exit;
+}
+
+// Read the POST input.
+$input = json_decode(file_get_contents("php://input"), true);
+if (!isset($input['permissions']) || !is_array($input['permissions'])) {
+ echo json_encode(["error" => "Invalid input"]);
+ exit;
+}
+
+$permissions = $input['permissions'];
+$permissionsFile = USERS_DIR . "userPermissions.json";
+
+// Load existing permissions if available and decrypt.
+if (file_exists($permissionsFile)) {
+ $encryptedContent = file_get_contents($permissionsFile);
+ $json = decryptData($encryptedContent, $encryptionKey);
+ $existingPermissions = json_decode($json, true);
+ if (!is_array($existingPermissions)) {
+ $existingPermissions = [];
+ }
+} else {
+ $existingPermissions = [];
+}
+
+// Loop through each permission update.
+foreach ($permissions as $perm) {
+ // Ensure username is provided.
+ if (!isset($perm['username'])) continue;
+ $username = $perm['username'];
+ // Skip updating permissions for admin users.
+ if (strtolower($username) === "admin") continue;
+
+ // Update permissions: default any missing value to false.
+ $existingPermissions[$username] = [
+ 'folderOnly' => isset($perm['folderOnly']) ? (bool)$perm['folderOnly'] : false,
+ 'readOnly' => isset($perm['readOnly']) ? (bool)$perm['readOnly'] : false,
+ 'disableUpload' => isset($perm['disableUpload']) ? (bool)$perm['disableUpload'] : false
+ ];
+}
+
+// Convert the permissions array to JSON.
+$plainText = json_encode($existingPermissions, JSON_PRETTY_PRINT);
+// Encrypt the JSON data.
+$encryptedData = encryptData($plainText, $encryptionKey);
+// Save encrypted permissions back to the JSON file.
+$result = file_put_contents($permissionsFile, $encryptedData);
+if ($result === false) {
+ echo json_encode(["error" => "Failed to save user permissions."]);
+ exit;
+}
+
+echo json_encode(["success" => "User permissions updated successfully."]);
+?>
\ No newline at end of file
diff --git a/upload.php b/upload.php
index c1a2b9a..eb6189a 100644
--- a/upload.php
+++ b/upload.php
@@ -18,6 +18,15 @@ if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
http_response_code(401);
exit;
}
+$userPermissions = loadUserPermissions($username);
+$username = $_SESSION['username'] ?? '';
+if ($username) {
+ $userPermissions = loadUserPermissions($username);
+ if (isset($userPermissions['disableUpload']) && $userPermissions['disableUpload'] === true) {
+ echo json_encode(["error" => "Disabled upload users are not allowed to upload."]);
+ exit();
+ }
+}
/*
* Handle test chunk requests.