diff --git a/README.md b/README.md index 0678eba..3860606 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,10 @@ FileRise is a lightweight, secure, self-hosted web application for uploading, sy - Features an intuitive interface with Material Icons for quick recognition and access. - Allows administrators to manage authentication settings, user management, and login methods in real time. - Includes real-time validation that prevents the accidental disabling of all authentication methods simultaneously. + - User Permissions options + - Folder Only gives user their own root folder + - Read Only makes it so user can only read the files + - Disable upload --- diff --git a/addUser.php b/addUser.php index 482ba59..fe9d78a 100644 --- a/addUser.php +++ b/addUser.php @@ -1,5 +1,5 @@ restore_from_trash'; - // Insert restoreBtn right after the first button. if (firstButton) { insertAfter(restoreBtn, firstButton); } else { @@ -97,14 +109,12 @@ function updateAuthenticatedUI(data) { } restoreBtn.style.display = "block"; - // Create admin panel button. let adminPanelBtn = document.getElementById("adminPanelBtn"); if (!adminPanelBtn) { adminPanelBtn = document.createElement("button"); adminPanelBtn.id = "adminPanelBtn"; adminPanelBtn.classList.add("btn", "btn-info"); adminPanelBtn.innerHTML = 'admin_panel_settings'; - // Insert adminPanelBtn right after the restore button. insertAfter(adminPanelBtn, restoreBtn); adminPanelBtn.addEventListener("click", openAdminPanel); } else { @@ -117,20 +127,16 @@ function updateAuthenticatedUI(data) { if (adminPanelBtn) adminPanelBtn.style.display = "none"; } - // User panel button: Always visible for authenticated users. let userPanelBtn = document.getElementById("userPanelBtn"); if (!userPanelBtn) { userPanelBtn = document.createElement("button"); userPanelBtn.id = "userPanelBtn"; userPanelBtn.classList.add("btn", "btn-user"); userPanelBtn.innerHTML = 'account_circle'; - - // Try to insert the user panel button right after the admin panel button if it exists. let adminPanelBtn = document.getElementById("adminPanelBtn"); if (adminPanelBtn) { insertAfter(userPanelBtn, adminPanelBtn); } else { - // If no admin panel button exists, insert right after the first button in headerButtons. const firstButton = headerButtons.firstElementChild; if (firstButton) { insertAfter(userPanelBtn, firstButton); @@ -162,7 +168,6 @@ function checkAuthentication(showLoginToast = true) { } window.setupMode = false; if (data.authenticated) { - // Update localStorage for TOTP state if provided by the server. if (typeof data.totp_enabled !== "undefined") { localStorage.setItem("userTOTPEnabled", data.totp_enabled ? "true" : "false"); } @@ -181,10 +186,9 @@ function checkAuthentication(showLoginToast = true) { .catch(() => false); } -/* ----------------- TOTP Login Modal ----------------- */ -let lastLoginData = null; // For auto-submission +/* ----------------- Authentication Submission ----------------- */ function submitLogin(data) { - lastLoginData = data; + setLastLoginData(data); sendRequest("auth.php", "POST", data, { "X-CSRF-Token": window.csrfToken }) .then(response => { if (response.success) { @@ -210,451 +214,9 @@ function submitLogin(data) { showToast("Login failed: Unknown error"); }); } +window.submitLogin = submitLogin; -function openTOTPLoginModal() { - let totpLoginModal = document.getElementById("totpLoginModal"); - const isDarkMode = document.body.classList.contains("dark-mode"); - const modalBg = isDarkMode ? "#2c2c2c" : "#fff"; - const textColor = isDarkMode ? "#e0e0e0" : "#000"; - - if (!totpLoginModal) { - totpLoginModal = document.createElement("div"); - totpLoginModal.id = "totpLoginModal"; - totpLoginModal.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: rgba(0,0,0,0.5); - display: flex; - justify-content: center; - align-items: center; - z-index: 3200; - `; - totpLoginModal.innerHTML = ` -
- × -

Enter TOTP Code

