Fetch URL fixes, Extended “Remember Me” cookie behavior, submitLogin() overhaul
This commit is contained in:
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,5 +1,37 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Changes 4/19/2025
|
||||||
|
|
||||||
|
- **Extended “Remember Me” cookie behavior**
|
||||||
|
In `AuthController::finalizeLogin()`, after setting `remember_me_token` re‑issued the PHP session cookie with the same 30‑day expiry and called `session_regenerate_id(true)`.
|
||||||
|
|
||||||
|
- **Fetch URL fixes**
|
||||||
|
Changed all front‑end `fetch("api/…")` calls to absolute paths `fetch("/api/…")` to avoid relative‑path 404/403 issues.
|
||||||
|
|
||||||
|
- **CSRF token refresh**
|
||||||
|
Updated `submitLogin()` and both TOTP submission handlers to `async/await` a fresh CSRF token from `/api/auth/token.php` (with `credentials: "include"`) immediately before any POST.
|
||||||
|
|
||||||
|
- **submitLogin() overhaul**
|
||||||
|
Refactored to:
|
||||||
|
1. Fetch CSRF
|
||||||
|
2. POST credentials to `/api/auth/auth.php`
|
||||||
|
3. On `totp_required`, re‑fetch CSRF *again* before calling `openTOTPLoginModal()`
|
||||||
|
4. Handle full logins vs. TOTP flows cleanly.
|
||||||
|
|
||||||
|
- **TOTP handlers update**
|
||||||
|
In both the “Confirm TOTP” button flow and the auto‑submit on 6‑digit input:
|
||||||
|
- Refreshed CSRF token before every `/api/totp_verify.php` call
|
||||||
|
- Checked `response.ok` before parsing JSON
|
||||||
|
- Improved `.catch` error handling
|
||||||
|
|
||||||
|
- **verifyTOTP() endpoint enhancement**
|
||||||
|
Inside the **pending‑login** branch of `verifyTOTP()`:
|
||||||
|
- Pulled `$_SESSION['pending_login_remember_me']`
|
||||||
|
- If true, wrote the persistent token store, set `remember_me_token`, re‑issued the session cookie, and regenerated the session ID
|
||||||
|
- Cleaned up pending session variables
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Changes 4/18/2025
|
## Changes 4/18/2025
|
||||||
|
|
||||||
### fileListView.js
|
### fileListView.js
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ function updateLoginOptionsUIFromStorage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function loadAdminConfigFunc() {
|
export function loadAdminConfigFunc() {
|
||||||
return fetch("api/admin/getConfig.php", { credentials: "include" })
|
return fetch("/api/admin/getConfig.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(config => {
|
.then(config => {
|
||||||
localStorage.setItem("headerTitle", config.header_title || "FileRise");
|
localStorage.setItem("headerTitle", config.header_title || "FileRise");
|
||||||
@@ -105,7 +105,7 @@ export function loadAdminConfigFunc() {
|
|||||||
localStorage.setItem("disableBasicAuth", config.loginOptions.disableBasicAuth);
|
localStorage.setItem("disableBasicAuth", config.loginOptions.disableBasicAuth);
|
||||||
localStorage.setItem("disableOIDCLogin", config.loginOptions.disableOIDCLogin);
|
localStorage.setItem("disableOIDCLogin", config.loginOptions.disableOIDCLogin);
|
||||||
localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/{label}?secret={secret}&issuer=FileRise");
|
localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/{label}?secret={secret}&issuer=FileRise");
|
||||||
|
|
||||||
updateLoginOptionsUIFromStorage();
|
updateLoginOptionsUIFromStorage();
|
||||||
|
|
||||||
const headerTitleElem = document.querySelector(".header-title h1");
|
const headerTitleElem = document.querySelector(".header-title h1");
|
||||||
@@ -149,9 +149,9 @@ function updateAuthenticatedUI(data) {
|
|||||||
if (data.username) {
|
if (data.username) {
|
||||||
localStorage.setItem("username", data.username);
|
localStorage.setItem("username", data.username);
|
||||||
}
|
}
|
||||||
if (typeof data.folderOnly !== "undefined") {
|
if (typeof data.folderOnly !== "undefined") {
|
||||||
localStorage.setItem("folderOnly", data.folderOnly ? "true" : "false");
|
localStorage.setItem("folderOnly", data.folderOnly ? "true" : "false");
|
||||||
localStorage.setItem("readOnly", data.readOnly ? "true" : "false");
|
localStorage.setItem("readOnly", data.readOnly ? "true" : "false");
|
||||||
localStorage.setItem("disableUpload", data.disableUpload ? "true" : "false");
|
localStorage.setItem("disableUpload", data.disableUpload ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,11 +198,11 @@ function updateAuthenticatedUI(data) {
|
|||||||
userPanelBtn.classList.add("btn", "btn-user");
|
userPanelBtn.classList.add("btn", "btn-user");
|
||||||
userPanelBtn.setAttribute("data-i18n-title", "user_panel");
|
userPanelBtn.setAttribute("data-i18n-title", "user_panel");
|
||||||
userPanelBtn.innerHTML = '<i class="material-icons">account_circle</i>';
|
userPanelBtn.innerHTML = '<i class="material-icons">account_circle</i>';
|
||||||
|
|
||||||
const adminBtn = document.getElementById("adminPanelBtn");
|
const adminBtn = document.getElementById("adminPanelBtn");
|
||||||
if (adminBtn) insertAfter(userPanelBtn, adminBtn);
|
if (adminBtn) insertAfter(userPanelBtn, adminBtn);
|
||||||
else if (firstButton) insertAfter(userPanelBtn, firstButton);
|
else if (firstButton) insertAfter(userPanelBtn, firstButton);
|
||||||
else headerButtons.appendChild(userPanelBtn);
|
else headerButtons.appendChild(userPanelBtn);
|
||||||
userPanelBtn.addEventListener("click", openUserPanel);
|
userPanelBtn.addEventListener("click", openUserPanel);
|
||||||
} else {
|
} else {
|
||||||
userPanelBtn.style.display = "block";
|
userPanelBtn.style.display = "block";
|
||||||
@@ -214,7 +214,7 @@ function updateAuthenticatedUI(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkAuthentication(showLoginToast = true) {
|
function checkAuthentication(showLoginToast = true) {
|
||||||
return sendRequest("api/auth/checkAuth.php")
|
return sendRequest("/api/auth/checkAuth.php")
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.setup) {
|
if (data.setup) {
|
||||||
window.setupMode = true;
|
window.setupMode = true;
|
||||||
@@ -228,9 +228,9 @@ function checkAuthentication(showLoginToast = true) {
|
|||||||
}
|
}
|
||||||
window.setupMode = false;
|
window.setupMode = false;
|
||||||
if (data.authenticated) {
|
if (data.authenticated) {
|
||||||
localStorage.setItem("folderOnly", data.folderOnly );
|
localStorage.setItem("folderOnly", data.folderOnly);
|
||||||
localStorage.setItem("readOnly", data.readOnly );
|
localStorage.setItem("readOnly", data.readOnly);
|
||||||
localStorage.setItem("disableUpload",data.disableUpload);
|
localStorage.setItem("disableUpload", data.disableUpload);
|
||||||
updateLoginOptionsUIFromStorage();
|
updateLoginOptionsUIFromStorage();
|
||||||
if (typeof data.totp_enabled !== "undefined") {
|
if (typeof data.totp_enabled !== "undefined") {
|
||||||
localStorage.setItem("userTOTPEnabled", data.totp_enabled ? "true" : "false");
|
localStorage.setItem("userTOTPEnabled", data.totp_enabled ? "true" : "false");
|
||||||
@@ -251,55 +251,71 @@ function checkAuthentication(showLoginToast = true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------- Authentication Submission ----------------- */
|
/* ----------------- Authentication Submission ----------------- */
|
||||||
function submitLogin(data) {
|
async function submitLogin(data) {
|
||||||
setLastLoginData(data);
|
setLastLoginData(data);
|
||||||
window.__lastLoginData = data;
|
window.__lastLoginData = data;
|
||||||
|
|
||||||
sendRequest("api/auth/auth.php", "POST", data, { "X-CSRF-Token": window.csrfToken })
|
try {
|
||||||
.then(response => {
|
// ─── 1) Get CSRF for the initial auth call ───
|
||||||
if (response.success || response.status === "ok") {
|
let res = await fetch("/api/auth/token.php", { credentials: "include" });
|
||||||
sessionStorage.setItem("welcomeMessage", "Welcome back, " + data.username + "!");
|
if (!res.ok) throw new Error("Could not fetch CSRF token");
|
||||||
// Fetch and update permissions, then reload.
|
window.csrfToken = (await res.json()).csrf_token;
|
||||||
sendRequest("api/getUserPermissions.php", "GET")
|
|
||||||
.then(permissionData => {
|
// ─── 2) Send credentials ───
|
||||||
if (permissionData && typeof permissionData === "object") {
|
const response = await sendRequest(
|
||||||
localStorage.setItem("folderOnly", permissionData.folderOnly ? "true" : "false");
|
"/api/auth/auth.php",
|
||||||
localStorage.setItem("readOnly", permissionData.readOnly ? "true" : "false");
|
"POST",
|
||||||
localStorage.setItem("disableUpload", permissionData.disableUpload ? "true" : "false");
|
data,
|
||||||
}
|
{ "X-CSRF-Token": window.csrfToken }
|
||||||
})
|
);
|
||||||
.catch(() => {
|
|
||||||
// ignore permission‐fetch errors
|
// ─── 3a) Full login (no TOTP) ───
|
||||||
})
|
if (response.success || response.status === "ok") {
|
||||||
.finally(() => {
|
sessionStorage.setItem("welcomeMessage", "Welcome back, " + data.username + "!");
|
||||||
window.location.reload();
|
// … fetch permissions & reload …
|
||||||
});
|
try {
|
||||||
} else if (response.totp_required) {
|
const perm = await sendRequest("/api/getUserPermissions.php", "GET");
|
||||||
openTOTPLoginModal();
|
if (perm && typeof perm === "object") {
|
||||||
} else if (response.error && response.error.includes("Too many failed login attempts")) {
|
localStorage.setItem("folderOnly", perm.folderOnly ? "true" : "false");
|
||||||
showToast(response.error);
|
localStorage.setItem("readOnly", perm.readOnly ? "true" : "false");
|
||||||
const loginButton = document.querySelector("#authForm button[type='submit']");
|
localStorage.setItem("disableUpload",perm.disableUpload? "true" : "false");
|
||||||
if (loginButton) {
|
|
||||||
loginButton.disabled = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
loginButton.disabled = false;
|
|
||||||
showToast("You can now try logging in again.");
|
|
||||||
}, 30 * 60 * 1000);
|
|
||||||
}
|
}
|
||||||
} else {
|
} catch {}
|
||||||
showToast("Login failed: " + (response.error || "Unknown error"));
|
return window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 3b) TOTP required ───
|
||||||
|
if (response.totp_required) {
|
||||||
|
// **Refresh** CSRF before the TOTP verify call
|
||||||
|
res = await fetch("/api/auth/token.php", { credentials: "include" });
|
||||||
|
if (res.ok) {
|
||||||
|
window.csrfToken = (await res.json()).csrf_token;
|
||||||
}
|
}
|
||||||
})
|
// now open the modal—any totp_verify fetch from here on will use the new token
|
||||||
.catch(err => {
|
return openTOTPLoginModal();
|
||||||
// err may be an Error object or a string
|
}
|
||||||
let msg = "Unknown error";
|
|
||||||
if (err && typeof err === "object") {
|
// ─── 3c) Too many attempts ───
|
||||||
msg = err.error || err.message || msg;
|
if (response.error && response.error.includes("Too many failed login attempts")) {
|
||||||
} else if (typeof err === "string") {
|
showToast(response.error);
|
||||||
msg = err;
|
const btn = document.querySelector("#authForm button[type='submit']");
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.disabled = false;
|
||||||
|
showToast("You can now try logging in again.");
|
||||||
|
}, 30 * 60 * 1000);
|
||||||
}
|
}
|
||||||
showToast(`Login failed: ${msg}`);
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// ─── 3d) Other failures ───
|
||||||
|
showToast("Login failed: " + (response.error || "Unknown error"));
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err.message || err.error || "Unknown error";
|
||||||
|
showToast(`Login failed: ${msg}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.submitLogin = submitLogin;
|
window.submitLogin = submitLogin;
|
||||||
@@ -327,7 +343,7 @@ function closeRemoveUserModal() {
|
|||||||
|
|
||||||
function loadUserList() {
|
function loadUserList() {
|
||||||
// Updated path: from "getUsers.php" to "api/getUsers.php"
|
// Updated path: from "getUsers.php" to "api/getUsers.php"
|
||||||
fetch("api/getUsers.php", { credentials: "include" })
|
fetch("/api/getUsers.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
// Assuming the endpoint returns an array of users.
|
// Assuming the endpoint returns an array of users.
|
||||||
@@ -368,7 +384,7 @@ function initAuth() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
document.getElementById("logoutBtn").addEventListener("click", function () {
|
document.getElementById("logoutBtn").addEventListener("click", function () {
|
||||||
fetch("api/auth/logout.php", {
|
fetch("/api/auth/logout.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "X-CSRF-Token": window.csrfToken }
|
headers: { "X-CSRF-Token": window.csrfToken }
|
||||||
@@ -387,7 +403,7 @@ function initAuth() {
|
|||||||
showToast("Username and password are required!");
|
showToast("Username and password are required!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let url = "api/addUser.php";
|
let url = "/api/addUser.php";
|
||||||
if (window.setupMode) url += "?setup=1";
|
if (window.setupMode) url += "?setup=1";
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -422,7 +438,7 @@ function initAuth() {
|
|||||||
}
|
}
|
||||||
const confirmed = await showCustomConfirmModal("Are you sure you want to delete user " + usernameToRemove + "?");
|
const confirmed = await showCustomConfirmModal("Are you sure you want to delete user " + usernameToRemove + "?");
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
fetch("api/removeUser.php", {
|
fetch("/api/removeUser.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/json", "X-CSRF-Token": window.csrfToken },
|
headers: { "Content-Type": "application/json", "X-CSRF-Token": window.csrfToken },
|
||||||
@@ -461,7 +477,7 @@ function initAuth() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = { oldPassword, newPassword, confirmPassword };
|
const data = { oldPassword, newPassword, confirmPassword };
|
||||||
fetch("api/changePassword.php", {
|
fetch("/api/changePassword.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/json", "X-CSRF-Token": window.csrfToken },
|
headers: { "Content-Type": "application/json", "X-CSRF-Token": window.csrfToken },
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import { sendRequest } from './networkUtils.js';
|
|||||||
import { t, applyTranslations, setLocale } from './i18n.js';
|
import { t, applyTranslations, setLocale } from './i18n.js';
|
||||||
import { loadAdminConfigFunc } from './auth.js';
|
import { loadAdminConfigFunc } from './auth.js';
|
||||||
|
|
||||||
const version = "v1.2.0";
|
const version = "v1.2.1"; // Update this version string as needed
|
||||||
// Use t() for the admin panel title. (Make sure t("admin_panel") returns "Admin Panel" in English.)
|
|
||||||
const adminTitle = `${t("admin_panel")} <small style="font-size: 12px; color: gray;">${version}</small>`;
|
const adminTitle = `${t("admin_panel")} <small style="font-size: 12px; color: gray;">${version}</small>`;
|
||||||
|
|
||||||
let lastLoginData = null;
|
let lastLoginData = null;
|
||||||
@@ -84,7 +83,7 @@ export function openTOTPLoginModal() {
|
|||||||
showToast(t("please_enter_recovery_code"));
|
showToast(t("please_enter_recovery_code"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("api/totp_recover.php", {
|
fetch("/api/totp_recover.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -110,36 +109,47 @@ export function openTOTPLoginModal() {
|
|||||||
// TOTP submission
|
// TOTP submission
|
||||||
const totpInput = document.getElementById("totpLoginInput");
|
const totpInput = document.getElementById("totpLoginInput");
|
||||||
totpInput.focus();
|
totpInput.focus();
|
||||||
totpInput.addEventListener("input", function () {
|
|
||||||
|
totpInput.addEventListener("input", async function () {
|
||||||
const code = this.value.trim();
|
const code = this.value.trim();
|
||||||
if (code.length === 6) {
|
if (code.length !== 6) {
|
||||||
fetch("api/totp_verify.php", {
|
|
||||||
method: "POST",
|
return;
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-CSRF-Token": window.csrfToken
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ totp_code: code })
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(json => {
|
|
||||||
if (json.status === "ok") {
|
|
||||||
window.location.href = "/index.html";
|
|
||||||
} else {
|
|
||||||
showToast(json.message || t("totp_verification_failed"));
|
|
||||||
this.value = "";
|
|
||||||
totpLoginModal.style.display = "flex";
|
|
||||||
totpInput.focus();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showToast(t("totp_verification_failed"));
|
|
||||||
this.value = "";
|
|
||||||
totpLoginModal.style.display = "flex";
|
|
||||||
totpInput.focus();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tokenRes = await fetch("/api/auth/token.php", {
|
||||||
|
credentials: "include"
|
||||||
|
});
|
||||||
|
if (!tokenRes.ok) {
|
||||||
|
showToast(t("totp_verification_failed"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.csrfToken = (await tokenRes.json()).csrf_token;
|
||||||
|
|
||||||
|
const res = await fetch("/api/totp_verify.php", {
|
||||||
|
method: "POST",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-CSRF-Token": window.csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ totp_code: code })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.status === "ok") {
|
||||||
|
window.location.href = "/index.html";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showToast(json.message || t("totp_verification_failed"));
|
||||||
|
} else {
|
||||||
|
showToast(t("totp_verification_failed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.value = "";
|
||||||
|
totpLoginModal.style.display = "flex";
|
||||||
|
this.focus();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Re-open existing modal
|
// Re-open existing modal
|
||||||
@@ -241,7 +251,7 @@ export function openUserPanel() {
|
|||||||
totpCheckbox.checked = localStorage.getItem("userTOTPEnabled") === "true";
|
totpCheckbox.checked = localStorage.getItem("userTOTPEnabled") === "true";
|
||||||
totpCheckbox.addEventListener("change", function () {
|
totpCheckbox.addEventListener("change", function () {
|
||||||
localStorage.setItem("userTOTPEnabled", this.checked ? "true" : "false");
|
localStorage.setItem("userTOTPEnabled", this.checked ? "true" : "false");
|
||||||
fetch("api/updateUserPanel.php", {
|
fetch("/api/updateUserPanel.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: { "Content-Type": "application/json", "X-CSRF-Token": window.csrfToken },
|
headers: { "Content-Type": "application/json", "X-CSRF-Token": window.csrfToken },
|
||||||
@@ -354,13 +364,24 @@ export function openTOTPModal() {
|
|||||||
closeTOTPModal(true);
|
closeTOTPModal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("confirmTOTPBtn").addEventListener("click", function () {
|
document.getElementById("confirmTOTPBtn").addEventListener("click", async function () {
|
||||||
const code = document.getElementById("totpConfirmInput").value.trim();
|
const code = document.getElementById("totpConfirmInput").value.trim();
|
||||||
if (code.length !== 6) {
|
if (code.length !== 6) {
|
||||||
showToast(t("please_enter_valid_code"));
|
showToast(t("please_enter_valid_code"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("api/totp_verify.php", {
|
|
||||||
|
const tokenRes = await fetch("/api/auth/token.php", {
|
||||||
|
credentials: "include"
|
||||||
|
});
|
||||||
|
if (!tokenRes.ok) {
|
||||||
|
showToast(t("error_verifying_totp_code"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { csrf_token } = await tokenRes.json();
|
||||||
|
window.csrfToken = csrf_token;
|
||||||
|
|
||||||
|
const verifyRes = await fetch("/api/totp_verify.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -368,36 +389,40 @@ export function openTOTPModal() {
|
|||||||
"X-CSRF-Token": window.csrfToken
|
"X-CSRF-Token": window.csrfToken
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ totp_code: code })
|
body: JSON.stringify({ totp_code: code })
|
||||||
})
|
});
|
||||||
.then(r => r.json())
|
|
||||||
.then(result => {
|
if (!verifyRes.ok) {
|
||||||
if (result.status === 'ok') {
|
showToast(t("totp_verification_failed"));
|
||||||
showToast(t("totp_enabled_successfully"));
|
return;
|
||||||
// After successful TOTP verification, fetch the recovery code
|
}
|
||||||
fetch("api/totp_saveCode.php", {
|
const result = await verifyRes.json();
|
||||||
method: "POST",
|
if (result.status !== "ok") {
|
||||||
credentials: "include",
|
showToast(result.message || t("totp_verification_failed"));
|
||||||
headers: {
|
return;
|
||||||
"Content-Type": "application/json",
|
}
|
||||||
"X-CSRF-Token": window.csrfToken
|
|
||||||
}
|
showToast(t("totp_enabled_successfully"));
|
||||||
})
|
|
||||||
.then(r => r.json())
|
const saveRes = await fetch("/api/totp_saveCode.php", {
|
||||||
.then(data => {
|
method: "POST",
|
||||||
if (data.status === 'ok' && data.recoveryCode) {
|
credentials: "include",
|
||||||
// Show the recovery code in a secure modal
|
headers: {
|
||||||
showRecoveryCodeModal(data.recoveryCode);
|
"X-CSRF-Token": window.csrfToken
|
||||||
} else {
|
}
|
||||||
showToast(t("error_generating_recovery_code") + ": " + (data.message || t("unknown_error")));
|
});
|
||||||
}
|
if (!saveRes.ok) {
|
||||||
})
|
showToast(t("error_generating_recovery_code"));
|
||||||
.catch(() => { showToast(t("error_generating_recovery_code")); });
|
closeTOTPModal(false);
|
||||||
closeTOTPModal(false);
|
return;
|
||||||
} else {
|
}
|
||||||
showToast(t("totp_verification_failed") + ": " + (result.message || t("invalid_code")));
|
const data = await saveRes.json();
|
||||||
}
|
if (data.status === "ok" && data.recoveryCode) {
|
||||||
})
|
showRecoveryCodeModal(data.recoveryCode);
|
||||||
.catch(() => { showToast(t("error_verifying_totp_code")); });
|
} else {
|
||||||
|
showToast(t("error_generating_recovery_code") + ": " + (data.message || t("unknown_error")));
|
||||||
|
}
|
||||||
|
|
||||||
|
closeTOTPModal(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus the input and attach enter key listener
|
// Focus the input and attach enter key listener
|
||||||
@@ -438,7 +463,7 @@ export function openTOTPModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadTOTPQRCode() {
|
function loadTOTPQRCode() {
|
||||||
fetch("api/totp_setup.php", {
|
fetch("/api/totp_setup.php", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -477,7 +502,7 @@ export function closeTOTPModal(disable = true) {
|
|||||||
localStorage.setItem("userTOTPEnabled", "false");
|
localStorage.setItem("userTOTPEnabled", "false");
|
||||||
}
|
}
|
||||||
// Call endpoint to remove the TOTP secret from the user's record
|
// Call endpoint to remove the TOTP secret from the user's record
|
||||||
fetch("api/totp_disable.php", {
|
fetch("/api/totp_disable.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -563,7 +588,7 @@ function showCustomConfirmModal(message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function openAdminPanel() {
|
export function openAdminPanel() {
|
||||||
fetch("api/admin/getConfig.php", { credentials: "include" })
|
fetch("/api/admin/getConfig.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(config => {
|
.then(config => {
|
||||||
if (config.header_title) {
|
if (config.header_title) {
|
||||||
@@ -725,7 +750,7 @@ export function openAdminPanel() {
|
|||||||
const disableBasicAuth = disableBasicAuthCheckbox.checked;
|
const disableBasicAuth = disableBasicAuthCheckbox.checked;
|
||||||
const disableOIDCLogin = disableOIDCLoginCheckbox.checked;
|
const disableOIDCLogin = disableOIDCLoginCheckbox.checked;
|
||||||
const globalOtpauthUrl = document.getElementById("globalOtpauthUrl").value.trim();
|
const globalOtpauthUrl = document.getElementById("globalOtpauthUrl").value.trim();
|
||||||
sendRequest("api/admin/updateConfig.php", "POST", {
|
sendRequest("/api/admin/updateConfig.php", "POST", {
|
||||||
header_title: newHeaderTitle,
|
header_title: newHeaderTitle,
|
||||||
oidc: newOIDCConfig,
|
oidc: newOIDCConfig,
|
||||||
disableFormLogin,
|
disableFormLogin,
|
||||||
@@ -898,7 +923,7 @@ export function openUserPermissionsModal() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
// Send the permissionsData to the server.
|
// Send the permissionsData to the server.
|
||||||
sendRequest("api/updateUserPermissions.php", "POST", { permissions: permissionsData }, { "X-CSRF-Token": window.csrfToken })
|
sendRequest("/api/updateUserPermissions.php", "POST", { permissions: permissionsData }, { "X-CSRF-Token": window.csrfToken })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
showToast(t("user_permissions_updated_successfully"));
|
showToast(t("user_permissions_updated_successfully"));
|
||||||
@@ -924,11 +949,11 @@ function loadUserPermissionsList() {
|
|||||||
listContainer.innerHTML = "";
|
listContainer.innerHTML = "";
|
||||||
|
|
||||||
// First, fetch the current permissions from the server.
|
// First, fetch the current permissions from the server.
|
||||||
fetch("api/getUserPermissions.php", { credentials: "include" })
|
fetch("/api/getUserPermissions.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(permissionsData => {
|
.then(permissionsData => {
|
||||||
// Then, fetch the list of users.
|
// Then, fetch the list of users.
|
||||||
return fetch("api/getUsers.php", { credentials: "include" })
|
return fetch("/api/getUsers.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(usersData => {
|
.then(usersData => {
|
||||||
const users = Array.isArray(usersData) ? usersData : (usersData.users || []);
|
const users = Array.isArray(usersData) ? usersData : (usersData.users || []);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const confirmDelete = document.getElementById("confirmDeleteFiles");
|
const confirmDelete = document.getElementById("confirmDeleteFiles");
|
||||||
if (confirmDelete) {
|
if (confirmDelete) {
|
||||||
confirmDelete.addEventListener("click", function () {
|
confirmDelete.addEventListener("click", function () {
|
||||||
fetch("api/file/deleteFiles.php", {
|
fetch("/api/file/deleteFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -178,7 +178,7 @@ export function handleExtractZipSelected(e) {
|
|||||||
// Show the progress modal.
|
// Show the progress modal.
|
||||||
document.getElementById("downloadProgressModal").style.display = "block";
|
document.getElementById("downloadProgressModal").style.display = "block";
|
||||||
|
|
||||||
fetch("api/file/extractZip.php", {
|
fetch("/api/file/extractZip.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -245,7 +245,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
console.log("Download confirmed. Showing progress modal.");
|
console.log("Download confirmed. Showing progress modal.");
|
||||||
document.getElementById("downloadProgressModal").style.display = "block";
|
document.getElementById("downloadProgressModal").style.display = "block";
|
||||||
const folder = window.currentFolder || "root";
|
const folder = window.currentFolder || "root";
|
||||||
fetch("api/file/downloadZip.php", {
|
fetch("/api/file/downloadZip.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -309,7 +309,7 @@ export async function loadCopyMoveFolderListForModal(dropdownId) {
|
|||||||
if (window.userFolderOnly) {
|
if (window.userFolderOnly) {
|
||||||
const username = localStorage.getItem("username") || "root";
|
const username = localStorage.getItem("username") || "root";
|
||||||
try {
|
try {
|
||||||
const response = await fetch("api/folder/getFolderList.php?restricted=1");
|
const response = await fetch("/api/folder/getFolderList.php?restricted=1");
|
||||||
let folders = await response.json();
|
let folders = await response.json();
|
||||||
if (Array.isArray(folders) && folders.length && typeof folders[0] === "object" && folders[0].folder) {
|
if (Array.isArray(folders) && folders.length && typeof folders[0] === "object" && folders[0].folder) {
|
||||||
folders = folders.map(item => item.folder);
|
folders = folders.map(item => item.folder);
|
||||||
@@ -339,7 +339,7 @@ export async function loadCopyMoveFolderListForModal(dropdownId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("api/folder/getFolderList.php");
|
const response = await fetch("/api/folder/getFolderList.php");
|
||||||
let folders = await response.json();
|
let folders = await response.json();
|
||||||
if (Array.isArray(folders) && folders.length && typeof folders[0] === "object" && folders[0].folder) {
|
if (Array.isArray(folders) && folders.length && typeof folders[0] === "object" && folders[0].folder) {
|
||||||
folders = folders.map(item => item.folder);
|
folders = folders.map(item => item.folder);
|
||||||
@@ -397,7 +397,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
showToast("Error: Cannot copy files to the same folder.");
|
showToast("Error: Cannot copy files to the same folder.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("api/file/copyFiles.php", {
|
fetch("/api/file/copyFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -448,7 +448,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
showToast("Error: Cannot move files to the same folder.");
|
showToast("Error: Cannot move files to the same folder.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("api/file/moveFiles.php", {
|
fetch("/api/file/moveFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -514,7 +514,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const folderUsed = window.fileFolder;
|
const folderUsed = window.fileFolder;
|
||||||
fetch("api/file/renameFile.php", {
|
fetch("/api/file/renameFile.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ export function folderDropHandler(event) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!dragData || !dragData.fileName) return;
|
if (!dragData || !dragData.fileName) return;
|
||||||
fetch("api/file/moveFiles.php", {
|
fetch("/api/file/moveFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export function saveFile(fileName, folder) {
|
|||||||
content: editor.getValue(),
|
content: editor.getValue(),
|
||||||
folder: folderUsed
|
folder: folderUsed
|
||||||
};
|
};
|
||||||
fetch("api/file/saveFile.php", {
|
fetch("/api/file/saveFile.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export function loadFileList(folderParam) {
|
|||||||
fileListContainer.style.visibility = "hidden";
|
fileListContainer.style.visibility = "hidden";
|
||||||
fileListContainer.innerHTML = "<div class='loader'>Loading files...</div>";
|
fileListContainer.innerHTML = "<div class='loader'>Loading files...</div>";
|
||||||
|
|
||||||
return fetch("api/file/getFileList.php?folder=" + encodeURIComponent(folder) + "&recursive=1&t=" + new Date().getTime())
|
return fetch("/api/file/getFileList.php?folder=" + encodeURIComponent(folder) + "&recursive=1&t=" + new Date().getTime())
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
showToast("Session expired. Please log in again.");
|
showToast("Session expired. Please log in again.");
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function openShareModal(file, folder) {
|
|||||||
document.getElementById("generateShareLinkBtn").addEventListener("click", () => {
|
document.getElementById("generateShareLinkBtn").addEventListener("click", () => {
|
||||||
const expiration = document.getElementById("shareExpiration").value;
|
const expiration = document.getElementById("shareExpiration").value;
|
||||||
const password = document.getElementById("sharePassword").value;
|
const password = document.getElementById("sharePassword").value;
|
||||||
fetch("api/file/createShareLink.php", {
|
fetch("/api/file/createShareLink.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ function removeGlobalTag(tagName) {
|
|||||||
|
|
||||||
// NEW: Save global tag removal to the server.
|
// NEW: Save global tag removal to the server.
|
||||||
function saveGlobalTagRemoval(tagName) {
|
function saveGlobalTagRemoval(tagName) {
|
||||||
fetch("api/file/saveFileTag.php", {
|
fetch("/api/file/saveFileTag.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -316,7 +316,7 @@ if (localStorage.getItem('globalTags')) {
|
|||||||
|
|
||||||
// New function to load global tags from the server's persistent JSON.
|
// New function to load global tags from the server's persistent JSON.
|
||||||
export function loadGlobalTags() {
|
export function loadGlobalTags() {
|
||||||
fetch("api/file/getFileTag.php", { credentials: "include" })
|
fetch("/api/file/getFileTag.php", { credentials: "include" })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// If the file doesn't exist, assume there are no global tags.
|
// If the file doesn't exist, assume there are no global tags.
|
||||||
@@ -449,7 +449,7 @@ export function saveFileTags(file, deleteGlobal = false, tagToDelete = null) {
|
|||||||
payload.deleteGlobal = true;
|
payload.deleteGlobal = true;
|
||||||
payload.tagToDelete = tagToDelete;
|
payload.tagToDelete = tagToDelete;
|
||||||
}
|
}
|
||||||
fetch("api/file/saveFileTag.php", {
|
fetch("/api/file/saveFileTag.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ function breadcrumbDropHandler(e) {
|
|||||||
}
|
}
|
||||||
const filesToMove = dragData.files ? dragData.files : (dragData.fileName ? [dragData.fileName] : []);
|
const filesToMove = dragData.files ? dragData.files : (dragData.fileName ? [dragData.fileName] : []);
|
||||||
if (filesToMove.length === 0) return;
|
if (filesToMove.length === 0) return;
|
||||||
fetch("api/file/moveFiles.php", {
|
fetch("/api/file/moveFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -202,7 +202,7 @@ function checkUserFolderPermission() {
|
|||||||
window.currentFolder = username;
|
window.currentFolder = username;
|
||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
return fetch("api/getUserPermissions.php", { credentials: "include" })
|
return fetch("/api/getUserPermissions.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(permissionsData => {
|
.then(permissionsData => {
|
||||||
console.log("checkUserFolderPermission: permissionsData =", permissionsData);
|
console.log("checkUserFolderPermission: permissionsData =", permissionsData);
|
||||||
@@ -302,7 +302,7 @@ function folderDropHandler(event) {
|
|||||||
}
|
}
|
||||||
const filesToMove = dragData.files ? dragData.files : (dragData.fileName ? [dragData.fileName] : []);
|
const filesToMove = dragData.files ? dragData.files : (dragData.fileName ? [dragData.fileName] : []);
|
||||||
if (filesToMove.length === 0) return;
|
if (filesToMove.length === 0) return;
|
||||||
fetch("api/file/moveFiles.php", {
|
fetch("/api/file/moveFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -353,7 +353,7 @@ export async function loadFolderTree(selectedFolder) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build fetch URL.
|
// Build fetch URL.
|
||||||
let fetchUrl = 'api/folder/getFolderList.php';
|
let fetchUrl = '/api/folder/getFolderList.php';
|
||||||
if (window.userFolderOnly) {
|
if (window.userFolderOnly) {
|
||||||
fetchUrl += '?restricted=1';
|
fetchUrl += '?restricted=1';
|
||||||
}
|
}
|
||||||
@@ -547,7 +547,7 @@ document.getElementById("submitRenameFolder").addEventListener("click", function
|
|||||||
showToast("CSRF token not loaded yet! Please try again.");
|
showToast("CSRF token not loaded yet! Please try again.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("api/folder/renameFolder.php", {
|
fetch("/api/folder/renameFolder.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -592,7 +592,7 @@ attachEnterKeyListener("deleteFolderModal", "confirmDeleteFolder");
|
|||||||
document.getElementById("confirmDeleteFolder").addEventListener("click", function () {
|
document.getElementById("confirmDeleteFolder").addEventListener("click", function () {
|
||||||
const selectedFolder = window.currentFolder || "root";
|
const selectedFolder = window.currentFolder || "root";
|
||||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
fetch("api/folder/deleteFolder.php", {
|
fetch("/api/folder/deleteFolder.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -639,7 +639,7 @@ document.getElementById("submitCreateFolder").addEventListener("click", function
|
|||||||
fullFolderName = selectedFolder + "/" + folderInput;
|
fullFolderName = selectedFolder + "/" + folderInput;
|
||||||
}
|
}
|
||||||
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
fetch("api/folder/createFolder.php", {
|
fetch("/api/folder/createFolder.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function openFolderShareModal(folder) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Post to the createFolderShareLink endpoint.
|
// Post to the createFolderShareLink endpoint.
|
||||||
fetch("api/folder/createShareFolderLink.php", {
|
fetch("/api/folder/createShareFolderLink.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { t, applyTranslations, setLocale } from './i18n.js';
|
|||||||
|
|
||||||
// Remove the retry logic version and just use loadCsrfToken directly:
|
// Remove the retry logic version and just use loadCsrfToken directly:
|
||||||
function loadCsrfToken() {
|
function loadCsrfToken() {
|
||||||
return fetch('api/auth/token.php', { credentials: 'include' })
|
return fetch('/api/auth/token.php', { credentials: 'include' })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Token fetch failed with status: " + response.status);
|
throw new Error("Token fetch failed with status: " + response.status);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function setupTrashRestoreDelete() {
|
|||||||
showToast(t("no_trash_selected"));
|
showToast(t("no_trash_selected"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("api/file/restoreFiles.php", {
|
fetch("/api/file/restoreFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -109,7 +109,7 @@ export function setupTrashRestoreDelete() {
|
|||||||
showToast(t("trash_empty"));
|
showToast(t("trash_empty"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch("api/file/restoreFiles.php", {
|
fetch("/api/file/restoreFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -151,7 +151,7 @@ export function setupTrashRestoreDelete() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showConfirm("Are you sure you want to permanently delete the selected trash items?", () => {
|
showConfirm("Are you sure you want to permanently delete the selected trash items?", () => {
|
||||||
fetch("api/file/deleteTrashFiles.php", {
|
fetch("/api/file/deleteTrashFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -186,7 +186,7 @@ export function setupTrashRestoreDelete() {
|
|||||||
if (deleteAllBtn) {
|
if (deleteAllBtn) {
|
||||||
deleteAllBtn.addEventListener("click", () => {
|
deleteAllBtn.addEventListener("click", () => {
|
||||||
showConfirm("Are you sure you want to permanently delete all trash items? This action cannot be undone.", () => {
|
showConfirm("Are you sure you want to permanently delete all trash items? This action cannot be undone.", () => {
|
||||||
fetch("api/file/deleteTrashFiles.php", {
|
fetch("/api/file/deleteTrashFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -234,7 +234,7 @@ export function setupTrashRestoreDelete() {
|
|||||||
* Loads trash items from the server and updates the restore modal list.
|
* Loads trash items from the server and updates the restore modal list.
|
||||||
*/
|
*/
|
||||||
export function loadTrashItems() {
|
export function loadTrashItems() {
|
||||||
fetch("api/file/getTrashItems.php", { credentials: "include" })
|
fetch("/api/file/getTrashItems.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(trashItems => {
|
.then(trashItems => {
|
||||||
const listContainer = document.getElementById("restoreFilesList");
|
const listContainer = document.getElementById("restoreFilesList");
|
||||||
@@ -271,7 +271,7 @@ export function loadTrashItems() {
|
|||||||
* Automatically purges (permanently deletes) trash items older than 3 days.
|
* Automatically purges (permanently deletes) trash items older than 3 days.
|
||||||
*/
|
*/
|
||||||
function autoPurgeOldTrash() {
|
function autoPurgeOldTrash() {
|
||||||
fetch("api/file/getTrashItems.php", { credentials: "include" })
|
fetch("/api/file/getTrashItems.php", { credentials: "include" })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(trashItems => {
|
.then(trashItems => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -279,7 +279,7 @@ function autoPurgeOldTrash() {
|
|||||||
const oldItems = trashItems.filter(item => (now - (item.trashedAt * 1000)) > threeDays);
|
const oldItems = trashItems.filter(item => (now - (item.trashedAt * 1000)) > threeDays);
|
||||||
if (oldItems.length > 0) {
|
if (oldItems.length > 0) {
|
||||||
const files = oldItems.map(item => item.trashName);
|
const files = oldItems.map(item => item.trashName);
|
||||||
fetch("api/file/deleteTrashFiles.php", {
|
fetch("/api/file/deleteTrashFiles.php", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ function removeChunkFolderRepeatedly(identifier, csrfToken, maxAttempts = 3, int
|
|||||||
// Prefix with "resumable_" to match your PHP regex.
|
// Prefix with "resumable_" to match your PHP regex.
|
||||||
params.append('folder', 'resumable_' + identifier);
|
params.append('folder', 'resumable_' + identifier);
|
||||||
params.append('csrf_token', csrfToken);
|
params.append('csrf_token', csrfToken);
|
||||||
fetch('api/upload/removeChunks.php', {
|
fetch('/api/upload/removeChunks.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -664,7 +664,7 @@ function submitFiles(allFiles) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
xhr.open("POST", "api/upload/upload.php", true);
|
xhr.open("POST", "/api/upload/upload.php", true);
|
||||||
xhr.setRequestHeader("X-CSRF-Token", window.csrfToken);
|
xhr.setRequestHeader("X-CSRF-Token", window.csrfToken);
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -238,22 +238,39 @@ class AuthController
|
|||||||
$token = bin2hex(random_bytes(32));
|
$token = bin2hex(random_bytes(32));
|
||||||
$expiry = time() + 30 * 24 * 60 * 60;
|
$expiry = time() + 30 * 24 * 60 * 60;
|
||||||
$all = [];
|
$all = [];
|
||||||
|
|
||||||
if (file_exists($tokFile)) {
|
if (file_exists($tokFile)) {
|
||||||
$dec = decryptData(file_get_contents($tokFile), $GLOBALS['encryptionKey']);
|
$dec = decryptData(file_get_contents($tokFile), $GLOBALS['encryptionKey']);
|
||||||
$all = json_decode($dec, true) ?: [];
|
$all = json_decode($dec, true) ?: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$all[$token] = [
|
$all[$token] = [
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
'expiry' => $expiry,
|
'expiry' => $expiry,
|
||||||
'isAdmin' => $_SESSION['isAdmin']
|
'isAdmin' => $_SESSION['isAdmin']
|
||||||
];
|
];
|
||||||
|
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
$tokFile,
|
$tokFile,
|
||||||
encryptData(json_encode($all, JSON_PRETTY_PRINT), $GLOBALS['encryptionKey']),
|
encryptData(json_encode($all, JSON_PRETTY_PRINT), $GLOBALS['encryptionKey']),
|
||||||
LOCK_EX
|
LOCK_EX
|
||||||
);
|
);
|
||||||
$secure = (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
|
||||||
|
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
||||||
|
|
||||||
setcookie('remember_me_token', $token, $expiry, '/', '', $secure, true);
|
setcookie('remember_me_token', $token, $expiry, '/', '', $secure, true);
|
||||||
|
|
||||||
|
setcookie(
|
||||||
|
session_name(),
|
||||||
|
session_id(),
|
||||||
|
$expiry,
|
||||||
|
'/',
|
||||||
|
'',
|
||||||
|
$secure,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
session_regenerate_id(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
|
|||||||
@@ -847,104 +847,147 @@ class UserController
|
|||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public function verifyTOTP()
|
public function verifyTOTP()
|
||||||
{
|
{
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
// Set CSP headers if desired:
|
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self';");
|
||||||
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self';");
|
|
||||||
|
// Rate‑limit
|
||||||
// Rate‑limit: initialize totp_failures if not set.
|
if (!isset($_SESSION['totp_failures'])) {
|
||||||
if (!isset($_SESSION['totp_failures'])) {
|
$_SESSION['totp_failures'] = 0;
|
||||||
$_SESSION['totp_failures'] = 0;
|
}
|
||||||
}
|
if ($_SESSION['totp_failures'] >= 5) {
|
||||||
if ($_SESSION['totp_failures'] >= 5) {
|
http_response_code(429);
|
||||||
http_response_code(429);
|
echo json_encode(['status' => 'error', 'message' => 'Too many TOTP attempts. Please try again later.']);
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Too many TOTP attempts. Please try again later.']);
|
exit;
|
||||||
exit;
|
}
|
||||||
}
|
|
||||||
|
// Must be authenticated OR pending login
|
||||||
// Must be authenticated OR have a pending login.
|
if (!((!empty($_SESSION['authenticated'])) || isset($_SESSION['pending_login_user']))) {
|
||||||
if (!((isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true) || isset($_SESSION['pending_login_user']))) {
|
http_response_code(403);
|
||||||
http_response_code(403);
|
echo json_encode(['status' => 'error', 'message' => 'Not authenticated']);
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Not authenticated']);
|
exit;
|
||||||
exit;
|
}
|
||||||
}
|
|
||||||
|
// CSRF check
|
||||||
// CSRF check.
|
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
||||||
$headersArr = array_change_key_case(getallheaders(), CASE_LOWER);
|
$csrfHeader = $headersArr['x-csrf-token'] ?? '';
|
||||||
$csrfHeader = isset($headersArr['x-csrf-token']) ? trim($headersArr['x-csrf-token']) : '';
|
if (empty($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
||||||
if (!isset($_SESSION['csrf_token']) || $csrfHeader !== $_SESSION['csrf_token']) {
|
http_response_code(403);
|
||||||
http_response_code(403);
|
echo json_encode(['status' => 'error', 'message' => 'Invalid CSRF token']);
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Invalid CSRF token']);
|
exit;
|
||||||
exit;
|
}
|
||||||
}
|
|
||||||
|
// Parse and validate input
|
||||||
// Parse input.
|
$inputData = json_decode(file_get_contents("php://input"), true);
|
||||||
$inputData = json_decode(file_get_contents("php://input"), true);
|
$code = trim($inputData['totp_code'] ?? '');
|
||||||
$code = trim($inputData['totp_code'] ?? '');
|
if (!preg_match('/^\d{6}$/', $code)) {
|
||||||
if (!preg_match('/^\d{6}$/', $code)) {
|
http_response_code(400);
|
||||||
http_response_code(400);
|
echo json_encode(['status' => 'error', 'message' => 'A valid 6-digit TOTP code is required']);
|
||||||
echo json_encode(['status' => 'error', 'message' => 'A valid 6-digit TOTP code is required']);
|
exit;
|
||||||
exit;
|
}
|
||||||
}
|
|
||||||
|
// TFA helper
|
||||||
// Create TFA object.
|
$tfa = new \RobThree\Auth\TwoFactorAuth(
|
||||||
$tfa = new \RobThree\Auth\TwoFactorAuth(
|
new \RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider(),
|
||||||
new \RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider(),
|
'FileRise', 6, 30, \RobThree\Auth\Algorithm::Sha1
|
||||||
'FileRise',
|
);
|
||||||
6,
|
|
||||||
30,
|
// Pending‑login flow (first password step passed)
|
||||||
\RobThree\Auth\Algorithm::Sha1
|
if (isset($_SESSION['pending_login_user'])) {
|
||||||
);
|
$username = $_SESSION['pending_login_user'];
|
||||||
|
$pendingSecret = $_SESSION['pending_login_secret'] ?? null;
|
||||||
// Check if we are in pending login flow.
|
$rememberMe = $_SESSION['pending_login_remember_me'] ?? false;
|
||||||
if (isset($_SESSION['pending_login_user'])) {
|
|
||||||
$username = $_SESSION['pending_login_user'];
|
if (!$pendingSecret || !$tfa->verifyCode($pendingSecret, $code)) {
|
||||||
$pendingSecret = $_SESSION['pending_login_secret'] ?? null;
|
$_SESSION['totp_failures']++;
|
||||||
if (!$pendingSecret || !$tfa->verifyCode($pendingSecret, $code)) {
|
http_response_code(400);
|
||||||
$_SESSION['totp_failures']++;
|
echo json_encode(['status' => 'error', 'message' => 'Invalid TOTP code']);
|
||||||
http_response_code(400);
|
exit;
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Invalid TOTP code']);
|
}
|
||||||
exit;
|
|
||||||
}
|
// === Issue “remember me” token if requested ===
|
||||||
// Successful pending login: finalize login.
|
if ($rememberMe) {
|
||||||
session_regenerate_id(true);
|
$tokFile = USERS_DIR . 'persistent_tokens.json';
|
||||||
$_SESSION['authenticated'] = true;
|
$token = bin2hex(random_bytes(32));
|
||||||
$_SESSION['username'] = $username;
|
$expiry = time() + 30 * 24 * 60 * 60;
|
||||||
// Set isAdmin based on user role.
|
$all = [];
|
||||||
$_SESSION['isAdmin'] = (userModel::getUserRole($username) === "1");
|
|
||||||
// Load additional permissions (e.g., folderOnly) as needed.
|
if (file_exists($tokFile)) {
|
||||||
$_SESSION['folderOnly'] = loadUserPermissions($username);
|
$dec = decryptData(file_get_contents($tokFile), $GLOBALS['encryptionKey']);
|
||||||
unset($_SESSION['pending_login_user'], $_SESSION['pending_login_secret'], $_SESSION['totp_failures']);
|
$all = json_decode($dec, true) ?: [];
|
||||||
echo json_encode(['status' => 'ok', 'message' => 'Login successful']);
|
}
|
||||||
exit;
|
$all[$token] = [
|
||||||
}
|
'username' => $username,
|
||||||
|
'expiry' => $expiry,
|
||||||
// Otherwise, we are in setup/verification flow.
|
'isAdmin' => $_SESSION['isAdmin']
|
||||||
$username = $_SESSION['username'] ?? '';
|
];
|
||||||
if (!$username) {
|
file_put_contents(
|
||||||
http_response_code(400);
|
$tokFile,
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Username not found in session']);
|
encryptData(json_encode($all, JSON_PRETTY_PRINT), $GLOBALS['encryptionKey']),
|
||||||
exit;
|
LOCK_EX
|
||||||
}
|
);
|
||||||
|
|
||||||
// Retrieve the user's TOTP secret from the model.
|
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
||||||
$totpSecret = userModel::getTOTPSecret($username);
|
|
||||||
if (!$totpSecret) {
|
// Persistent cookie
|
||||||
http_response_code(500);
|
setcookie('remember_me_token', $token, $expiry, '/', '', $secure, true);
|
||||||
echo json_encode(['status' => 'error', 'message' => 'TOTP secret not found. Please set up TOTP again.']);
|
|
||||||
exit;
|
// Re‑issue PHP session cookie
|
||||||
}
|
setcookie(
|
||||||
|
session_name(),
|
||||||
if (!$tfa->verifyCode($totpSecret, $code)) {
|
session_id(),
|
||||||
$_SESSION['totp_failures']++;
|
$expiry,
|
||||||
http_response_code(400);
|
'/',
|
||||||
echo json_encode(['status' => 'error', 'message' => 'Invalid TOTP code']);
|
'',
|
||||||
exit;
|
$secure,
|
||||||
}
|
true
|
||||||
|
);
|
||||||
// Successful verification.
|
}
|
||||||
unset($_SESSION['totp_failures']);
|
|
||||||
echo json_encode(['status' => 'ok', 'message' => 'TOTP successfully verified']);
|
// Finalize login
|
||||||
}
|
session_regenerate_id(true);
|
||||||
|
$_SESSION['authenticated'] = true;
|
||||||
|
$_SESSION['username'] = $username;
|
||||||
|
$_SESSION['isAdmin'] = (userModel::getUserRole($username) === "1");
|
||||||
|
$_SESSION['folderOnly'] = loadUserPermissions($username);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
unset(
|
||||||
|
$_SESSION['pending_login_user'],
|
||||||
|
$_SESSION['pending_login_secret'],
|
||||||
|
$_SESSION['pending_login_remember_me'],
|
||||||
|
$_SESSION['totp_failures']
|
||||||
|
);
|
||||||
|
|
||||||
|
echo json_encode(['status' => 'ok', 'message' => 'Login successful']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup/verification flow (not pending)
|
||||||
|
$username = $_SESSION['username'] ?? '';
|
||||||
|
if (!$username) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Username not found in session']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totpSecret = userModel::getTOTPSecret($username);
|
||||||
|
if (!$totpSecret) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'TOTP secret not found. Please set up TOTP again.']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$tfa->verifyCode($totpSecret, $code)) {
|
||||||
|
$_SESSION['totp_failures']++;
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['status' => 'error', 'message' => 'Invalid TOTP code']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Successful setup/verification
|
||||||
|
unset($_SESSION['totp_failures']);
|
||||||
|
echo json_encode(['status' => 'ok', 'message' => 'TOTP successfully verified']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user