diff --git a/auth.php b/auth.php index d6c9d45..231dcef 100644 --- a/auth.php +++ b/auth.php @@ -186,8 +186,8 @@ if (!preg_match('/^[A-Za-z0-9_\- ]+$/', $username)) { $user = authenticate($username, $password); if ($user !== false) { if (!empty($user['totp_secret'])) { - if (empty($data['totp_code'])) { - http_response_code(401); + // If TOTP code is missing or malformed, indicate that TOTP is required. + if (empty($data['totp_code']) || !preg_match('/^\d{6}$/', $data['totp_code'])) { echo json_encode([ "totp_required" => true, "message" => "TOTP code required" @@ -197,7 +197,6 @@ if ($user !== false) { $tfa = new \RobThree\Auth\TwoFactorAuth('FileRise'); $providedCode = trim($data['totp_code']); if (!$tfa->verifyCode($user['totp_secret'], $providedCode)) { - http_response_code(401); echo json_encode(["error" => "Invalid TOTP code"]); exit(); } @@ -232,10 +231,13 @@ if ($user !== false) { ]; $encryptedContent = encryptData(json_encode($persistentTokens, JSON_PRETTY_PRINT), $encryptionKey); file_put_contents($persistentTokensFile, $encryptedContent, LOCK_EX); + // Define $secure based on whether HTTPS is enabled + $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'); setcookie('remember_me_token', $token, $expiry, '/', '', $secure, true); } echo json_encode([ + "status" => "ok", "success" => "Login successful", "isAdmin" => $_SESSION["isAdmin"], "folderOnly"=> $_SESSION["folderOnly"], diff --git a/js/auth.js b/js/auth.js index 47cbe58..5c76af1 100644 --- a/js/auth.js +++ b/js/auth.js @@ -98,14 +98,14 @@ function loadAdminConfigFunc() { localStorage.setItem("disableFormLogin", config.loginOptions.disableFormLogin); localStorage.setItem("disableBasicAuth", config.loginOptions.disableBasicAuth); localStorage.setItem("disableOIDCLogin", config.loginOptions.disableOIDCLogin); - localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/FileRise?issuer=FileRise"); + localStorage.setItem("globalOtpauthUrl", config.globalOtpauthUrl || "otpauth://totp/{label}?secret={secret}&issuer=FileRise"); updateLoginOptionsUIFromStorage(); }) .catch(() => { localStorage.setItem("disableFormLogin", "false"); localStorage.setItem("disableBasicAuth", "false"); localStorage.setItem("disableOIDCLogin", "false"); - localStorage.setItem("globalOtpauthUrl", "otpauth://totp/FileRise?issuer=FileRise"); + localStorage.setItem("globalOtpauthUrl", "otpauth://totp/{label}?secret={secret}&issuer=FileRise"); updateLoginOptionsUIFromStorage(); }); } @@ -225,15 +225,15 @@ function checkAuthentication(showLoginToast = true) { function submitLogin(data) { setLastLoginData(data); window.__lastLoginData = data; - sendRequest("auth.php", "POST", data, { "X-CSRF-Token": window.csrfToken }) - .then(response => { - if (response.success) { - sessionStorage.setItem("welcomeMessage", "Welcome back, " + data.username + "!"); - window.location.reload(); - } else if (response.totp_required) { - openTOTPLoginModal(); - } else if (response.error && response.error.includes("Too many failed login attempts")) { - showToast(response.error); +sendRequest("auth.php", "POST", data, { "X-CSRF-Token": window.csrfToken }) + .then(response => { + if (response.success || response.status === "ok") { + sessionStorage.setItem("welcomeMessage", "Welcome back, " + data.username + "!"); + window.location.reload(); + } else if (response.totp_required) { + openTOTPLoginModal(); + } else if (response.error && response.error.includes("Too many failed login attempts")) { + showToast(response.error); const loginButton = document.getElementById("authForm").querySelector("button[type='submit']"); if (loginButton) { loginButton.disabled = true; diff --git a/js/authModals.js b/js/authModals.js index 78bbd8a..14818bf 100644 --- a/js/authModals.js +++ b/js/authModals.js @@ -49,15 +49,11 @@ export function openTOTPLoginModal() { totpInput.addEventListener("input", function () { const code = this.value.trim(); if (code.length === 6) { - // FORM-BASED LOGIN if (lastLoginData) { totpLoginModal.style.display = "none"; lastLoginData.totp_code = code; window.submitLogin(lastLoginData); - - // BASIC-AUTH / OIDC LOGIN } else { - // keep modal open until we know the result fetch("totp_verify.php", { method: "POST", credentials: "include", @@ -67,23 +63,23 @@ export function openTOTPLoginModal() { }, body: JSON.stringify({ totp_code: code }) }) - .then(res => res.json()) - .then(json => { - if (json.success) { - window.location.href = "index.html"; - } else { - showToast(json.error || json.message || "TOTP verification failed"); - this.value = ""; - totpLoginModal.style.display = "flex"; - totpInput.focus(); - } - }) - .catch(() => { - showToast("TOTP verification failed"); + .then(res => res.json()) + .then(json => { + if (json.status === "ok") { + window.location.href = "index.html"; + } else { + showToast(json.message || "TOTP verification failed"); this.value = ""; totpLoginModal.style.display = "flex"; totpInput.focus(); - }); + } + }) + .catch(() => { + showToast("TOTP verification failed"); + this.value = ""; + totpLoginModal.style.display = "flex"; + totpInput.focus(); + }); } } }); @@ -92,7 +88,6 @@ export function openTOTPLoginModal() { const modalContent = totpLoginModal.firstElementChild; modalContent.style.background = modalBg; modalContent.style.color = textColor; - // reset input if reopening const totpInput = document.getElementById("totpLoginInput"); if (totpInput) { totpInput.value = ""; @@ -191,10 +186,10 @@ export function openUserPanel() { } 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 = ` + 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; @@ -232,19 +227,17 @@ export function openTOTPModal() { `; 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", @@ -254,17 +247,16 @@ export function openTOTPModal() { }, 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."); }); + .then(r => r.json()) + .then(result => { + if (result.status === 'ok') { + showToast("TOTP successfully enabled."); + closeTOTPModal(false); + } else { + showToast("TOTP verification failed: " + (result.message || "Invalid code.")); + } + }) + .catch(() => { showToast("Error verifying TOTP code."); }); }); } else { totpModal.style.display = "flex"; @@ -508,7 +500,7 @@ export function openAdminPanel() { 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("globalOtpauthUrl").value = window.currentOIDCConfig.globalOtpauthUrl || 'otpauth://totp/{label}?secret={secret}&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; @@ -529,7 +521,7 @@ export function openAdminPanel() { 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("globalOtpauthUrl").value = window.currentOIDCConfig.globalOtpauthUrl || 'otpauth://totp/{label}?secret={secret}&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"; diff --git a/totp_verify.php b/totp_verify.php index 96d6322..edb90c2 100644 --- a/totp_verify.php +++ b/totp_verify.php @@ -4,19 +4,6 @@ require_once 'vendor/autoload.php'; require_once 'config.php'; -// Secure session cookie -session_set_cookie_params([ - 'lifetime' => 0, - 'path' => '/', - 'domain' => '', // your domain - 'secure' => true, // only over HTTPS - 'httponly' => true, - 'samesite' => 'Lax' -]); -if (session_status() !== PHP_SESSION_ACTIVE) { - session_start(); -} - // JSON + CSP header('Content-Type: application/json'); header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self';");