- -
- `; - document.body.appendChild(totpLoginModal); - document.getElementById("closeTOTPLoginModal").addEventListener("click", () => { - totpLoginModal.style.display = "none"; - }); - const totpInput = document.getElementById("totpLoginInput"); - document.getElementById("totpLoginInput").focus(); - totpInput.addEventListener("input", function () { - if (this.value.trim().length === 6 && lastLoginData) { - lastLoginData.totp_code = this.value.trim(); - totpLoginModal.style.display = "none"; - submitLogin(lastLoginData); - } - }); - } else { - totpLoginModal.style.display = "flex"; - // Update colors in case dark mode changed. - const modalContent = totpLoginModal.firstElementChild; - modalContent.style.background = modalBg; - modalContent.style.color = textColor; - } -} - -/* ----------------- User Panel Modal ----------------- */ -function openUserPanel() { - let userPanelModal = document.getElementById("userPanelModal"); - const isDarkMode = document.body.classList.contains("dark-mode"); - const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; - // Added transform and transition none to prevent scaling on click. - const modalContentStyles = ` - background: ${isDarkMode ? "#2c2c2c" : "#fff"}; - color: ${isDarkMode ? "#e0e0e0" : "#000"}; - padding: 20px; - max-width: 600px; - width: 90%; - border-radius: 8px; - position: relative; - overflow-y: auto; - max-height: 90vh; - border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"}; - transform: none; - transition: none; - `; - if (!userPanelModal) { - userPanelModal = document.createElement("div"); - userPanelModal.id = "userPanelModal"; - userPanelModal.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: ${overlayBackground}; - display: flex; - justify-content: center; - align-items: center; - z-index: 3000; - `; - userPanelModal.innerHTML = ` - - `; - document.body.appendChild(userPanelModal); - document.getElementById("closeUserPanel").addEventListener("click", () => { - userPanelModal.style.display = "none"; - }); - // Bind the "Change Password" button to open the changePasswordModal. - document.getElementById("openChangePasswordModalBtn").addEventListener("click", () => { - document.getElementById("changePasswordModal").style.display = "block"; - }); - // Initialize TOTP checkbox state and TOTP configuration button. - const totpCheckbox = document.getElementById("userTOTPEnabled"); - // Initialize checkbox based on stored setting. - totpCheckbox.checked = localStorage.getItem("userTOTPEnabled") === "true"; - - totpCheckbox.addEventListener("change", function () { - // Save the new state. - localStorage.setItem("userTOTPEnabled", this.checked ? "true" : "false"); - - const enabled = this.checked; - fetch("updateUserPanel.php", { - method: "POST", - credentials: "include", - headers: { - "Content-Type": "application/json", - "X-CSRF-Token": window.csrfToken - }, - body: JSON.stringify({ totp_enabled: enabled }) - }) - .then(r => r.json()) - .then(result => { - if (!result.success) { - showToast("Error updating TOTP setting: " + result.error); - } else if (enabled) { - // Automatically open the TOTP modal when TOTP is enabled. - openTOTPModal(); - } - }) - .catch(() => { showToast("Error updating TOTP setting."); }); - }); - } else { - // Update colors in case dark mode changed. - userPanelModal.style.backgroundColor = overlayBackground; - const modalContent = userPanelModal.querySelector(".modal-content"); - modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff"; - modalContent.style.color = isDarkMode ? "#e0e0e0" : "#000"; - modalContent.style.border = isDarkMode ? "1px solid #444" : "1px solid #ccc"; - } - userPanelModal.style.display = "flex"; -} - -/* ----------------- TOTP Setup Modal ----------------- */ -function openTOTPModal() { - let totpModal = document.getElementById("totpModal"); - const isDarkMode = document.body.classList.contains("dark-mode"); - const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; - const modalContentStyles = ` - background: ${isDarkMode ? "#2c2c2c" : "#fff"}; - color: ${isDarkMode ? "#e0e0e0" : "#000"}; - padding: 20px; - max-width: 400px; - width: 90%; - border-radius: 8px; - position: relative; - `; - if (!totpModal) { - totpModal = document.createElement("div"); - totpModal.id = "totpModal"; - totpModal.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: ${overlayBackground}; - display: flex; - justify-content: center; - align-items: center; - z-index: 3100; - `; - totpModal.innerHTML = ` - - `; - document.body.appendChild(totpModal); - document.getElementById("closeTOTPModal").addEventListener("click", closeTOTPModal); - const totpInput = document.getElementById("totpSetupInput"); - totpInput.addEventListener("input", function () { - if (this.value.trim().length === 6 && lastLoginData) { - lastLoginData.totp_code = this.value.trim(); - totpModal.style.display = "none"; - submitLogin(lastLoginData); - } - }); - } else { - totpModal.style.display = "flex"; - totpModal.style.backgroundColor = overlayBackground; - const modalContent = totpModal.querySelector(".modal-content"); - modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff"; - modalContent.style.color = isDarkMode ? "#e0e0e0" : "#000"; - } -} - -function closeTOTPModal() { - const totpModal = document.getElementById("totpModal"); - if (totpModal) totpModal.style.display = "none"; -} - -function openAdminPanel() { - fetch("getConfig.php", { credentials: "include" }) - .then(response => response.json()) - .then(config => { - if (config.oidc) Object.assign(currentOIDCConfig, config.oidc); - if (config.globalOtpauthUrl) currentOIDCConfig.globalOtpauthUrl = config.globalOtpauthUrl; - const isDarkMode = document.body.classList.contains("dark-mode"); - const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; - const modalContentStyles = ` - background: ${isDarkMode ? "#2c2c2c" : "#fff"}; - color: ${isDarkMode ? "#e0e0e0" : "#000"}; - padding: 20px; - max-width: 600px; - width: 90%; - border-radius: 8px; - position: relative; - overflow-y: auto; - max-height: 90vh; - border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"}; - `; - let adminModal = document.getElementById("adminPanelModal"); - - if (!adminModal) { - adminModal = document.createElement("div"); - adminModal.id = "adminPanelModal"; - adminModal.style.cssText = ` - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: ${overlayBackground}; - display: flex; - justify-content: center; - align-items: center; - z-index: 3000; - `; - adminModal.innerHTML = ` - - `; - document.body.appendChild(adminModal); - - document.getElementById("closeAdminPanel").addEventListener("click", closeAdminPanel); - adminModal.addEventListener("click", (e) => { - if (e.target === adminModal) closeAdminPanel(); - }); - document.getElementById("cancelAdminSettings").addEventListener("click", closeAdminPanel); - document.getElementById("adminOpenAddUser").addEventListener("click", () => { - toggleVisibility("addUserModal", true); - document.getElementById("newUsername").focus(); - }); - document.getElementById("adminOpenRemoveUser").addEventListener("click", () => { - loadUserList(); - toggleVisibility("removeUserModal", true); - }); - document.getElementById("saveAdminSettings").addEventListener("click", () => { - const disableFormLoginCheckbox = document.getElementById("disableFormLogin"); - const disableBasicAuthCheckbox = document.getElementById("disableBasicAuth"); - const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin"); - const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length; - if (totalDisabled === 3) { - showToast("At least one login method must remain enabled."); - disableOIDCLoginCheckbox.checked = false; - localStorage.setItem("disableOIDCLogin", "false"); - updateLoginOptionsUI({ - disableFormLogin: disableFormLoginCheckbox.checked, - disableBasicAuth: disableBasicAuthCheckbox.checked, - disableOIDCLogin: disableOIDCLoginCheckbox.checked - }); - return; - } - const newOIDCConfig = { - providerUrl: document.getElementById("oidcProviderUrl").value.trim(), - clientId: document.getElementById("oidcClientId").value.trim(), - clientSecret: document.getElementById("oidcClientSecret").value.trim(), - redirectUri: document.getElementById("oidcRedirectUri").value.trim() - }; - const disableFormLogin = disableFormLoginCheckbox.checked; - const disableBasicAuth = disableBasicAuthCheckbox.checked; - const disableOIDCLogin = disableOIDCLoginCheckbox.checked; - const globalOtpauthUrl = document.getElementById("globalOtpauthUrl").value.trim(); - sendRequest("updateConfig.php", "POST", { - oidc: newOIDCConfig, - disableFormLogin, - disableBasicAuth, - disableOIDCLogin, - globalOtpauthUrl - }, { "X-CSRF-Token": window.csrfToken }) - .then(response => { - if (response.success) { - showToast("Settings updated successfully."); - localStorage.setItem("disableFormLogin", disableFormLogin); - localStorage.setItem("disableBasicAuth", disableBasicAuth); - localStorage.setItem("disableOIDCLogin", disableOIDCLogin); - updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin }); - closeAdminPanel(); - } else { - showToast("Error updating settings: " + (response.error || "Unknown error")); - } - }) - .catch(() => { }); - }); - // Enforce that at least one login method remains enabled. - const disableFormLoginCheckbox = document.getElementById("disableFormLogin"); - const disableBasicAuthCheckbox = document.getElementById("disableBasicAuth"); - const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin"); - function enforceLoginOptionConstraint(changedCheckbox) { - const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length; - if (changedCheckbox.checked && totalDisabled === 3) { - showToast("At least one login method must remain enabled."); - changedCheckbox.checked = false; - } - } - disableFormLoginCheckbox.addEventListener("change", function () { enforceLoginOptionConstraint(this); }); - disableBasicAuthCheckbox.addEventListener("change", function () { enforceLoginOptionConstraint(this); }); - disableOIDCLoginCheckbox.addEventListener("change", function () { enforceLoginOptionConstraint(this); }); - - // UPDATE checkboxes using fetched configuration: - document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; - document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; - document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; - } else { - // If the modal already exists, update its styles and values. - adminModal.style.backgroundColor = overlayBackground; - const modalContent = adminModal.querySelector(".modal-content"); - if (modalContent) { - modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff"; - modalContent.style.color = isDarkMode ? "#e0e0e0" : "#000"; - modalContent.style.border = isDarkMode ? "1px solid #444" : "1px solid #ccc"; - } - document.getElementById("oidcProviderUrl").value = currentOIDCConfig.providerUrl; - document.getElementById("oidcClientId").value = currentOIDCConfig.clientId; - document.getElementById("oidcClientSecret").value = currentOIDCConfig.clientSecret; - document.getElementById("oidcRedirectUri").value = currentOIDCConfig.redirectUri; - document.getElementById("globalOtpauthUrl").value = currentOIDCConfig.globalOtpauthUrl || 'otpauth://totp/FileRise?issuer=FileRise'; - - // UPDATE checkboxes using fetched configuration: - document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; - document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; - document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; - - adminModal.style.display = "flex"; - } - }) - .catch(() => { - // In case of error, fallback to localStorage values - let adminModal = document.getElementById("adminPanelModal"); - if (adminModal) { - adminModal.style.backgroundColor = "rgba(0,0,0,0.5)"; - const modalContent = adminModal.querySelector(".modal-content"); - if (modalContent) { - modalContent.style.background = "#fff"; - modalContent.style.color = "#000"; - modalContent.style.border = "1px solid #ccc"; - } - document.getElementById("oidcProviderUrl").value = currentOIDCConfig.providerUrl; - document.getElementById("oidcClientId").value = currentOIDCConfig.clientId; - document.getElementById("oidcClientSecret").value = currentOIDCConfig.clientSecret; - document.getElementById("oidcRedirectUri").value = currentOIDCConfig.redirectUri; - document.getElementById("globalOtpauthUrl").value = currentOIDCConfig.globalOtpauthUrl || 'otpauth://totp/FileRise?issuer=FileRise'; - - document.getElementById("disableFormLogin").checked = localStorage.getItem("disableFormLogin") === "true"; - document.getElementById("disableBasicAuth").checked = localStorage.getItem("disableBasicAuth") === "true"; - document.getElementById("disableOIDCLogin").checked = localStorage.getItem("disableOIDCLogin") === "true"; - adminModal.style.display = "flex"; - } else { - openAdminPanel(); - } - }); -} - -function closeAdminPanel() { - const adminModal = document.getElementById("adminPanelModal"); - if (adminModal) adminModal.style.display = "none"; -} - -/* ----------------- Other Helpers ----------------- */ +/* ----------------- Other Helpers and Initialization ----------------- */ window.changeItemsPerPage = function (value) { localStorage.setItem("itemsPerPage", value); if (typeof renderFileTable === "function") renderFileTable(window.currentFolder || "root"); @@ -695,8 +257,8 @@ function loadUserList() { }) .catch(() => { }); } +window.loadUserList = loadUserList; -/* ----------------- Initialization ----------------- */ function initAuth() { checkAuthentication(false); loadAdminConfigFunc(); @@ -722,12 +284,6 @@ function initAuth() { headers: { "X-CSRF-Token": window.csrfToken } }).then(() => window.location.reload(true)).catch(() => { }); }); - const oidcLoginBtn = document.getElementById("oidcLoginBtn"); - if (oidcLoginBtn) { - oidcLoginBtn.addEventListener("click", function () { - window.location.href = "auth.php?oidc"; - }); - } document.getElementById("addUserBtn").addEventListener("click", function () { resetUserForm(); toggleVisibility("addUserModal", true); @@ -795,7 +351,6 @@ function initAuth() { .catch(() => { }); }); document.getElementById("cancelRemoveUserBtn").addEventListener("click", closeRemoveUserModal); - // Change password bindings. document.getElementById("changePasswordBtn").addEventListener("click", function () { document.getElementById("changePasswordModal").style.display = "block"; document.getElementById("oldPassword").focus(); diff --git a/auth.php b/auth.php index ada5929..972b736 100644 --- a/auth.php +++ b/auth.php @@ -1,18 +1,36 @@ = 3 && $parts[0] === $username) { + return trim($parts[2]); + } + } + } + return null; +} +/* --- OIDC Authentication Flow --- */ +if (isset($_GET['oidc'])) { // Read and decrypt OIDC configuration from JSON file. $adminConfigFile = USERS_DIR . 'adminConfig.json'; if (file_exists($adminConfigFile)) { $encryptedContent = file_get_contents($adminConfigFile); $decryptedContent = decryptData($encryptedContent, $encryptionKey); if ($decryptedContent === false) { - echo json_encode(['error' => 'Failed to decrypt admin configuration.']); + // Log internal error and return a generic message. + error_log("Failed to decrypt admin configuration."); + echo json_encode(['error' => 'Internal error.']); exit; } $adminConfig = json_decode($decryptedContent, true); @@ -42,8 +60,6 @@ if (isset($_GET['oidc'])) { ); $oidc->setRedirectURL($oidc_redirect_uri); - // Since PKCE is disabled in Keycloak, we do not set any PKCE parameters. - if ($_GET['oidc'] === 'callback') { try { $oidc->authenticate(); @@ -51,11 +67,16 @@ if (isset($_GET['oidc'])) { session_regenerate_id(true); $_SESSION["authenticated"] = true; $_SESSION["username"] = $username; - $_SESSION["isAdmin"] = false; + // Determine the user role from users.txt. + $userRole = getUserRole($username); + $_SESSION["isAdmin"] = ($userRole === "1"); + // *** Use loadUserPermissions() here instead of loadFolderPermission() *** + $_SESSION["folderOnly"] = loadUserPermissions($username); header("Location: index.html"); exit(); } catch (Exception $e) { - echo json_encode(["error" => "Authentication failed: " . $e->getMessage()]); + error_log("OIDC authentication error: " . $e->getMessage()); + echo json_encode(["error" => "Authentication failed."]); exit(); } } else { @@ -63,14 +84,15 @@ if (isset($_GET['oidc'])) { $oidc->authenticate(); exit(); } catch (Exception $e) { - echo json_encode(["error" => "Authentication initiation failed: " . $e->getMessage()]); + error_log("OIDC initiation error: " . $e->getMessage()); + echo json_encode(["error" => "Authentication initiation failed."]); exit(); } } } -// --- Fallback: Form-based Authentication --- - +/* --- Fallback: Form-based Authentication --- */ +// (Form-based branch code remains unchanged. It calls loadUserPermissions() in its basic auth branch.) $usersFile = USERS_DIR . USERS_FILE; $maxAttempts = 5; $lockoutTime = 30 * 60; @@ -104,13 +126,6 @@ if (isset($failedAttempts[$ip])) { } } -/* - * Updated authenticate() function: - * It reads each line from users.txt. - * It expects records in the format: - * username:hashed_password:role[:encrypted_totp_secret] - * If a fourth field is present and non-empty, it decrypts it to obtain the TOTP secret. - */ function authenticate($username, $password) { global $usersFile, $encryptionKey; if (!file_exists($usersFile)) { @@ -119,10 +134,9 @@ function authenticate($username, $password) { $lines = file($usersFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { $parts = explode(':', trim($line)); - if (count($parts) < 3) continue; // Skip invalid lines. + if (count($parts) < 3) continue; if ($username === $parts[0] && password_verify($password, $parts[1])) { $result = ['role' => $parts[2]]; - // If there's a fourth field, decrypt it to get the TOTP secret. if (isset($parts[3]) && !empty($parts[3])) { $result['totp_secret'] = decryptData($parts[3], $encryptionKey); } else { @@ -151,7 +165,6 @@ if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $username)) { $user = authenticate($username, $password); if ($user !== false) { - // Only require TOTP if the user's TOTP secret is set. if (!empty($user['totp_secret'])) { if (empty($data['totp_code'])) { echo json_encode([ @@ -168,8 +181,6 @@ if ($user !== false) { } } } - // --- End TOTP Integration --- - if (isset($failedAttempts[$ip])) { unset($failedAttempts[$ip]); saveFailedAttempts($attemptsFile, $failedAttempts); @@ -178,6 +189,7 @@ if ($user !== false) { $_SESSION["authenticated"] = true; $_SESSION["username"] = $username; $_SESSION["isAdmin"] = ($user['role'] === "1"); + $_SESSION["folderOnly"] = loadUserPermissions($username); if ($rememberMe) { $token = bin2hex(random_bytes(32)); @@ -194,14 +206,19 @@ if ($user !== false) { $persistentTokens[$token] = [ "username" => $username, "expiry" => $expiry, - "isAdmin" => ($user['role'] === "1") + "isAdmin" => ($_SESSION["isAdmin"] === true) ]; $encryptedContent = encryptData(json_encode($persistentTokens, JSON_PRETTY_PRINT), $encryptionKey); file_put_contents($persistentTokensFile, $encryptedContent, LOCK_EX); setcookie('remember_me_token', $token, $expiry, '/', '', $secure, true); } - echo json_encode(["success" => "Login successful", "isAdmin" => $_SESSION["isAdmin"]]); + echo json_encode([ + "success" => "Login successful", + "isAdmin" => $_SESSION["isAdmin"], + "folderOnly"=> $_SESSION["folderOnly"], + "username" => $_SESSION["username"] + ]); } else { if (isset($failedAttempts[$ip])) { $failedAttempts[$ip]['count']++; diff --git a/authModals.js b/authModals.js new file mode 100644 index 0000000..a079edf --- /dev/null +++ b/authModals.js @@ -0,0 +1,655 @@ +import { showToast, toggleVisibility } from './domUtils.js'; +import { sendRequest } from './networkUtils.js'; + +const version = "v1.0.5"; +const adminTitle = `Admin Panel ${version}`; +let lastLoginData = null; + +export function setLastLoginData(data) { + lastLoginData = data; +} + +export function openTOTPLoginModal() { + let totpLoginModal = document.getElementById("totpLoginModal"); + const isDarkMode = document.body.classList.contains("dark-mode"); + const modalBg = isDarkMode ? "#2c2c2c" : "#fff"; + const textColor = isDarkMode ? "#e0e0e0" : "#000"; + + if (!totpLoginModal) { + totpLoginModal = document.createElement("div"); + totpLoginModal.id = "totpLoginModal"; + totpLoginModal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0,0,0,0.5); + display: flex; + justify-content: center; + align-items: center; + z-index: 3200; + `; + totpLoginModal.innerHTML = ` +
+ × +

