release(v2.0.2): add config-driven demo mode and lock demo account changes

This commit is contained in:
Ryan
2025-11-23 05:58:39 -05:00
committed by GitHub
parent fd8029a6bf
commit 827e65e367
7 changed files with 95 additions and 47 deletions

View File

@@ -1,5 +1,17 @@
# Changelog
## Changes 11/23/2025 (v2.0.2)
release(v2.0.2): add config-driven demo mode and lock demo account changes
- Wire FR_DEMO_MODE through AdminModel/siteConfig and admin getConfig (demoMode flag)
- Drive demo detection in JS from __FR_SITE_CFG__.demoMode instead of hostname
- Show consistent login tip + toasts for demo using shared __FR_DEMO__ flag
- Block password changes for the demo user and profile picture uploads when in demo mode
- Keep normal user dropdown/admin UI visible even on the demo, while still protecting the demo account
---
## Changes 11/23/2025 (v2.0.0)
### FileRise Core v2.0.0 & FileRise Pro v1.1.0

View File

@@ -16,6 +16,7 @@ define('REGEX_FOLDER_NAME','/^(?!^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$)(?!.*[.
define('PATTERN_FOLDER_NAME','[\p{L}\p{N}_\-\s\/\\\\]+');
define('REGEX_FILE_NAME', '/^[^\x00-\x1F\/\\\\]{1,255}$/u');
define('REGEX_USER', '/^[\p{L}\p{N}_\- ]+$/u');
define('FR_DEMO_MODE', false);
date_default_timezone_set(TIMEZONE);

View File

@@ -34,18 +34,19 @@ window.currentOIDCConfig = currentOIDCConfig;
(function installToastFilter() {
const isDemoHost = location.hostname.toLowerCase() === 'demo.filerise.net';
window.__FR_TOAST_FILTER__ = function (msgKeyOrText) {
const isDemoMode = !!window.__FR_DEMO__;
// Suppress the nag while doing TOTP step-up
if (window.pendingTOTP && (msgKeyOrText === 'please_log_in_to_continue' ||
/please log in/i.test(String(msgKeyOrText)))) {
return null; // suppress
}
// Demo host
if (isDemoHost && (msgKeyOrText === 'please_log_in_to_continue' ||
/please log in/i.test(String(msgKeyOrText)))) {
// Demo mode: swap login prompt for demo creds
if (isDemoMode &&
(msgKeyOrText === 'please_log_in_to_continue' ||
/please log in/i.test(String(msgKeyOrText)))) {
return "Demo site — use:\nUsername: demo\nPassword: demo";
}
@@ -81,14 +82,16 @@ window.pendingTOTP = new URLSearchParams(window.location.search).get('totp_requi
// override showToast to suppress the "Please log in to continue." toast during TOTP
function showToast(msgKeyOrText, type) {
const isDemoHost = window.location.hostname.toLowerCase() === "demo.filerise.net";
const isDemoMode = !!window.__FR_DEMO__;
// If it's the pre-login prompt and we're on the demo site, show demo creds instead.
if (isDemoHost) {
// For the pre-login prompt in demo mode, show demo creds instead
if (isDemoMode &&
(msgKeyOrText === "please_log_in_to_continue" ||
/please log in/i.test(String(msgKeyOrText)))) {
return originalShowToast("Demo site — use: \nUsername: demo\nPassword: demo", 12000);
}
// Dont nag during pending TOTP, as you already had
// Dont nag during pending TOTP
if (window.pendingTOTP && msgKeyOrText === "please_log_in_to_continue") {
return;
}
@@ -97,11 +100,10 @@ function showToast(msgKeyOrText, type) {
let msg = msgKeyOrText;
try {
const translated = t(msgKeyOrText);
// If t() changed it or it's a key-like string, use the translation
if (typeof translated === "string" && translated !== msgKeyOrText) {
msg = translated;
}
} catch { /* if t() isnt available here, just use the original */ }
} catch { }
return originalShowToast(msg);
}
@@ -351,26 +353,8 @@ export async function updateAuthenticatedUI(data) {
if (r) r.style.display = "none";
}
// b) admin panel button only on demo.filerise.net
if (data.isAdmin && window.location.hostname === "demo.filerise.net") {
let a = document.getElementById("adminPanelBtn");
if (!a) {
a = document.createElement("button");
a.id = "adminPanelBtn";
a.classList.add("btn", "btn-info");
a.setAttribute("data-i18n-title", "admin_panel");
a.innerHTML = '<i class="material-icons">admin_panel_settings</i>';
insertAfter(a, document.getElementById("restoreFilesBtn"));
a.addEventListener("click", openAdminPanel);
}
a.style.display = "block";
} else {
const a = document.getElementById("adminPanelBtn");
if (a) a.style.display = "none";
}
// c) user dropdown on non-demo
if (window.location.hostname !== "demo.filerise.net") {
{
let dd = document.getElementById("userDropdown");
// choose icon *or* img
@@ -866,6 +850,10 @@ function initAuth() {
});
document.getElementById("cancelRemoveUserBtn").addEventListener("click", closeRemoveUserModal);
document.getElementById("changePasswordBtn").addEventListener("click", function () {
if (window.__FR_DEMO__) {
showToast("Password changes are disabled on the public demo.");
return;
}
document.getElementById("changePasswordModal").style.display = "block";
document.getElementById("oldPassword").focus();
});
@@ -873,6 +861,10 @@ function initAuth() {
document.getElementById("changePasswordModal").style.display = "none";
});
document.getElementById("saveNewPasswordBtn").addEventListener("click", function () {
if (window.__FR_DEMO__) {
showToast("Password changes are disabled on the public demo.");
return;
}
const oldPassword = document.getElementById("oldPassword").value.trim();
const newPassword = document.getElementById("newPassword").value.trim();
const confirmPassword = document.getElementById("confirmPassword").value.trim();

View File

@@ -62,23 +62,43 @@ async function ensureToastReady() {
}
function isDemoHost() {
// Handles optional "www." just in case
try {
const cfg = window.__FR_SITE_CFG__ || {};
if (typeof cfg.demoMode !== 'undefined') {
return !!cfg.demoMode;
}
} catch {
// ignore
}
// Fallback for older configs / direct demo host:
return location.hostname.replace(/^www\./, '') === 'demo.filerise.net';
}
function showLoginTip(message) {
const tip = document.getElementById('fr-login-tip');
if (!tip) return;
tip.innerHTML = ''; // clear
if (message) tip.append(document.createTextNode(message));
if (location.hostname.replace(/^www\./, '') === 'demo.filerise.net') {
const line = document.createElement('div'); line.style.marginTop = '6px';
const mk = t => { const k = document.createElement('code'); k.textContent = t; return k; };
line.append(document.createTextNode('Demo login — user: '), mk('demo'),
document.createTextNode(' · pass: '), mk('demo'));
tip.innerHTML = ''; // clear
if (message) {
tip.append(document.createTextNode(message));
}
if (isDemoHost()) {
const line = document.createElement('div');
line.style.marginTop = '6px';
const mk = t => {
const k = document.createElement('code');
k.textContent = t;
return k;
};
line.append(
document.createTextNode('Demo login — user: '), mk('demo'),
document.createTextNode(' · pass: '), mk('demo')
);
tip.append(line);
}
tip.style.display = 'block'; // reveal without shifting layout
tip.style.display = 'block';
}
async function hideOverlaySmoothly(overlay) {
@@ -552,11 +572,13 @@ function bindDarkMode() {
const r = await fetch('/api/siteConfig.php', { credentials: 'include' });
const j = await r.json().catch(() => ({}));
window.__FR_SITE_CFG__ = j || {};
window.__FR_DEMO__ = !!(window.__FR_SITE_CFG__.demoMode);
// Early pass: title + login options (skip touching <h1> to avoid flicker)
applySiteConfig(window.__FR_SITE_CFG__, { phase: 'early' });
return window.__FR_SITE_CFG__;
} catch {
window.__FR_SITE_CFG__ = {};
window.__FR_DEMO__ = false;
applySiteConfig({}, { phase: 'early' });
return null;
}

View File

@@ -176,6 +176,7 @@ class AdminController
'version' => $proVersion,
'license' => $licenseString,
],
'demoMode' => defined('FR_DEMO_MODE') ? (bool)FR_DEMO_MODE : false,
];
$isAdmin = !empty($_SESSION['authenticated']) && !empty($_SESSION['isAdmin']);

View File

@@ -272,6 +272,15 @@ class UserController
echo json_encode(["error" => "No username in session"]);
exit;
}
// Block changing the demo account password when in demo mode
if (FR_DEMO_MODE && $username === 'demo') {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'success' => false,
'error' => 'Password changes are disabled on the public demo.'
]);
exit;
}
$data = self::readJson();
$oldPassword = trim($data["oldPassword"] ?? "");
@@ -608,6 +617,15 @@ class UserController
self::requireAuth();
self::requireCsrf();
if (defined('FR_DEMO_MODE') && FR_DEMO_MODE) {
http_response_code(403);
echo json_encode([
'success' => false,
'error' => 'Profile picture changes are disabled in the demo environment.',
]);
exit;
}
if (empty($_FILES['profile_picture']) || $_FILES['profile_picture']['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'No file uploaded or error']);

View File

@@ -121,6 +121,7 @@ private static function sanitizeLogoUrl($url): string
$config['branding']['headerBgDark'] ?? ''
),
],
'demoMode' => (defined('FR_DEMO_MODE') && FR_DEMO_MODE),
];
// NEW: include ONLYOFFICE minimal public flag
@@ -136,16 +137,17 @@ private static function sanitizeLogoUrl($url): string
$locked = defined('ONLYOFFICE_ENABLED') || defined('ONLYOFFICE_JWT_SECRET')
|| defined('ONLYOFFICE_DOCS_ORIGIN') || defined('ONLYOFFICE_PUBLIC_ORIGIN');
if ($locked) {
$ooEnabled = defined('ONLYOFFICE_ENABLED') ? (bool)ONLYOFFICE_ENABLED : false;
} else {
$ooEnabled = isset($config['onlyoffice']['enabled']) ? (bool)$config['onlyoffice']['enabled'] : false;
}
if ($locked) {
$ooEnabled = defined('ONLYOFFICE_ENABLED') ? (bool)ONLYOFFICE_ENABLED : false;
} else {
$ooEnabled = isset($config['onlyoffice']['enabled']) ? (bool)$config['onlyoffice']['enabled'] : false;
}
$public['onlyoffice'] = ['enabled' => $ooEnabled];
$public['onlyoffice'] = ['enabled' => $ooEnabled];
$public['demoMode'] = defined('FR_DEMO_MODE') ? (bool)FR_DEMO_MODE : false;
return $public;
}
return $public;
}
/** Write USERS_DIR/siteConfig.json atomically (unencrypted). */
public static function writeSiteConfig(array $publicSubset): array