extend TOTP to basic auth & OIDC. Fix share btn galleryview.

This commit is contained in:
Ryan
2025-04-05 22:22:47 -04:00
committed by GitHub
parent 899b04e49a
commit 5100e8bf3b
12 changed files with 466 additions and 230 deletions

View File

@@ -1,11 +1,16 @@
import { sendRequest } from './networkUtils.js';
import { toggleVisibility, showToast, attachEnterKeyListener, showCustomConfirmModal } from './domUtils.js';
import {
toggleVisibility,
showToast as originalShowToast,
attachEnterKeyListener,
showCustomConfirmModal
} from './domUtils.js';
import { loadFileList } from './fileListView.js';
import { initFileActions } from './fileActions.js';
import { renderFileTable } from './fileListView.js';
import { loadFolderTree } from './folderManager.js';
import {
openTOTPLoginModal,
openTOTPLoginModal as originalOpenTOTPLoginModal,
openUserPanel,
openTOTPModal,
closeTOTPModal,
@@ -24,6 +29,43 @@ const currentOIDCConfig = {
};
window.currentOIDCConfig = currentOIDCConfig;
/* ----------------- TOTP & Toast Overrides ----------------- */
// detect if were in a pendingTOTP state
window.pendingTOTP = new URLSearchParams(window.location.search).get('totp_required') === '1';
// override showToast to suppress the "Please log in to continue." toast during TOTP
function showToast(msg) {
if (window.pendingTOTP && msg === "Please log in to continue.") {
return;
}
originalShowToast(msg);
}
window.showToast = showToast;
// wrap the TOTP modal opener to disable other login buttons only for Basic/OIDC flows
function openTOTPLoginModal() {
originalOpenTOTPLoginModal();
const isFormLogin = Boolean(window.__lastLoginData);
if (!isFormLogin) {
// disable BasicAuth link
const basicLink = document.querySelector("a[href='login_basic.php']");
if (basicLink) {
basicLink.style.pointerEvents = 'none';
basicLink.style.opacity = '0.5';
}
// disable OIDC button
const oidcBtn = document.getElementById("oidcLoginBtn");
if (oidcBtn) {
oidcBtn.disabled = true;
oidcBtn.style.opacity = '0.5';
}
// hide the form login
const authForm = document.getElementById("authForm");
if (authForm) authForm.style.display = 'none';
}
}
/* ----------------- Utility Functions ----------------- */
function updateItemsPerPageSelect() {
const selectElem = document.querySelector(".form-control.bottom-select");
@@ -85,7 +127,6 @@ function updateAuthenticatedUI(data) {
if (typeof data.totp_enabled !== "undefined") {
localStorage.setItem("userTOTPEnabled", data.totp_enabled ? "true" : "false");
}
if (data.username) {
localStorage.setItem("username", data.username);
}
@@ -103,11 +144,8 @@ function updateAuthenticatedUI(data) {
restoreBtn.id = "restoreFilesBtn";
restoreBtn.classList.add("btn", "btn-warning");
restoreBtn.innerHTML = '<i class="material-icons" title="Restore/Delete Trash">restore_from_trash</i>';
if (firstButton) {
insertAfter(restoreBtn, firstButton);
} else {
headerButtons.appendChild(restoreBtn);
}
if (firstButton) insertAfter(restoreBtn, firstButton);
else headerButtons.appendChild(restoreBtn);
}
restoreBtn.style.display = "block";
@@ -128,7 +166,7 @@ function updateAuthenticatedUI(data) {
const adminPanelBtn = document.getElementById("adminPanelBtn");
if (adminPanelBtn) adminPanelBtn.style.display = "none";
}
if (window.location.hostname !== "demo.filerise.net") {
let userPanelBtn = document.getElementById("userPanelBtn");
if (!userPanelBtn) {
@@ -136,17 +174,10 @@ function updateAuthenticatedUI(data) {
userPanelBtn.id = "userPanelBtn";
userPanelBtn.classList.add("btn", "btn-user");
userPanelBtn.innerHTML = '<i class="material-icons" title="User Panel">account_circle</i>';
let adminPanelBtn = document.getElementById("adminPanelBtn");
if (adminPanelBtn) {
insertAfter(userPanelBtn, adminPanelBtn);
} else {
const firstButton = headerButtons.firstElementChild;
if (firstButton) {
insertAfter(userPanelBtn, firstButton);
} else {
headerButtons.appendChild(userPanelBtn);
}
}
const adminBtn = document.getElementById("adminPanelBtn");
if (adminBtn) insertAfter(userPanelBtn, adminBtn);
else if (firstButton) insertAfter(userPanelBtn, firstButton);
else headerButtons.appendChild(userPanelBtn);
userPanelBtn.addEventListener("click", openUserPanel);
} else {
userPanelBtn.style.display = "block";
@@ -193,6 +224,7 @@ function checkAuthentication(showLoginToast = true) {
/* ----------------- Authentication Submission ----------------- */
function submitLogin(data) {
setLastLoginData(data);
window.__lastLoginData = data;
sendRequest("auth.php", "POST", data, { "X-CSRF-Token": window.csrfToken })
.then(response => {
if (response.success) {
@@ -220,7 +252,7 @@ function submitLogin(data) {
}
window.submitLogin = submitLogin;
/* ----------------- Other Helpers and Initialization ----------------- */
/* ----------------- Other Helpers ----------------- */
window.changeItemsPerPage = function (value) {
localStorage.setItem("itemsPerPage", value);
if (typeof renderFileTable === "function") renderFileTable(window.currentFolder || "root");
@@ -259,7 +291,7 @@ function loadUserList() {
closeRemoveUserModal();
}
})
.catch(() => { });
.catch(() => {});
}
window.loadUserList = loadUserList;
@@ -286,7 +318,7 @@ function initAuth() {
method: "POST",
credentials: "include",
headers: { "X-CSRF-Token": window.csrfToken }
}).then(() => window.location.reload(true)).catch(() => { });
}).then(() => window.location.reload(true)).catch(() => {});
});
document.getElementById("addUserBtn").addEventListener("click", function () {
resetUserForm();
@@ -352,7 +384,7 @@ function initAuth() {
showToast("Error: " + (data.error || "Could not remove user"));
}
})
.catch(() => { });
.catch(() => {});
});
document.getElementById("cancelRemoveUserBtn").addEventListener("click", closeRemoveUserModal);
document.getElementById("changePasswordBtn").addEventListener("click", function () {
@@ -404,13 +436,19 @@ document.addEventListener("DOMContentLoaded", function () {
disableBasicAuth: localStorage.getItem("disableBasicAuth") === "true",
disableOIDCLogin: localStorage.getItem("disableOIDCLogin") === "true"
});
const oidcLoginBtn = document.getElementById("oidcLoginBtn");
if (oidcLoginBtn) {
oidcLoginBtn.addEventListener("click", () => {
// Redirect to the OIDC auth endpoint. The endpoint can be adjusted if needed.
window.location.href = "auth.php?oidc=initiate";
});
}
// If TOTP is pending, show modal and skip normal auth init
if (window.pendingTOTP) {
openTOTPLoginModal();
return;
}
});
export { initAuth, checkAuthentication };

View File

@@ -3,61 +3,102 @@ import { sendRequest } from './networkUtils.js';
const version = "v1.0.7";
const adminTitle = `Admin Panel <small style="font-size: 12px; color: gray;">${version}</small>`;
let lastLoginData = null;
let lastLoginData = null;
export function setLastLoginData(data) {
lastLoginData = data;
lastLoginData = data;
// expose to auth.js so it can tell form-login vs basic/oidc
window.__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";
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 = `
if (!totpLoginModal) {
totpLoginModal = document.createElement("div");
totpLoginModal.id = "totpLoginModal";
totpLoginModal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
top: 0; left: 0;
width: 100vw; height: 100vh;
background-color: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
display: flex; justify-content: center; align-items: center;
z-index: 3200;
`;
totpLoginModal.innerHTML = `
<div style="background: ${modalBg}; padding: 20px; border-radius: 8px; text-align: center; position: relative; color: ${textColor};">
<span id="closeTOTPLoginModal" style="position: absolute; top: 10px; right: 10px; cursor: pointer; font-size: 24px;">&times;</span>
totpLoginModal.innerHTML = `
<div style="background: ${modalBg}; padding:20px; border-radius:8px; text-align:center; position:relative; color:${textColor};">
<span id="closeTOTPLoginModal" style="position:absolute; top:10px; right:10px; cursor:pointer; font-size:24px;">&times;</span>
<h3>Enter TOTP Code</h3>
<input type="text" id="totpLoginInput" maxlength="6" style="font-size:24px; text-align:center; width:100%; padding:10px;" placeholder="6-digit code" />
<input type="text" id="totpLoginInput" maxlength="6"
style="font-size:24px; text-align:center; width:100%; padding:10px;"
placeholder="6-digit code" />
</div>
`;
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;
document.body.appendChild(totpLoginModal);
document.getElementById("closeTOTPLoginModal").addEventListener("click", () => {
totpLoginModal.style.display = "none";
});
const totpInput = document.getElementById("totpLoginInput");
totpInput.focus();
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",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": window.csrfToken
},
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");
this.value = "";
totpLoginModal.style.display = "flex";
totpInput.focus();
});
}
}
});
} else {
totpLoginModal.style.display = "flex";
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 = "";
totpInput.focus();
}
}
}
export function openUserPanel() {

View File

@@ -297,7 +297,7 @@ export function renderGalleryView(folder) {
<button class="btn btn-sm btn-warning rename-btn" onclick='renameFile(${JSON.stringify(file.name)}, ${JSON.stringify(file.folder || "root")})' title="Rename">
<i class="material-icons">drive_file_rename_outline</i>
</button>
<button class="btn btn-sm btn-secondary share-btn" onclick='openShareModal(${JSON.stringify(file)}, ${JSON.stringify(folder)})' title="Share">
<button class="btn btn-sm btn-secondary share-btn" data-file="${escapeHTML(file.name)}" title="Share">
<i class="material-icons">share</i>
</button>
</div>
@@ -306,10 +306,23 @@ export function renderGalleryView(folder) {
});
galleryHTML += "</div>";
fileListContainer.innerHTML = galleryHTML;
createViewToggleButton();
updateFileActionButtons();
// Bind share button clicks
document.querySelectorAll(".share-btn").forEach(btn => {
btn.addEventListener("click", e => {
e.stopPropagation();
const fileName = btn.getAttribute("data-file");
const file = fileData.find(f => f.name === fileName);
import('./filePreview.js').then(module => {
module.openShareModal(file, folder);
});
});
});
}
export function sortFiles(column, folder) {

View File

@@ -253,4 +253,5 @@ export function displayFilePreview(file, container) {
}
}
window.previewFile = previewFile;
window.previewFile = previewFile;
window.openShareModal = openShareModal;