Enter TOTP Code

+ +
+ `; + document.body.appendChild(totpLoginModal); + document.getElementById("closeTOTPLoginModal").addEventListener("click", () => { + totpLoginModal.style.display = "none"; + }); + const totpInput = document.getElementById("totpLoginInput"); + totpInput.focus(); + totpInput.addEventListener("input", function () { + if (this.value.trim().length === 6 && lastLoginData) { + lastLoginData.totp_code = this.value.trim(); + totpLoginModal.style.display = "none"; + if (typeof window.submitLogin === "function") { + window.submitLogin(lastLoginData); + } + } + }); + } else { + totpLoginModal.style.display = "flex"; + const modalContent = totpLoginModal.firstElementChild; + modalContent.style.background = modalBg; + modalContent.style.color = textColor; + } +} + +export function openUserPanel() { + const username = localStorage.getItem("username") || "User"; + let userPanelModal = document.getElementById("userPanelModal"); + const isDarkMode = document.body.classList.contains("dark-mode"); + const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; + const modalContentStyles = ` + background: ${isDarkMode ? "#2c2c2c" : "#fff"}; + color: ${isDarkMode ? "#e0e0e0" : "#000"}; + padding: 20px; + max-width: 600px; + width: 90%; + border-radius: 8px; + position: relative; + overflow-y: auto; + max-height: 90vh; + border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"}; + transform: none; + transition: none; + `; + if (!userPanelModal) { + userPanelModal = document.createElement("div"); + userPanelModal.id = "userPanelModal"; + userPanelModal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: ${overlayBackground}; + display: flex; + justify-content: center; + align-items: center; + z-index: 3000; + `; + userPanelModal.innerHTML = ` + + `; + document.body.appendChild(userPanelModal); + document.getElementById("closeUserPanel").addEventListener("click", () => { + userPanelModal.style.display = "none"; + }); + document.getElementById("openChangePasswordModalBtn").addEventListener("click", () => { + document.getElementById("changePasswordModal").style.display = "block"; + }); + const totpCheckbox = document.getElementById("userTOTPEnabled"); + totpCheckbox.checked = localStorage.getItem("userTOTPEnabled") === "true"; + totpCheckbox.addEventListener("change", function () { + localStorage.setItem("userTOTPEnabled", this.checked ? "true" : "false"); + const enabled = this.checked; + fetch("updateUserPanel.php", { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": window.csrfToken + }, + body: JSON.stringify({ totp_enabled: enabled }) + }) + .then(r => r.json()) + .then(result => { + if (!result.success) { + showToast("Error updating TOTP setting: " + result.error); + } else if (enabled) { + openTOTPModal(); + } + }) + .catch(() => { showToast("Error updating TOTP setting."); }); + }); + } else { + userPanelModal.style.backgroundColor = overlayBackground; + const modalContent = userPanelModal.querySelector(".modal-content"); + modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff"; + modalContent.style.color = isDarkMode ? "#e0e0e0" : "#000"; + modalContent.style.border = isDarkMode ? "1px solid #444" : "1px solid #ccc"; + } + userPanelModal.style.display = "flex"; +} + +export function openTOTPModal() { + let totpModal = document.getElementById("totpModal"); + const isDarkMode = document.body.classList.contains("dark-mode"); + const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; + const modalContentStyles = ` + background: ${isDarkMode ? "#2c2c2c" : "#fff"}; + color: ${isDarkMode ? "#e0e0e0" : "#000"}; + padding: 20px; + max-width: 400px; + width: 90%; + border-radius: 8px; + position: relative; + `; + if (!totpModal) { + totpModal = document.createElement("div"); + totpModal.id = "totpModal"; + totpModal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: ${overlayBackground}; + display: flex; + justify-content: center; + align-items: center; + z-index: 3100; + `; + totpModal.innerHTML = ` + + `; + document.body.appendChild(totpModal); + // Bind the X button to call closeTOTPModal with disable=true + document.getElementById("closeTOTPModal").addEventListener("click", () => { + closeTOTPModal(true); + }); + + // Add event listener for TOTP confirmation + document.getElementById("confirmTOTPBtn").addEventListener("click", function () { + const code = document.getElementById("totpConfirmInput").value.trim(); + if (code.length !== 6) { + showToast("Please enter a valid 6-digit code."); + return; + } + // Call the endpoint to verify the TOTP code + fetch("totp_verify.php", { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": window.csrfToken + }, + body: JSON.stringify({ totp_code: code }) + }) + .then(r => r.json()) + .then(result => { + if (result.success) { + showToast("TOTP successfully enabled."); + // On success, close the modal without disabling + closeTOTPModal(false); + } else { + showToast("TOTP verification failed: " + (result.error || "Invalid code.")); + } + }) + .catch(() => { showToast("Error verifying TOTP code."); }); + }); + } else { + totpModal.style.display = "flex"; + totpModal.style.backgroundColor = overlayBackground; + const modalContent = totpModal.querySelector(".modal-content"); + modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff"; + modalContent.style.color = isDarkMode ? "#e0e0e0" : "#000"; + } +} + +// Updated closeTOTPModal function with a disable parameter +export function closeTOTPModal(disable = true) { + const totpModal = document.getElementById("totpModal"); + if (totpModal) totpModal.style.display = "none"; + + if (disable) { + // Uncheck the Enable TOTP checkbox + const totpCheckbox = document.getElementById("userTOTPEnabled"); + if (totpCheckbox) { + totpCheckbox.checked = false; + localStorage.setItem("userTOTPEnabled", "false"); + } + // Call endpoint to remove the TOTP secret from the user's record + fetch("totp_disable.php", { + method: "POST", + credentials: "include", + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": window.csrfToken + } + }) + .then(r => r.json()) + .then(result => { + if (!result.success) { + showToast("Error disabling TOTP setting: " + result.error); + } + }) + .catch(() => { showToast("Error disabling TOTP setting."); }); + } + } + +export function openAdminPanel() { + fetch("getConfig.php", { credentials: "include" }) + .then(response => response.json()) + .then(config => { + if (config.oidc) Object.assign(window.currentOIDCConfig, config.oidc); + if (config.globalOtpauthUrl) window.currentOIDCConfig.globalOtpauthUrl = config.globalOtpauthUrl; + const isDarkMode = document.body.classList.contains("dark-mode"); + const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; + const modalContentStyles = ` + background: ${isDarkMode ? "#2c2c2c" : "#fff"}; + color: ${isDarkMode ? "#e0e0e0" : "#000"}; + padding: 20px; + max-width: 600px; + width: 90%; + border-radius: 8px; + position: relative; + overflow-y: auto; + max-height: 90vh; + border: ${isDarkMode ? "1px solid #444" : "1px solid #ccc"}; + `; + let adminModal = document.getElementById("adminPanelModal"); + + if (!adminModal) { + adminModal = document.createElement("div"); + adminModal.id = "adminPanelModal"; + adminModal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: ${overlayBackground}; + display: flex; + justify-content: center; + align-items: center; + z-index: 3000; + `; + // Added a version number next to "Admin Panel" + adminModal.innerHTML = ` + + `; + document.body.appendChild(adminModal); + + document.getElementById("closeAdminPanel").addEventListener("click", closeAdminPanel); + adminModal.addEventListener("click", (e) => { + if (e.target === adminModal) closeAdminPanel(); + }); + document.getElementById("cancelAdminSettings").addEventListener("click", closeAdminPanel); + document.getElementById("adminOpenAddUser").addEventListener("click", () => { + toggleVisibility("addUserModal", true); + document.getElementById("newUsername").focus(); + }); + document.getElementById("adminOpenRemoveUser").addEventListener("click", () => { + if (typeof window.loadUserList === "function") { + window.loadUserList(); + } + toggleVisibility("removeUserModal", true); + }); + // New event binding for the User Permissions button: + document.getElementById("adminOpenUserPermissions").addEventListener("click", () => { + openUserPermissionsModal(); + }); + document.getElementById("saveAdminSettings").addEventListener("click", () => { + const disableFormLoginCheckbox = document.getElementById("disableFormLogin"); + const disableBasicAuthCheckbox = document.getElementById("disableBasicAuth"); + const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin"); + const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length; + if (totalDisabled === 3) { + showToast("At least one login method must remain enabled."); + disableOIDCLoginCheckbox.checked = false; + localStorage.setItem("disableOIDCLogin", "false"); + if (typeof window.updateLoginOptionsUI === "function") { + window.updateLoginOptionsUI({ + disableFormLogin: disableFormLoginCheckbox.checked, + disableBasicAuth: disableBasicAuthCheckbox.checked, + disableOIDCLogin: disableOIDCLoginCheckbox.checked + }); + } + return; + } + const newOIDCConfig = { + providerUrl: document.getElementById("oidcProviderUrl").value.trim(), + clientId: document.getElementById("oidcClientId").value.trim(), + clientSecret: document.getElementById("oidcClientSecret").value.trim(), + redirectUri: document.getElementById("oidcRedirectUri").value.trim() + }; + const disableFormLogin = disableFormLoginCheckbox.checked; + const disableBasicAuth = disableBasicAuthCheckbox.checked; + const disableOIDCLogin = disableOIDCLoginCheckbox.checked; + const globalOtpauthUrl = document.getElementById("globalOtpauthUrl").value.trim(); + sendRequest("updateConfig.php", "POST", { + oidc: newOIDCConfig, + disableFormLogin, + disableBasicAuth, + disableOIDCLogin, + globalOtpauthUrl + }, { "X-CSRF-Token": window.csrfToken }) + .then(response => { + if (response.success) { + showToast("Settings updated successfully."); + localStorage.setItem("disableFormLogin", disableFormLogin); + localStorage.setItem("disableBasicAuth", disableBasicAuth); + localStorage.setItem("disableOIDCLogin", disableOIDCLogin); + if (typeof window.updateLoginOptionsUI === "function") { + window.updateLoginOptionsUI({ disableFormLogin, disableBasicAuth, disableOIDCLogin }); + } + closeAdminPanel(); + } else { + showToast("Error updating settings: " + (response.error || "Unknown error")); + } + }) + .catch(() => { }); + }); + const disableFormLoginCheckbox = document.getElementById("disableFormLogin"); + const disableBasicAuthCheckbox = document.getElementById("disableBasicAuth"); + const disableOIDCLoginCheckbox = document.getElementById("disableOIDCLogin"); + function enforceLoginOptionConstraint(changedCheckbox) { + const totalDisabled = [disableFormLoginCheckbox, disableBasicAuthCheckbox, disableOIDCLoginCheckbox].filter(cb => cb.checked).length; + if (changedCheckbox.checked && totalDisabled === 3) { + showToast("At least one login method must remain enabled."); + changedCheckbox.checked = false; + } + } + disableFormLoginCheckbox.addEventListener("change", function () { enforceLoginOptionConstraint(this); }); + disableBasicAuthCheckbox.addEventListener("change", function () { enforceLoginOptionConstraint(this); }); + disableOIDCLoginCheckbox.addEventListener("change", function () { enforceLoginOptionConstraint(this); }); + + document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; + document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; + document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; + } else { + adminModal.style.backgroundColor = overlayBackground; + const modalContent = adminModal.querySelector(".modal-content"); + if (modalContent) { + modalContent.style.background = isDarkMode ? "#2c2c2c" : "#fff"; + modalContent.style.color = isDarkMode ? "#e0e0e0" : "#000"; + modalContent.style.border = isDarkMode ? "1px solid #444" : "1px solid #ccc"; + } + document.getElementById("oidcProviderUrl").value = window.currentOIDCConfig.providerUrl; + document.getElementById("oidcClientId").value = window.currentOIDCConfig.clientId; + document.getElementById("oidcClientSecret").value = window.currentOIDCConfig.clientSecret; + document.getElementById("oidcRedirectUri").value = window.currentOIDCConfig.redirectUri; + document.getElementById("globalOtpauthUrl").value = window.currentOIDCConfig.globalOtpauthUrl || 'otpauth://totp/FileRise?issuer=FileRise'; + document.getElementById("disableFormLogin").checked = config.loginOptions.disableFormLogin === true; + document.getElementById("disableBasicAuth").checked = config.loginOptions.disableBasicAuth === true; + document.getElementById("disableOIDCLogin").checked = config.loginOptions.disableOIDCLogin === true; + adminModal.style.display = "flex"; + } + }) + .catch(() => { + let adminModal = document.getElementById("adminPanelModal"); + if (adminModal) { + adminModal.style.backgroundColor = "rgba(0,0,0,0.5)"; + const modalContent = adminModal.querySelector(".modal-content"); + if (modalContent) { + modalContent.style.background = "#fff"; + modalContent.style.color = "#000"; + modalContent.style.border = "1px solid #ccc"; + } + document.getElementById("oidcProviderUrl").value = window.currentOIDCConfig.providerUrl; + document.getElementById("oidcClientId").value = window.currentOIDCConfig.clientId; + document.getElementById("oidcClientSecret").value = window.currentOIDCConfig.clientSecret; + document.getElementById("oidcRedirectUri").value = window.currentOIDCConfig.redirectUri; + document.getElementById("globalOtpauthUrl").value = window.currentOIDCConfig.globalOtpauthUrl || 'otpauth://totp/FileRise?issuer=FileRise'; + document.getElementById("disableFormLogin").checked = localStorage.getItem("disableFormLogin") === "true"; + document.getElementById("disableBasicAuth").checked = localStorage.getItem("disableBasicAuth") === "true"; + document.getElementById("disableOIDCLogin").checked = localStorage.getItem("disableOIDCLogin") === "true"; + adminModal.style.display = "flex"; + } else { + openAdminPanel(); + } + }); +} + +export function closeAdminPanel() { + const adminModal = document.getElementById("adminPanelModal"); + if (adminModal) adminModal.style.display = "none"; +} + +// --- New: User Permissions Modal --- + +export function openUserPermissionsModal() { + let userPermissionsModal = document.getElementById("userPermissionsModal"); + const isDarkMode = document.body.classList.contains("dark-mode"); + const overlayBackground = isDarkMode ? "rgba(0,0,0,0.7)" : "rgba(0,0,0,0.3)"; + const modalContentStyles = ` + background: ${isDarkMode ? "#2c2c2c" : "#fff"}; + color: ${isDarkMode ? "#e0e0e0" : "#000"}; + padding: 20px; + max-width: 500px; + width: 90%; + border-radius: 8px; + position: relative; + `; + + if (!userPermissionsModal) { + userPermissionsModal = document.createElement("div"); + userPermissionsModal.id = "userPermissionsModal"; + userPermissionsModal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: ${overlayBackground}; + display: flex; + justify-content: center; + align-items: center; + z-index: 3500; + `; + userPermissionsModal.innerHTML = ` + + `; + document.body.appendChild(userPermissionsModal); + document.getElementById("closeUserPermissionsModal").addEventListener("click", () => { + userPermissionsModal.style.display = "none"; + }); + document.getElementById("cancelUserPermissionsBtn").addEventListener("click", () => { + userPermissionsModal.style.display = "none"; + }); + document.getElementById("saveUserPermissionsBtn").addEventListener("click", () => { + // Collect permissions data from each user row. + const rows = userPermissionsModal.querySelectorAll(".user-permission-row"); + const permissionsData = []; + rows.forEach(row => { + const username = row.getAttribute("data-username"); + const folderOnlyCheckbox = row.querySelector("input[data-permission='folderOnly']"); + const readOnlyCheckbox = row.querySelector("input[data-permission='readOnly']"); + const disableUploadCheckbox = row.querySelector("input[data-permission='disableUpload']"); + permissionsData.push({ + username, + folderOnly: folderOnlyCheckbox.checked, + readOnly: readOnlyCheckbox.checked, + disableUpload: disableUploadCheckbox.checked + }); + }); + // Send the permissionsData to the server. + sendRequest("updateUserPermissions.php", "POST", { permissions: permissionsData }, { "X-CSRF-Token": window.csrfToken }) + .then(response => { + if (response.success) { + showToast("User permissions updated successfully."); + userPermissionsModal.style.display = "none"; + } else { + showToast("Error updating permissions: " + (response.error || "Unknown error")); + } + }) + .catch(() => { + showToast("Error updating permissions."); + }); + }); + } else { + userPermissionsModal.style.display = "flex"; + } + // Load the list of users into the modal. + loadUserPermissionsList(); +} + +function loadUserPermissionsList() { + const listContainer = document.getElementById("userPermissionsList"); + if (!listContainer) return; + listContainer.innerHTML = ""; + + // First, fetch the current permissions from the server. + fetch("getUserPermissions.php", { credentials: "include" }) + .then(response => response.json()) + .then(permissionsData => { + // Then, fetch the list of users. + return fetch("getUsers.php", { credentials: "include" }) + .then(response => response.json()) + .then(usersData => { + const users = Array.isArray(usersData) ? usersData : (usersData.users || []); + if (users.length === 0) { + listContainer.innerHTML = "

No users found.

"; + return; + } + users.forEach(user => { + // Skip admin users. + 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: localStorage.getItem("folderOnly") === "true", + readOnly: localStorage.getItem("readOnly") === "true", + disableUpload: localStorage.getItem("disableUpload") === "true" + }; + const userPerm = (permissionsData && typeof permissionsData === "object" && permissionsData[user.username]) || defaultPerm; + + // Create a row for the user. + const row = document.createElement("div"); + row.classList.add("user-permission-row"); + row.setAttribute("data-username", user.username); + row.style.padding = "10px 0"; + row.innerHTML = ` +
${user.username}
+
+ + + +
+
+ `; + listContainer.appendChild(row); + }); + }); + }) + .catch(() => { + listContainer.innerHTML = "

Error loading users.

"; + }); +} \ 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